diff --git a/.devcontainer/mobile/container-compose-overrides.yml b/.devcontainer/mobile/container-compose-overrides.yml index d6cd95018f..99e41cbece 100644 --- a/.devcontainer/mobile/container-compose-overrides.yml +++ b/.devcontainer/mobile/container-compose-overrides.yml @@ -6,28 +6,35 @@ services: - IMMICH_SERVER_URL=http://127.0.0.1:2283/ volumes: !override # bind mount host to /workspaces/immich - ..:/workspaces/immich - - cli_node_modules:/workspaces/immich/cli/node_modules - - e2e_node_modules:/workspaces/immich/e2e/node_modules - - open_api_node_modules:/workspaces/immich/open-api/typescript-sdk/node_modules - - server_node_modules:/workspaces/immich/server/node_modules - - web_node_modules:/workspaces/immich/web/node_modules - - ${UPLOAD_LOCATION}/photos:/data + - ${UPLOAD_LOCATION:-upload-devcontainer-volume}${UPLOAD_LOCATION:+/photos}:/data + - pnpm-store:/usr/src/app/.pnpm-store + - server-node_modules:/usr/src/app/server/node_modules + - web-node_modules:/usr/src/app/web/node_modules + - github-node_modules:/usr/src/app/.github/node_modules + - cli-node_modules:/usr/src/app/cli/node_modules + - docs-node_modules:/usr/src/app/docs/node_modules + - e2e-node_modules:/usr/src/app/e2e/node_modules + - sdk-node_modules:/usr/src/app/open-api/typescript-sdk/node_modules + - app-node_modules:/usr/src/app/node_modules + - sveltekit:/usr/src/app/web/.svelte-kit + - coverage:/usr/src/app/web/coverage - /etc/localtime:/etc/localtime:ro - + immich-web: + env_file: !reset [] + immich-machine-learning: + env_file: !reset [] database: + env_file: !reset [] + environment: !override + POSTGRES_PASSWORD: ${DB_PASSWORD-postgres} + POSTGRES_USER: ${DB_USERNAME-postgres} + POSTGRES_DB: ${DB_DATABASE_NAME-immich} + POSTGRES_INITDB_ARGS: '--data-checksums' + POSTGRES_HOST_AUTH_METHOD: md5 volumes: - - ${UPLOAD_LOCATION}/postgres:/var/lib/postgresql/data - + - ${UPLOAD_LOCATION:-postgres-devcontainer-volume}${UPLOAD_LOCATION:+/postgres}:/var/lib/postgresql/data + redis: + env_file: !reset [] volumes: - # Node modules for each service to avoid conflicts and ensure consistent dependencies - cli_node_modules: - e2e_node_modules: - open_api_node_modules: - server_node_modules: - web_node_modules: - - # UPLOAD_LOCATION must be set to a absolute path or vol-upload - vol-upload: - - # DB_DATA_LOCATION must be set to a absolute path or vol-database - vol-database: + upload-devcontainer-volume: + postgres-devcontainer-volume: diff --git a/.devcontainer/mobile/devcontainer.json b/.devcontainer/mobile/devcontainer.json index 0dbcc8e9c8..140a2ecac3 100644 --- a/.devcontainer/mobile/devcontainer.json +++ b/.devcontainer/mobile/devcontainer.json @@ -40,7 +40,7 @@ "userEnvProbe": "loginInteractiveShell", "remoteEnv": { // The location where your uploaded files are stored - "UPLOAD_LOCATION": "${localEnv:UPLOAD_LOCATION:./Library}", + "UPLOAD_LOCATION": "${localEnv:UPLOAD_LOCATION:./library}", // Connection secret for postgres. You should change it to a random password // Please use only the characters `A-Za-z0-9`, without special characters or spaces "DB_PASSWORD": "${localEnv:DB_PASSWORD:postgres}", diff --git a/.github/.nvmrc b/.github/.nvmrc index e2228113dd..0a492611a0 100644 --- a/.github/.nvmrc +++ b/.github/.nvmrc @@ -1 +1 @@ -22.19.0 +24.11.0 diff --git a/.github/labeler.yml b/.github/labeler.yml index c0c52f1d7e..d0e4a3097b 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -6,7 +6,6 @@ cli: documentation: - changed-files: - any-glob-to-any-file: - - docs/blob/** - docs/docs/** - docs/src/** - docs/static/** @@ -32,7 +31,7 @@ documentation: 🧠machine-learning: - changed-files: - any-glob-to-any-file: - - machine-learning/app/** + - machine-learning/** changelog:translation: - head-branch: ['^chore/translations$'] diff --git a/.github/mise.toml b/.github/mise.toml new file mode 100644 index 0000000000..6930d41187 --- /dev/null +++ b/.github/mise.toml @@ -0,0 +1,10 @@ +[tasks.install] +run = "pnpm install --filter github --frozen-lockfile" + +[tasks.format] +env._.path = "./node_modules/.bin" +run = "prettier --check ." + +[tasks."format-fix"] +env._.path = "./node_modules/.bin" +run = "prettier --write ." diff --git a/.github/workflows/build-mobile.yml b/.github/workflows/build-mobile.yml index 1b57731b23..c214ba564e 100644 --- a/.github/workflows/build-mobile.yml +++ b/.github/workflows/build-mobile.yml @@ -1,12 +1,16 @@ name: Build Mobile on: - workflow_dispatch: workflow_call: inputs: ref: required: false type: string + environment: + description: 'Target environment' + required: true + default: 'development' + type: string secrets: KEY_JKS: required: true @@ -16,6 +20,30 @@ on: required: true ANDROID_STORE_PASSWORD: required: true + APP_STORE_CONNECT_API_KEY_ID: + required: true + APP_STORE_CONNECT_API_KEY_ISSUER_ID: + required: true + APP_STORE_CONNECT_API_KEY: + required: true + IOS_CERTIFICATE_P12: + required: true + IOS_CERTIFICATE_PASSWORD: + required: true + IOS_PROVISIONING_PROFILE: + required: true + IOS_PROVISIONING_PROFILE_SHARE_EXTENSION: + required: true + IOS_PROVISIONING_PROFILE_WIDGET_EXTENSION: + required: true + IOS_DEVELOPMENT_PROVISIONING_PROFILE: + required: true + IOS_DEVELOPMENT_PROVISIONING_PROFILE_SHARE_EXTENSION: + required: true + IOS_DEVELOPMENT_PROVISIONING_PROFILE_WIDGET_EXTENSION: + required: true + FASTLANE_TEAM_ID: + required: true pull_request: push: branches: [main] @@ -34,10 +62,17 @@ jobs: outputs: should_run: ${{ steps.check.outputs.should_run }} steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Check what should run id: check - uses: immich-app/devtools/actions/pre-job@24820aa4ef67959b0dcf69a438cccf00d7c7042b # pre-job-action-v1.0.1 + uses: immich-app/devtools/actions/pre-job@08bac802a312fc89808e0dd589271ca0974087b5 # pre-job-action-v2.0.0 with: + github-token: ${{ steps.token.outputs.token }} filters: | mobile: - 'mobile/**' @@ -55,10 +90,17 @@ jobs: runs-on: mich steps: - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: ref: ${{ inputs.ref || github.sha }} persist-credentials: false + token: ${{ steps.token.outputs.token }} - name: Create the Keystore env: @@ -66,14 +108,14 @@ jobs: working-directory: ./mobile run: printf "%s" $KEY_JKS | base64 -d > android/key.jks - - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + - uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: distribution: 'zulu' java-version: '17' - name: Restore Gradle Cache id: cache-gradle-restore - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: | ~/.gradle/caches @@ -123,14 +165,14 @@ jobs: fi - name: Publish Android Artifact - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: release-apk-signed path: mobile/build/app/outputs/flutter-apk/*.apk - name: Save Gradle Cache id: cache-gradle-save - uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4 + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 if: github.ref == 'refs/heads/main' with: path: | @@ -140,3 +182,150 @@ jobs: mobile/android/.gradle mobile/.dart_tool key: ${{ steps.cache-gradle-restore.outputs.cache-primary-key }} + + build-sign-ios: + name: Build and sign iOS + needs: pre-job + permissions: + contents: read + # Run on main branch or workflow_dispatch, or on PRs/other branches (build only, no upload) + if: ${{ !github.event.pull_request.head.repo.fork && fromJSON(needs.pre-job.outputs.should_run).mobile == true }} + runs-on: macos-latest + + steps: + - name: Checkout code + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + with: + ref: ${{ inputs.ref || github.sha }} + persist-credentials: false + + - name: Setup Flutter SDK + uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e # v2 + with: + channel: 'stable' + flutter-version-file: ./mobile/pubspec.yaml + cache: true + + - name: Install Flutter dependencies + working-directory: ./mobile + run: flutter pub get + + - name: Generate translation files + run: dart run easy_localization:generate -S ../i18n && dart run bin/generate_keys.dart + working-directory: ./mobile + + - name: Generate platform APIs + run: make pigeon + working-directory: ./mobile + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.3' + working-directory: ./mobile/ios + + - name: Install CocoaPods dependencies + working-directory: ./mobile/ios + run: | + pod install + + - name: Install Fastlane + working-directory: ./mobile/ios + run: | + gem install bundler + bundle config set --local path 'vendor/bundle' + bundle install + + - name: Create API Key + env: + API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }} + API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }} + API_KEY_CONTENT: ${{ secrets.APP_STORE_CONNECT_API_KEY }} + working-directory: ./mobile/ios + run: | + mkdir -p ~/.appstoreconnect/private_keys + echo "$API_KEY_CONTENT" | base64 --decode > ~/.appstoreconnect/private_keys/AuthKey_${API_KEY_ID}.p8 + + - name: Import Certificate and Provisioning Profiles + env: + IOS_CERTIFICATE_P12: ${{ secrets.IOS_CERTIFICATE_P12 }} + IOS_CERTIFICATE_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }} + IOS_PROVISIONING_PROFILE: ${{ secrets.IOS_PROVISIONING_PROFILE }} + IOS_PROVISIONING_PROFILE_SHARE_EXTENSION: ${{ secrets.IOS_PROVISIONING_PROFILE_SHARE_EXTENSION }} + IOS_PROVISIONING_PROFILE_WIDGET_EXTENSION: ${{ secrets.IOS_PROVISIONING_PROFILE_WIDGET_EXTENSION }} + IOS_DEVELOPMENT_PROVISIONING_PROFILE: ${{ secrets.IOS_DEVELOPMENT_PROVISIONING_PROFILE }} + IOS_DEVELOPMENT_PROVISIONING_PROFILE_SHARE_EXTENSION: ${{ secrets.IOS_DEVELOPMENT_PROVISIONING_PROFILE_SHARE_EXTENSION }} + IOS_DEVELOPMENT_PROVISIONING_PROFILE_WIDGET_EXTENSION: ${{ secrets.IOS_DEVELOPMENT_PROVISIONING_PROFILE_WIDGET_EXTENSION }} + ENVIRONMENT: ${{ inputs.environment || 'development' }} + working-directory: ./mobile/ios + run: | + # Decode certificate + echo "$IOS_CERTIFICATE_P12" | base64 --decode > certificate.p12 + + # Decode provisioning profiles based on environment + if [[ "$ENVIRONMENT" == "development" ]]; then + echo "$IOS_DEVELOPMENT_PROVISIONING_PROFILE" | base64 --decode > profile_dev.mobileprovision + echo "$IOS_DEVELOPMENT_PROVISIONING_PROFILE_SHARE_EXTENSION" | base64 --decode > profile_dev_share.mobileprovision + echo "$IOS_DEVELOPMENT_PROVISIONING_PROFILE_WIDGET_EXTENSION" | base64 --decode > profile_dev_widget.mobileprovision + ls -lh profile_dev*.mobileprovision + else + echo "$IOS_PROVISIONING_PROFILE" | base64 --decode > profile.mobileprovision + echo "$IOS_PROVISIONING_PROFILE_SHARE_EXTENSION" | base64 --decode > profile_share.mobileprovision + echo "$IOS_PROVISIONING_PROFILE_WIDGET_EXTENSION" | base64 --decode > profile_widget.mobileprovision + ls -lh profile*.mobileprovision + fi + + - name: Create keychain and import certificate + env: + KEYCHAIN_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }} + CERTIFICATE_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }} + working-directory: ./mobile/ios + run: | + # Create keychain + security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain + security set-keychain-settings -t 3600 -u build.keychain + + # Import certificate + security import certificate.p12 -k build.keychain -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security + security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" build.keychain + + # Verify certificate was imported + security find-identity -v -p codesigning build.keychain + + - name: Build and deploy to TestFlight + env: + FASTLANE_TEAM_ID: ${{ secrets.FASTLANE_TEAM_ID }} + IOS_CERTIFICATE_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }} + KEYCHAIN_NAME: build.keychain + KEYCHAIN_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }} + APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }} + APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }} + ENVIRONMENT: ${{ inputs.environment || 'development' }} + BUNDLE_ID_SUFFIX: ${{ inputs.environment == 'production' && '' || 'development' }} + GITHUB_REF: ${{ github.ref }} + working-directory: ./mobile/ios + run: | + # Only upload to TestFlight on main branch + if [[ "$GITHUB_REF" == "refs/heads/main" ]]; then + if [[ "$ENVIRONMENT" == "development" ]]; then + bundle exec fastlane gha_testflight_dev + else + bundle exec fastlane gha_release_prod + fi + else + # Build only, no TestFlight upload for non-main branches + bundle exec fastlane gha_build_only + fi + + - name: Clean up keychain + if: always() + run: | + security delete-keychain build.keychain || true + + - name: Upload IPA artifact + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + name: ios-release-ipa + path: mobile/ios/Runner.ipa diff --git a/.github/workflows/cache-cleanup.yml b/.github/workflows/cache-cleanup.yml index cdff8ed931..7d49d94791 100644 --- a/.github/workflows/cache-cleanup.yml +++ b/.github/workflows/cache-cleanup.yml @@ -18,14 +18,21 @@ jobs: contents: read actions: write steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Check out code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false + token: ${{ steps.token.outputs.token }} - name: Cleanup env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ steps.token.outputs.token }} REF: ${{ github.ref }} run: | gh extension install actions/gh-actions-cache diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 777f1898ec..fc2c9f6853 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -29,15 +29,22 @@ jobs: working-directory: ./cli steps: - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false + token: ${{ steps.token.outputs.token }} - name: Setup pnpm - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version-file: './cli/.nvmrc' registry-url: 'https://registry.npmjs.org' @@ -50,7 +57,7 @@ jobs: - run: pnpm install --frozen-lockfile - run: pnpm build - - run: pnpm publish + - run: pnpm publish --no-git-checks if: ${{ github.event_name == 'release' }} env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} @@ -64,19 +71,26 @@ jobs: needs: publish steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Checkout - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false + token: ${{ steps.token.outputs.token }} - name: Set up QEMU - uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 + uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 - name: Login to GitHub Container Registry - uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 if: ${{ !github.event.pull_request.head.repo.fork }} with: registry: ghcr.io @@ -91,7 +105,7 @@ jobs: - name: Generate docker image tags id: metadata - uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0 + uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5.9.0 with: flavor: | latest=false diff --git a/.github/workflows/close-duplicates.yml b/.github/workflows/close-duplicates.yml index 8470e0e18c..b3c79f81d8 100644 --- a/.github/workflows/close-duplicates.yml +++ b/.github/workflows/close-duplicates.yml @@ -35,7 +35,7 @@ jobs: needs: [get_body, should_run] if: ${{ needs.should_run.outputs.should_run == 'true' }} container: - image: ghcr.io/immich-app/mdq:main@sha256:d8ae47cf2e6cf4e2559bd57a60b73674fe44f897cba2c2bddff2987a05be10a4 + image: ghcr.io/immich-app/mdq:main@sha256:9c905a4ff69f00c4b2f98b40b6090ab3ab18d1a15ed1379733b8691aa1fcb271 outputs: checked: ${{ steps.get_checkbox.outputs.checked }} steps: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 503dd30d9a..34228843ad 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -43,14 +43,21 @@ jobs: # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Checkout repository - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false + token: ${{ steps.token.outputs.token }} # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3 + uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -63,7 +70,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3 + uses: github/codeql-action/autobuild@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -76,6 +83,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3 + uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2 with: category: '/language:${{matrix.language}}' diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index a630d27809..81b11c4aca 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -22,10 +22,17 @@ jobs: outputs: should_run: ${{ steps.check.outputs.should_run }} steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Check what should run id: check - uses: immich-app/devtools/actions/pre-job@24820aa4ef67959b0dcf69a438cccf00d7c7042b # pre-job-action-v1.0.1 + uses: immich-app/devtools/actions/pre-job@08bac802a312fc89808e0dd589271ca0974087b5 # pre-job-action-v2.0.0 with: + github-token: ${{ steps.token.outputs.token }} filters: | server: - 'server/**' @@ -53,11 +60,12 @@ jobs: suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn', '-rknn'] steps: - name: Login to GitHub Container Registry - uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Re-tag image env: REGISTRY_NAME: 'ghcr.io' @@ -82,11 +90,12 @@ jobs: suffix: [''] steps: - name: Login to GitHub Container Registry - uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Re-tag image env: REGISTRY_NAME: 'ghcr.io' @@ -107,24 +116,23 @@ jobs: matrix: include: - device: cpu - tag-suffix: '' - device: cuda - tag-suffix: '-cuda' + suffixes: '-cuda' platforms: linux/amd64 - device: openvino - tag-suffix: '-openvino' + suffixes: '-openvino' platforms: linux/amd64 - device: armnn - tag-suffix: '-armnn' + suffixes: '-armnn' platforms: linux/arm64 - device: rknn - tag-suffix: '-rknn' + suffixes: '-rknn' platforms: linux/arm64 - device: rocm - tag-suffix: '-rocm' + suffixes: '-rocm' platforms: linux/amd64 runner-mapping: '{"linux/amd64": "mich"}' - uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@129aeda75a450666ce96e8bc8126652e717917a7 # multi-runner-build-workflow-0.1.1 + uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@47a2ee86898ccff51592d6572391fb1abcd7f782 # multi-runner-build-workflow-v2.0.1 permissions: contents: read actions: read @@ -138,7 +146,7 @@ jobs: dockerfile: machine-learning/Dockerfile platforms: ${{ matrix.platforms }} runner-mapping: ${{ matrix.runner-mapping }} - tag-suffix: ${{ matrix.tag-suffix }} + suffixes: ${{ matrix.suffixes }} dockerhub-push: ${{ github.event_name == 'release' }} build-args: | DEVICE=${{ matrix.device }} @@ -147,7 +155,7 @@ jobs: name: Build and Push Server needs: pre-job if: ${{ fromJSON(needs.pre-job.outputs.should_run).server == true }} - uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@129aeda75a450666ce96e8bc8126652e717917a7 # multi-runner-build-workflow-0.1.1 + uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@47a2ee86898ccff51592d6572391fb1abcd7f782 # multi-runner-build-workflow-v2.0.1 permissions: contents: read actions: read diff --git a/.github/workflows/docs-build.yml b/.github/workflows/docs-build.yml index 9a35a0ae91..823aa98fc8 100644 --- a/.github/workflows/docs-build.yml +++ b/.github/workflows/docs-build.yml @@ -20,10 +20,17 @@ jobs: outputs: should_run: ${{ steps.check.outputs.should_run }} steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Check what should run id: check - uses: immich-app/devtools/actions/pre-job@24820aa4ef67959b0dcf69a438cccf00d7c7042b # pre-job-action-v1.0.1 + uses: immich-app/devtools/actions/pre-job@08bac802a312fc89808e0dd589271ca0974087b5 # pre-job-action-v2.0.0 with: + github-token: ${{ steps.token.outputs.token }} filters: | docs: - 'docs/**' @@ -46,16 +53,23 @@ jobs: working-directory: ./docs steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false + token: ${{ steps.token.outputs.token }} - name: Setup pnpm - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version-file: './docs/.nvmrc' cache: 'pnpm' @@ -71,7 +85,7 @@ jobs: run: pnpm build - name: Upload build output - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: docs-build-output path: docs/build/ diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/docs-deploy.yml index b504b811e3..5d01646fef 100644 --- a/.github/workflows/docs-deploy.yml +++ b/.github/workflows/docs-deploy.yml @@ -5,6 +5,9 @@ on: types: - completed +env: + TG_NON_INTERACTIVE: 'true' + jobs: checks: name: Docs Deploy Checks @@ -16,12 +19,19 @@ jobs: parameters: ${{ steps.parameters.outputs.result }} artifact: ${{ steps.get-artifact.outputs.result }} steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - if: ${{ github.event.workflow_run.conclusion != 'success' }} run: echo 'The triggering workflow did not succeed' && exit 1 - name: Get artifact id: get-artifact - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: + github-token: ${{ steps.token.outputs.token }} script: | let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ owner: context.repo.owner, @@ -38,10 +48,11 @@ jobs: return { found: true, id: matchArtifact.id }; - name: Determine deploy parameters id: parameters - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: HEAD_SHA: ${{ github.event.workflow_run.head_sha }} with: + github-token: ${{ steps.token.outputs.token }} script: | const eventType = context.payload.workflow_run.event; const isFork = context.payload.workflow_run.repository.fork; @@ -107,17 +118,28 @@ jobs: pull-requests: write if: ${{ fromJson(needs.checks.outputs.artifact).found && fromJson(needs.checks.outputs.parameters).shouldDeploy }} steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false + token: ${{ steps.token.outputs.token }} + + - name: Setup Mise + uses: immich-app/devtools/actions/use-mise@cd24790a7f5f6439ac32cc94f5523cb2de8bfa8c # use-mise-action-v1.1.0 - name: Load parameters id: parameters - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: PARAM_JSON: ${{ needs.checks.outputs.parameters }} with: + github-token: ${{ steps.token.outputs.token }} script: | const parameters = JSON.parse(process.env.PARAM_JSON); core.setOutput("event", parameters.event); @@ -125,10 +147,11 @@ jobs: core.setOutput("shouldDeploy", parameters.shouldDeploy); - name: Download artifact - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: ARTIFACT_JSON: ${{ needs.checks.outputs.artifact }} with: + github-token: ${{ steps.token.outputs.token }} script: | let artifact = JSON.parse(process.env.ARTIFACT_JSON); let download = await github.rest.actions.downloadArtifact({ @@ -150,12 +173,8 @@ jobs: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }} - uses: gruntwork-io/terragrunt-action@aee21a7df999be8b471c2a8564c6cd853cb674e1 # v2.1.8 - with: - tg_version: '0.58.12' - tofu_version: '1.7.1' - tg_dir: 'deployment/modules/cloudflare/docs' - tg_command: 'apply' + working-directory: 'deployment/modules/cloudflare/docs' + run: 'mise run //deployment:tf apply' - name: Deploy Docs Subdomain Output id: docs-output @@ -165,20 +184,12 @@ jobs: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }} - uses: gruntwork-io/terragrunt-action@aee21a7df999be8b471c2a8564c6cd853cb674e1 # v2.1.8 - with: - tg_version: '0.58.12' - tofu_version: '1.7.1' - tg_dir: 'deployment/modules/cloudflare/docs' - tg_command: 'output -json' - - - name: Output Cleaning - id: clean - env: - TG_OUTPUT: ${{ steps.docs-output.outputs.tg_action_output }} + working-directory: 'deployment/modules/cloudflare/docs' run: | - CLEANED=$(echo "$TG_OUTPUT" | sed 's|%0A|\n|g ; s|%3C|<|g' | jq -c .) - echo "output=$CLEANED" >> $GITHUB_OUTPUT + mise run //deployment:tf output -- -json | jq -r ' + "projectName=\(.pages_project_name.value)", + "subdomain=\(.immich_app_branch_subdomain.value)" + ' >> $GITHUB_OUTPUT - name: Publish to Cloudflare Pages # TODO: Action is deprecated @@ -186,7 +197,7 @@ jobs: with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN_PAGES_UPLOAD }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - projectName: ${{ fromJson(steps.clean.outputs.output).pages_project_name.value }} + projectName: ${{ steps.docs-output.outputs.projectName }} workingDirectory: 'docs' directory: 'build' branch: ${{ steps.parameters.outputs.name }} @@ -199,19 +210,16 @@ jobs: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }} - uses: gruntwork-io/terragrunt-action@aee21a7df999be8b471c2a8564c6cd853cb674e1 # v2.1.8 - with: - tg_version: '0.58.12' - tofu_version: '1.7.1' - tg_dir: 'deployment/modules/cloudflare/docs-release' - tg_command: 'apply' + working-directory: 'deployment/modules/cloudflare/docs-release' + run: 'mise run //deployment:tf apply' - name: Comment uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3.2.0 if: ${{ steps.parameters.outputs.event == 'pr' }} with: + token: ${{ steps.token.outputs.token }} number: ${{ fromJson(needs.checks.outputs.parameters).pr_number }} body: | - 📖 Documentation deployed to [${{ fromJson(steps.clean.outputs.output).immich_app_branch_subdomain.value }}](https://${{ fromJson(steps.clean.outputs.output).immich_app_branch_subdomain.value }}) + 📖 Documentation deployed to [${{ steps.docs-output.outputs.subdomain }}](https://${{ steps.docs-output.outputs.subdomain }}) emojis: 'rocket' body-include: '' diff --git a/.github/workflows/docs-destroy.yml b/.github/workflows/docs-destroy.yml index 37653c0990..3ad3f3558e 100644 --- a/.github/workflows/docs-destroy.yml +++ b/.github/workflows/docs-destroy.yml @@ -5,6 +5,9 @@ on: permissions: {} +env: + TG_NON_INTERACTIVE: 'true' + jobs: deploy: name: Docs Destroy @@ -13,10 +16,20 @@ jobs: contents: read pull-requests: write steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false + token: ${{ steps.token.outputs.token }} + + - name: Setup Mise + uses: immich-app/devtools/actions/use-mise@cd24790a7f5f6439ac32cc94f5523cb2de8bfa8c # use-mise-action-v1.1.0 - name: Destroy Docs Subdomain env: @@ -25,16 +38,13 @@ jobs: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }} - uses: gruntwork-io/terragrunt-action@aee21a7df999be8b471c2a8564c6cd853cb674e1 # v2.1.8 - with: - tg_version: '0.58.12' - tofu_version: '1.7.1' - tg_dir: 'deployment/modules/cloudflare/docs' - tg_command: 'destroy -refresh=false' + working-directory: 'deployment/modules/cloudflare/docs' + run: 'mise run //deployment:tf destroy -- -refresh=false' - name: Comment uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3.2.0 with: + token: ${{ steps.token.outputs.token }} number: ${{ github.event.number }} delete: true body-include: '' diff --git a/.github/workflows/fix-format.yml b/.github/workflows/fix-format.yml index bec34c2713..f7f34b929c 100644 --- a/.github/workflows/fix-format.yml +++ b/.github/workflows/fix-format.yml @@ -16,30 +16,30 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 + uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 with: app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: 'Checkout' - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: ref: ${{ github.event.pull_request.head.ref }} token: ${{ steps.generate-token.outputs.token }} persist-credentials: true - name: Setup pnpm - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version-file: './server/.nvmrc' cache: 'pnpm' cache-dependency-path: '**/pnpm-lock.yaml' - name: Fix formatting - run: make install-all && make format-all + run: pnpm --recursive install && pnpm run --recursive --parallel fix:format - name: Commit and push uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9.1.4 @@ -48,9 +48,10 @@ jobs: message: 'chore: fix formatting' - name: Remove label - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 if: always() with: + github-token: ${{ steps.generate-token.outputs.token }} script: | github.rest.issues.removeLabel({ issue_number: context.payload.pull_request.number, diff --git a/.github/workflows/merge-translations.yml b/.github/workflows/merge-translations.yml index a0329c8f73..32e1b1a138 100644 --- a/.github/workflows/merge-translations.yml +++ b/.github/workflows/merge-translations.yml @@ -10,6 +10,11 @@ on: required: true WEBLATE_TOKEN: required: true + inputs: + skip: + description: 'Skip translations' + required: false + type: boolean permissions: {} @@ -23,10 +28,19 @@ jobs: permissions: pull-requests: write steps: + - name: Generate a token + id: generate_token + if: ${{ inputs.skip != true }} + uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Find translation PR id: find_pr + if: ${{ inputs.skip != true }} env: - GH_TOKEN: ${{ github.token }} + GH_TOKEN: ${{ steps.generate_token.outputs.token }} run: | set -euo pipefail @@ -49,20 +63,15 @@ jobs: exit 1 fi - - name: Generate a token - id: generate_token - uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 - with: - app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} - private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - - name: Lock weblate + if: ${{ inputs.skip != true }} env: WEBLATE_TOKEN: ${{ secrets.WEBLATE_TOKEN }} run: | curl --fail-with-body -X POST -H "Authorization: Token $WEBLATE_TOKEN" "$WEBLATE_HOST/api/components/$WEBLATE_COMPONENT/lock/" -d lock=true - name: Commit translations + if: ${{ inputs.skip != true }} env: WEBLATE_TOKEN: ${{ secrets.WEBLATE_TOKEN }} run: | @@ -71,6 +80,7 @@ jobs: - name: Merge PR id: merge_pr + if: ${{ inputs.skip != true }} env: GH_TOKEN: ${{ steps.generate_token.outputs.token }} PR_NUMBER: ${{ steps.find_pr.outputs.PR_NUMBER }} @@ -83,6 +93,7 @@ jobs: gh pr merge "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --auto --squash - name: Wait for PR to merge + if: ${{ inputs.skip != true }} env: GH_TOKEN: ${{ steps.generate_token.outputs.token }} PR_NUMBER: ${{ steps.find_pr.outputs.PR_NUMBER }} @@ -106,7 +117,12 @@ jobs: exit 1 - name: Unlock weblate + if: ${{ inputs.skip != true }} env: WEBLATE_TOKEN: ${{ secrets.WEBLATE_TOKEN }} run: | curl --fail-with-body -X POST -H "Authorization: Token $WEBLATE_TOKEN" "$WEBLATE_HOST/api/components/$WEBLATE_COMPONENT/lock/" -d lock=false + + - name: Report success + run: | + echo "Workflow completed successfully (or was skipped)" diff --git a/.github/workflows/pr-label-validation.yml b/.github/workflows/pr-label-validation.yml index 2c75be8653..0544de3dad 100644 --- a/.github/workflows/pr-label-validation.yml +++ b/.github/workflows/pr-label-validation.yml @@ -13,9 +13,16 @@ jobs: issues: write pull-requests: write steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Require PR to have a changelog label uses: mheap/github-action-required-labels@8afbe8ae6ab7647d0c9f0cfa7c2f939650d22509 # v5.5.1 with: + token: ${{ steps.token.outputs.token }} mode: exactly count: 1 use_regex: true diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml index ad73c78cf8..263426e548 100644 --- a/.github/workflows/pr-labeler.yml +++ b/.github/workflows/pr-labeler.yml @@ -11,4 +11,12 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0 + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + + - uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1 + with: + repo-token: ${{ steps.token.outputs.token }} diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index add68ffbd7..6e7ee8f608 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -10,12 +10,17 @@ on: type: choice options: - 'false' + - major - minor - patch mobileBump: description: 'Bump mobile build number' required: false type: boolean + skipTranslations: + description: 'Skip translations' + required: false + type: boolean concurrency: group: ${{ github.workflow }}-${{ github.ref }}-root @@ -26,6 +31,8 @@ permissions: {} jobs: merge_translations: uses: ./.github/workflows/merge-translations.yml + with: + skip: ${{ inputs.skipTranslations }} permissions: pull-requests: write secrets: @@ -35,31 +42,33 @@ jobs: bump_version: runs-on: ubuntu-latest + needs: [merge_translations] outputs: ref: ${{ steps.push-tag.outputs.commit_long_sha }} permissions: {} # No job-level permissions are needed because it uses the app-token steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 + uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 with: app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Checkout - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: token: ${{ steps.generate-token.outputs.token }} persist-credentials: true + ref: main - name: Install uv - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 + uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2 - name: Setup pnpm - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version-file: './server/.nvmrc' cache: 'pnpm' @@ -90,8 +99,23 @@ jobs: ALIAS: ${{ secrets.ALIAS }} ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }} + # iOS secrets + APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }} + APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }} + APP_STORE_CONNECT_API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY }} + IOS_CERTIFICATE_P12: ${{ secrets.IOS_CERTIFICATE_P12 }} + IOS_CERTIFICATE_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }} + IOS_PROVISIONING_PROFILE: ${{ secrets.IOS_PROVISIONING_PROFILE }} + IOS_PROVISIONING_PROFILE_SHARE_EXTENSION: ${{ secrets.IOS_PROVISIONING_PROFILE_SHARE_EXTENSION }} + IOS_PROVISIONING_PROFILE_WIDGET_EXTENSION: ${{ secrets.IOS_PROVISIONING_PROFILE_WIDGET_EXTENSION }} + IOS_DEVELOPMENT_PROVISIONING_PROFILE: ${{ secrets.IOS_DEVELOPMENT_PROVISIONING_PROFILE }} + IOS_DEVELOPMENT_PROVISIONING_PROFILE_SHARE_EXTENSION: ${{ secrets.IOS_DEVELOPMENT_PROVISIONING_PROFILE_SHARE_EXTENSION }} + IOS_DEVELOPMENT_PROVISIONING_PROFILE_WIDGET_EXTENSION: ${{ secrets.IOS_DEVELOPMENT_PROVISIONING_PROFILE_WIDGET_EXTENSION }} + FASTLANE_TEAM_ID: ${{ secrets.FASTLANE_TEAM_ID }} + with: ref: ${{ needs.bump_version.outputs.ref }} + environment: production prepare_release: runs-on: ubuntu-latest @@ -102,24 +126,25 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 + uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 with: app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Checkout - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: token: ${{ steps.generate-token.outputs.token }} persist-credentials: false - name: Download APK - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 with: name: release-apk-signed + github-token: ${{ steps.generate-token.outputs.token }} - name: Create draft release - uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3 + uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1 with: draft: true tag_name: ${{ env.IMMICH_VERSION }} diff --git a/.github/workflows/preview-label.yaml b/.github/workflows/preview-label.yaml index 1d9a0060ad..8760b67fc0 100644 --- a/.github/workflows/preview-label.yaml +++ b/.github/workflows/preview-label.yaml @@ -13,10 +13,17 @@ jobs: permissions: pull-requests: write steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2.8.2 with: + github-token: ${{ steps.token.outputs.token }} message-id: 'preview-status' - message: 'Deploying preview environment to https://pr-${{ github.event.pull_request.number }}.preview.internal.immich.cloud/' + message: 'Deploying preview environment to https://pr-${{ github.event.pull_request.number }}.preview.internal.immich.build/' remove-label: runs-on: ubuntu-latest @@ -24,8 +31,15 @@ jobs: permissions: pull-requests: write steps: - - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + + - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + github-token: ${{ steps.token.outputs.token }} script: | github.rest.issues.removeLabel({ issue_number: context.payload.pull_request.number, @@ -37,11 +51,13 @@ jobs: - uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2.8.2 if: ${{ github.event.pull_request.head.repo.fork }} with: + github-token: ${{ steps.token.outputs.token }} message-id: 'preview-status' message: 'PRs from forks cannot have preview environments.' - uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2.8.2 if: ${{ !github.event.pull_request.head.repo.fork }} with: + github-token: ${{ steps.token.outputs.token }} message-id: 'preview-status' message: 'Preview environment has been removed.' diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml new file mode 100644 index 0000000000..524f6bc77c --- /dev/null +++ b/.github/workflows/release-pr.yml @@ -0,0 +1,170 @@ +name: Manage release PR +on: + workflow_dispatch: + push: + branches: + - main + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true + +permissions: {} + +jobs: + bump: + runs-on: ubuntu-latest + steps: + - name: Generate a token + id: generate-token + uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ steps.generate-token.outputs.token }} + persist-credentials: true + ref: main + + - name: Install uv + uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2 + + - name: Setup pnpm + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 + + - name: Setup Node + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + with: + node-version-file: './server/.nvmrc' + cache: 'pnpm' + cache-dependency-path: '**/pnpm-lock.yaml' + + - name: Determine release type + id: bump-type + uses: ietf-tools/semver-action@c90370b2958652d71c06a3484129a4d423a6d8a8 # v1.11.0 + with: + token: ${{ steps.generate-token.outputs.token }} + + - name: Bump versions + env: + TYPE: ${{ steps.bump-type.outputs.bump }} + run: | + if [ "$TYPE" == "none" ]; then + exit 1 # TODO: Is there a cleaner way to abort the workflow? + fi + misc/release/pump-version.sh -s $TYPE -m true + + - name: Manage Outline release document + id: outline + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + OUTLINE_API_KEY: ${{ secrets.OUTLINE_API_KEY }} + NEXT_VERSION: ${{ steps.bump-type.outputs.next }} + with: + github-token: ${{ steps.generate-token.outputs.token }} + script: | + const fs = require('fs'); + + const outlineKey = process.env.OUTLINE_API_KEY; + const parentDocumentId = 'da856355-0844-43df-bd71-f8edce5382d9' + const collectionId = 'e2910656-714c-4871-8721-447d9353bd73'; + const baseUrl = 'https://outline.immich.cloud'; + + const listResponse = await fetch(`${baseUrl}/api/documents.list`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${outlineKey}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ parentDocumentId }) + }); + + if (!listResponse.ok) { + throw new Error(`Outline list failed: ${listResponse.statusText}`); + } + + const listData = await listResponse.json(); + const allDocuments = listData.data || []; + + const document = allDocuments.find(doc => doc.title === 'next'); + + let documentId; + let documentUrl; + let documentText; + + if (!document) { + // Create new document + console.log('No existing document found. Creating new one...'); + const notesTmpl = fs.readFileSync('misc/release/notes.tmpl', 'utf8'); + const createResponse = await fetch(`${baseUrl}/api/documents.create`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${outlineKey}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + title: 'next', + text: notesTmpl, + collectionId: collectionId, + parentDocumentId: parentDocumentId, + publish: true + }) + }); + + if (!createResponse.ok) { + throw new Error(`Failed to create document: ${createResponse.statusText}`); + } + + const createData = await createResponse.json(); + documentId = createData.data.id; + const urlId = createData.data.urlId; + documentUrl = `${baseUrl}/doc/next-${urlId}`; + documentText = createData.data.text || ''; + console.log(`Created new document: ${documentUrl}`); + } else { + documentId = document.id; + const docPath = document.url; + documentUrl = `${baseUrl}${docPath}`; + documentText = document.text || ''; + console.log(`Found existing document: ${documentUrl}`); + } + + // Generate GitHub release notes + console.log('Generating GitHub release notes...'); + const releaseNotesResponse = await github.rest.repos.generateReleaseNotes({ + owner: context.repo.owner, + repo: context.repo.repo, + tag_name: `${process.env.NEXT_VERSION}`, + }); + + // Combine the content + const changelog = ` + # ${process.env.NEXT_VERSION} + + ${documentText} + + ${releaseNotesResponse.data.body} + + --- + + ` + + const existingChangelog = fs.existsSync('CHANGELOG.md') ? fs.readFileSync('CHANGELOG.md', 'utf8') : ''; + fs.writeFileSync('CHANGELOG.md', changelog + existingChangelog, 'utf8'); + + core.setOutput('document_url', documentUrl); + + - name: Create PR + id: create-pr + uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 + with: + token: ${{ steps.generate-token.outputs.token }} + commit-message: 'chore: release ${{ steps.bump-type.outputs.next }}' + title: 'chore: release ${{ steps.bump-type.outputs.next }}' + body: 'Release notes: ${{ steps.outline.outputs.document_url }}' + labels: 'changelog:skip' + branch: 'release/next' + draft: true diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml index 460e7da4a7..12bdbc55bf 100644 --- a/.github/workflows/sdk.yml +++ b/.github/workflows/sdk.yml @@ -16,15 +16,22 @@ jobs: run: working-directory: ./open-api/typescript-sdk steps: - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false + token: ${{ steps.token.outputs.token }} - name: Setup pnpm - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 # Setup .npmrc file to publish to npm - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version-file: './open-api/typescript-sdk/.nvmrc' registry-url: 'https://registry.npmjs.org' @@ -35,6 +42,6 @@ jobs: - name: Build run: pnpm build - name: Publish - run: pnpm publish + run: pnpm publish --no-git-checks env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index f5e68fb42d..99ee773af4 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -19,10 +19,17 @@ jobs: outputs: should_run: ${{ steps.check.outputs.should_run }} steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Check what should run id: check - uses: immich-app/devtools/actions/pre-job@24820aa4ef67959b0dcf69a438cccf00d7c7042b # pre-job-action-v1.0.1 + uses: immich-app/devtools/actions/pre-job@08bac802a312fc89808e0dd589271ca0974087b5 # pre-job-action-v2.0.0 with: + github-token: ${{ steps.token.outputs.token }} filters: | mobile: - 'mobile/**' @@ -41,10 +48,17 @@ jobs: run: working-directory: ./mobile steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false + token: ${{ steps.token.outputs.token }} - name: Setup Flutter SDK uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e # v2.21.0 @@ -58,7 +72,7 @@ jobs: - name: Install DCM uses: CQLabs/setup-dcm@8697ae0790c0852e964a6ef1d768d62a6675481a # v2.0.1 with: - github-token: ${{ secrets.GITHUB_TOKEN }} + github-token: ${{ steps.token.outputs.token }} version: auto working-directory: ./mobile diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 773d14e171..44d7250f2f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,10 +16,17 @@ jobs: outputs: should_run: ${{ steps.check.outputs.should_run }} steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Check what should run id: check - uses: immich-app/devtools/actions/pre-job@24820aa4ef67959b0dcf69a438cccf00d7c7042b # pre-job-action-v1.0.1 + uses: immich-app/devtools/actions/pre-job@08bac802a312fc89808e0dd589271ca0974087b5 # pre-job-action-v2.0.0 with: + github-token: ${{ steps.token.outputs.token }} filters: | i18n: - 'i18n/**' @@ -55,14 +62,22 @@ jobs: run: working-directory: ./server steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false + token: ${{ steps.token.outputs.token }} + - name: Setup pnpm - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version-file: './server/.nvmrc' cache: 'pnpm' @@ -92,14 +107,21 @@ jobs: run: working-directory: ./cli steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false + token: ${{ steps.token.outputs.token }} - name: Setup pnpm - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version-file: './cli/.nvmrc' cache: 'pnpm' @@ -132,14 +154,21 @@ jobs: run: working-directory: ./cli steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false + token: ${{ steps.token.outputs.token }} - name: Setup pnpm - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version-file: './cli/.nvmrc' cache: 'pnpm' @@ -167,14 +196,21 @@ jobs: run: working-directory: ./web steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false + token: ${{ steps.token.outputs.token }} - name: Setup pnpm - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version-file: './web/.nvmrc' cache: 'pnpm' @@ -185,7 +221,7 @@ jobs: - name: Run pnpm install run: pnpm rebuild && pnpm install --frozen-lockfile - name: Run linter - run: pnpm lint:p + run: pnpm lint if: ${{ !cancelled() }} - name: Run formatter run: pnpm format @@ -204,14 +240,21 @@ jobs: run: working-directory: ./web steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false + token: ${{ steps.token.outputs.token }} - name: Setup pnpm - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version-file: './web/.nvmrc' cache: 'pnpm' @@ -235,14 +278,21 @@ jobs: permissions: contents: read steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false + token: ${{ steps.token.outputs.token }} - name: Setup pnpm - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version-file: './web/.nvmrc' cache: 'pnpm' @@ -276,14 +326,21 @@ jobs: run: working-directory: ./e2e steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false + token: ${{ steps.token.outputs.token }} - name: Setup pnpm - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version-file: './e2e/.nvmrc' cache: 'pnpm' @@ -315,14 +372,22 @@ jobs: run: working-directory: ./server steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false + submodules: 'recursive' + token: ${{ steps.token.outputs.token }} - name: Setup pnpm - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version-file: './server/.nvmrc' cache: 'pnpm' @@ -346,15 +411,22 @@ jobs: matrix: runner: [ubuntu-latest, ubuntu-24.04-arm] steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false submodules: 'recursive' + token: ${{ steps.token.outputs.token }} - name: Setup pnpm - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version-file: './e2e/.nvmrc' cache: 'pnpm' @@ -394,15 +466,22 @@ jobs: matrix: runner: [ubuntu-latest, ubuntu-24.04-arm] steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false submodules: 'recursive' + token: ${{ steps.token.outputs.token }} - name: Setup pnpm - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version-file: './e2e/.nvmrc' cache: 'pnpm' @@ -441,9 +520,16 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false + token: ${{ steps.token.outputs.token }} - name: Setup Flutter SDK uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e # v2.21.0 with: @@ -466,12 +552,19 @@ jobs: run: working-directory: ./machine-learning steps: - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false + token: ${{ steps.token.outputs.token }} - name: Install uv - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2 + - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 # TODO: add caching when supported (https://github.com/actions/setup-python/pull/818) # with: # python-version: 3.11 @@ -502,14 +595,21 @@ jobs: run: working-directory: ./.github steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false + token: ${{ steps.token.outputs.token }} - name: Setup pnpm - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version-file: './.github/.nvmrc' cache: 'pnpm' @@ -525,9 +625,16 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false + token: ${{ steps.token.outputs.token }} - name: Run ShellCheck uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # 2.0.0 with: @@ -539,14 +646,21 @@ jobs: permissions: contents: read steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false + token: ${{ steps.token.outputs.token }} - name: Setup pnpm - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version-file: './server/.nvmrc' cache: 'pnpm' @@ -581,7 +695,7 @@ jobs: contents: read services: postgres: - image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3@sha256:da52bbead5d818adaa8077c8dcdaad0aaf93038c31ad8348b51f9f0ec1310a4d + image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3@sha256:dbf18b3ffea4a81434c65b71e20d27203baf903a0275f4341e4c16dfd901fd67 env: POSTGRES_PASSWORD: postgres POSTGRES_USER: postgres @@ -594,14 +708,21 @@ jobs: run: working-directory: ./server steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false + token: ${{ steps.token.outputs.token }} - name: Setup pnpm - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version-file: './server/.nvmrc' cache: 'pnpm' diff --git a/.github/workflows/weblate-lock.yml b/.github/workflows/weblate-lock.yml index 36544d4eed..1f0a7608d1 100644 --- a/.github/workflows/weblate-lock.yml +++ b/.github/workflows/weblate-lock.yml @@ -23,10 +23,17 @@ jobs: outputs: should_run: ${{ steps.check.outputs.should_run }} steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Check what should run id: check - uses: immich-app/devtools/actions/pre-job@24820aa4ef67959b0dcf69a438cccf00d7c7042b # pre-job-action-v1.0.1 + uses: immich-app/devtools/actions/pre-job@08bac802a312fc89808e0dd589271ca0974087b5 # pre-job-action-v2.0.0 with: + github-token: ${{ steps.token.outputs.token }} filters: | i18n: - 'i18n/!(en)**\.json' @@ -40,10 +47,16 @@ jobs: permissions: {} if: ${{ fromJSON(needs.pre-job.outputs.should_run).i18n == true }} steps: + - id: token + uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + - name: Bot review status env: PR_NUMBER: ${{ github.event.pull_request.number || github.event.pull_request_review.pull_request.number }} - GH_TOKEN: ${{ github.token }} + GH_TOKEN: ${{ steps.token.outputs.token }} run: | # Then check for APPROVED by the bot, if absent fail gh pr view "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --json reviews | jq -e '.reviews | map(select(.author.login == env.BOT_NAME and .state == "APPROVED")) | length > 0' \ diff --git a/.vscode/launch.json b/.vscode/launch.json index 0cc9b256ca..9ed2bb77b8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -18,6 +18,20 @@ "name": "Immich Workers", "remoteRoot": "/usr/src/app/server", "localRoot": "${workspaceFolder}/server" + }, + { + "type": "node", + "request": "launch", + "name": "Immich CLI", + "program": "${workspaceFolder}/cli/dist/index.js", + "args": ["upload", "--help"], + "runtimeArgs": ["--enable-source-maps"], + "console": "integratedTerminal", + "resolveSourceMapLocations": ["${workspaceFolder}/cli/dist/**/*.js.map"], + "sourceMaps": true, + "outFiles": ["${workspaceFolder}/cli/dist/**/*.js"], + "skipFiles": ["/**"], + "preLaunchTask": "Build Immich CLI" } ] } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 119d6a961b..478a46b4bd 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -5,6 +5,7 @@ "label": "Fix Permissions, Install Dependencies", "type": "shell", "command": "[ -f /immich-devcontainer/container-start.sh ] && /immich-devcontainer/container-start.sh || exit 0", + "isBackground": true, "presentation": { "echo": true, "reveal": "always", @@ -25,6 +26,7 @@ "dependsOn": ["Fix Permissions, Install Dependencies"], "type": "shell", "command": "[ -f /immich-devcontainer/container-start-backend.sh ] && /immich-devcontainer/container-start-backend.sh || exit 0", + "isBackground": true, "presentation": { "echo": true, "reveal": "always", @@ -45,6 +47,7 @@ "dependsOn": ["Fix Permissions, Install Dependencies"], "type": "shell", "command": "[ -f /immich-devcontainer/container-start-frontend.sh ] && /immich-devcontainer/container-start-frontend.sh || exit 0", + "isBackground": true, "presentation": { "echo": true, "reveal": "always", @@ -67,6 +70,11 @@ "runOn": "folderOpen" }, "problemMatcher": [] + }, + { + "label": "Build Immich CLI", + "type": "shell", + "command": "pnpm --filter cli build:dev" } ] } diff --git a/Makefile b/Makefile index 34fb408c41..fc99170676 100644 --- a/Makefile +++ b/Makefile @@ -91,8 +91,6 @@ format-%: pnpm --filter $(call map-package,$*) run format:fix lint-%: pnpm --filter $(call map-package,$*) run lint:fix -lint-web: - pnpm --filter $(call map-package,$*) run lint:p check-%: pnpm --filter $(call map-package,$*) run check check-web: diff --git a/README.md b/README.md index 459cda481c..b540408475 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,8 @@ Deutsch Nederlands Türkçe - 中文 + 简体中文 + 正體中文 Українська Русский Português Brasileiro @@ -38,26 +39,25 @@ ภาษาไทย

-## Disclaimer -- ⚠️ The project is under **very active** development. -- ⚠️ Expect bugs and breaking changes. -- ⚠️ **Do not use the app as the only way to store your photos and videos.** -- ⚠️ Always follow [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) backup plan for your precious photos and videos! +> [!WARNING] +> ⚠️ Always follow [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) backup plan for your precious photos and videos! +> + > [!NOTE] > You can find the main documentation, including installation guides, at https://immich.app/. ## Links -- [Documentation](https://immich.app/docs) -- [About](https://immich.app/docs/overview/introduction) -- [Installation](https://immich.app/docs/install/requirements) +- [Documentation](https://docs.immich.app/) +- [About](https://docs.immich.app/overview/introduction) +- [Installation](https://docs.immich.app/install/requirements) - [Roadmap](https://immich.app/roadmap) - [Demo](#demo) - [Features](#features) -- [Translations](https://immich.app/docs/developer/translations) -- [Contributing](https://immich.app/docs/overview/support-the-project) +- [Translations](https://docs.immich.app/developer/translations) +- [Contributing](https://docs.immich.app/overview/support-the-project) ## Demo @@ -106,7 +106,7 @@ Access the demo [here](https://demo.immich.app). For the mobile app, you can use ## Translations -Read more about translations [here](https://immich.app/docs/developer/translations). +Read more about translations [here](https://docs.immich.app/developer/translations). Translation status diff --git a/cli/.nvmrc b/cli/.nvmrc index e2228113dd..0a492611a0 100644 --- a/cli/.nvmrc +++ b/cli/.nvmrc @@ -1 +1 @@ -22.19.0 +24.11.0 diff --git a/cli/README.md b/cli/README.md index 8fa2ace483..b9d61fce09 100644 --- a/cli/README.md +++ b/cli/README.md @@ -1,30 +1,38 @@ A command-line interface for interfacing with the self-hosted photo manager [Immich](https://immich.app/). -Please see the [Immich CLI documentation](https://immich.app/docs/features/command-line-interface). +Please see the [Immich CLI documentation](https://docs.immich.app/features/command-line-interface). # For developers Before building the CLI, you must build the immich server and the open-api client. To build the server run the following in the server folder: - $ npm install - $ npm run build + $ pnpm install + $ pnpm run build Then, to build the open-api client run the following in the open-api folder: $ ./bin/generate-open-api.sh -To run the Immich CLI from source, run the following in the cli folder: +## Run from build - $ npm install - $ npm run build - $ ts-node . +Go to the cli folder and build it: -You'll need ts-node, the easiest way to install it is to use npm: + $ pnpm install + $ pnpm run build + $ node dist/index.js - $ npm i -g ts-node +## Run and Debug from source (VSCode) + +With VScode you can run and debug the Immich CLI. Go to the launch.json file, find the Immich CLI config and change this with the command you need to debug + +`"args": ["upload", "--help"],` + +replace that for the command of your choice. + +## Install from build You can also build and install the CLI using - $ npm run build - $ npm install -g . + $ pnpm run build + $ pnpm install -g . **** diff --git a/cli/mise.toml b/cli/mise.toml new file mode 100644 index 0000000000..740184e03d --- /dev/null +++ b/cli/mise.toml @@ -0,0 +1,29 @@ +[tasks.install] +run = "pnpm install --filter @immich/cli --frozen-lockfile" + +[tasks.build] +env._.path = "./node_modules/.bin" +run = "vite build" + +[tasks.test] +env._.path = "./node_modules/.bin" +run = "vite" + +[tasks.lint] +env._.path = "./node_modules/.bin" +run = "eslint \"src/**/*.ts\" --max-warnings 0" + +[tasks."lint-fix"] +run = { task = "lint --fix" } + +[tasks.format] +env._.path = "./node_modules/.bin" +run = "prettier --check ." + +[tasks."format-fix"] +env._.path = "./node_modules/.bin" +run = "prettier --write ." + +[tasks.check] +env._.path = "./node_modules/.bin" +run = "tsc --noEmit" diff --git a/cli/package.json b/cli/package.json index 93fcd040c9..6fed806003 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "@immich/cli", - "version": "2.2.91", + "version": "2.2.101", "description": "Command Line Interface (CLI) for Immich", "type": "module", "exports": "./dist/index.js", @@ -20,7 +20,7 @@ "@types/lodash-es": "^4.17.12", "@types/micromatch": "^4.0.9", "@types/mock-fs": "^4.13.1", - "@types/node": "^22.18.1", + "@types/node": "^22.19.0", "@vitest/coverage-v8": "^3.0.0", "byte-size": "^9.0.0", "cli-progress": "^3.12.0", @@ -43,6 +43,7 @@ }, "scripts": { "build": "vite build", + "build:dev": "vite build --sourcemap true", "lint": "eslint \"src/**/*.ts\" --max-warnings 0", "lint:fix": "npm run lint -- --fix", "prepack": "npm run build", @@ -68,6 +69,6 @@ "micromatch": "^4.0.8" }, "volta": { - "node": "22.19.0" + "node": "24.11.0" } } diff --git a/cli/src/commands/asset.spec.ts b/cli/src/commands/asset.spec.ts index 21137a3296..7dce135985 100644 --- a/cli/src/commands/asset.spec.ts +++ b/cli/src/commands/asset.spec.ts @@ -271,7 +271,7 @@ describe('startWatch', () => { }); }); - it('should filger out ignored patterns', async () => { + it('should filter out ignored patterns', async () => { const testFilePath = path.join(testFolder, 'test.jpg'); const ignoredPattern = 'ignored'; const ignoredFolder = path.join(testFolder, ignoredPattern); diff --git a/cli/src/commands/asset.ts b/cli/src/commands/asset.ts index 2af3cf8d5e..ff7b609eef 100644 --- a/cli/src/commands/asset.ts +++ b/cli/src/commands/asset.ts @@ -37,6 +37,7 @@ export interface UploadOptionsDto { dryRun?: boolean; skipHash?: boolean; delete?: boolean; + deleteDuplicates?: boolean; album?: boolean; albumName?: string; includeHidden?: boolean; @@ -70,10 +71,8 @@ const uploadBatch = async (files: string[], options: UploadOptionsDto) => { console.log(JSON.stringify({ newFiles, duplicates, newAssets }, undefined, 4)); } await updateAlbums([...newAssets, ...duplicates], options); - await deleteFiles( - newAssets.map(({ filepath }) => filepath), - options, - ); + + await deleteFiles(newAssets, duplicates, options); }; export const startWatch = async ( @@ -406,28 +405,46 @@ const uploadFile = async (input: string, stats: Stats): Promise => { - if (!options.delete) { - return; +const deleteFiles = async (uploaded: Asset[], duplicates: Asset[], options: UploadOptionsDto): Promise => { + let fileCount = 0; + if (options.delete) { + fileCount += uploaded.length; + } + + if (options.deleteDuplicates) { + fileCount += duplicates.length; } if (options.dryRun) { - console.log(`Would have deleted ${files.length} local asset${s(files.length)}`); + console.log(`Would have deleted ${fileCount} local asset${s(fileCount)}`); + return; + } + + if (fileCount === 0) { return; } console.log('Deleting assets that have been uploaded...'); - const deletionProgress = new SingleBar( { format: 'Deleting local assets | {bar} | {percentage}% | ETA: {eta}s | {value}/{total} assets' }, Presets.shades_classic, ); - deletionProgress.start(files.length, 0); + deletionProgress.start(fileCount, 0); + + const chunkDelete = async (files: Asset[]) => { + for (const assetBatch of chunk(files, options.concurrency)) { + await Promise.all(assetBatch.map((input: Asset) => unlink(input.filepath))); + deletionProgress.update(assetBatch.length); + } + }; try { - for (const assetBatch of chunk(files, options.concurrency)) { - await Promise.all(assetBatch.map((input: string) => unlink(input))); - deletionProgress.update(assetBatch.length); + if (options.delete) { + await chunkDelete(uploaded); + } + + if (options.deleteDuplicates) { + await chunkDelete(duplicates); } } finally { deletionProgress.stop(); diff --git a/cli/src/index.ts b/cli/src/index.ts index a0392186c0..0bc2e5de37 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -8,6 +8,7 @@ import { serverInfo } from 'src/commands/server-info'; import { version } from '../package.json'; const defaultConfigDirectory = path.join(os.homedir(), '.config/immich/'); +const defaultConcurrency = Math.max(1, os.cpus().length - 1); const program = new Command() .name('immich') @@ -66,7 +67,7 @@ program .addOption( new Option('-c, --concurrency ', 'Number of assets to upload at the same time') .env('IMMICH_UPLOAD_CONCURRENCY') - .default(4), + .default(defaultConcurrency), ) .addOption( new Option('-j, --json-output', 'Output detailed information in json format') @@ -74,6 +75,11 @@ program .default(false), ) .addOption(new Option('--delete', 'Delete local assets after upload').env('IMMICH_DELETE_ASSETS')) + .addOption( + new Option('--delete-duplicates', 'Delete local assets that are duplicates (already exist on server)').env( + 'IMMICH_DELETE_DUPLICATES', + ), + ) .addOption(new Option('--no-progress', 'Hide progress bars').env('IMMICH_PROGRESS_BAR').default(true)) .addOption( new Option('--watch', 'Watch for changes and upload automatically') diff --git a/deployment/.env b/deployment/.env new file mode 100644 index 0000000000..f6ce050d29 --- /dev/null +++ b/deployment/.env @@ -0,0 +1,4 @@ +export CLOUDFLARE_ACCOUNT_ID="op://tf/cloudflare/account_id" +export CLOUDFLARE_API_TOKEN="op://tf/cloudflare/api_token" +export TF_STATE_POSTGRES_CONN_STR="op://tf/tf_state/postgres_conn_str" +export TF_VAR_env=$ENVIRONMENT diff --git a/deployment/mise.toml b/deployment/mise.toml new file mode 100644 index 0000000000..f3d07ac31f --- /dev/null +++ b/deployment/mise.toml @@ -0,0 +1,20 @@ +[tools] +terragrunt = "0.91.2" +opentofu = "1.10.6" + +[tasks."tg:fmt"] +run = "terragrunt hclfmt" +description = "Format terragrunt files" + +[tasks.tf] +run = "terragrunt run --all" +description = "Wrapper for terragrunt run-all" +dir = "{{cwd}}" + +[tasks."tf:fmt"] +run = "tofu fmt -recursive tf/" +description = "Format terraform files" + +[tasks."tf:init"] +run = { task = "tf init -- -reconfigure" } +dir = "{{cwd}}" diff --git a/deployment/modules/cloudflare/docs-release/domain.tf b/deployment/modules/cloudflare/docs-release/domain.tf index 0602045f71..3a6f479a74 100644 --- a/deployment/modules/cloudflare/docs-release/domain.tf +++ b/deployment/modules/cloudflare/docs-release/domain.tf @@ -1,11 +1,11 @@ resource "cloudflare_pages_domain" "immich_app_release_domain" { account_id = var.cloudflare_account_id project_name = data.terraform_remote_state.cloudflare_account.outputs.immich_app_archive_pages_project_name - domain = "immich.app" + domain = "docs.immich.app" } resource "cloudflare_record" "immich_app_release_domain" { - name = "immich.app" + name = "docs.immich.app" proxied = true ttl = 1 type = "CNAME" diff --git a/deployment/modules/cloudflare/docs/domain.tf b/deployment/modules/cloudflare/docs/domain.tf index a28fb4c0f8..c5f77de6b4 100644 --- a/deployment/modules/cloudflare/docs/domain.tf +++ b/deployment/modules/cloudflare/docs/domain.tf @@ -1,11 +1,11 @@ resource "cloudflare_pages_domain" "immich_app_branch_domain" { account_id = var.cloudflare_account_id project_name = local.is_release ? data.terraform_remote_state.cloudflare_account.outputs.immich_app_archive_pages_project_name : data.terraform_remote_state.cloudflare_account.outputs.immich_app_preview_pages_project_name - domain = "${var.prefix_name}.${local.deploy_domain_prefix}.immich.app" + domain = "docs.${var.prefix_name}.${local.deploy_domain_prefix}.immich.app" } resource "cloudflare_record" "immich_app_branch_subdomain" { - name = "${var.prefix_name}.${local.deploy_domain_prefix}.immich.app" + name = "docs.${var.prefix_name}.${local.deploy_domain_prefix}.immich.app" proxied = true ttl = 1 type = "CNAME" diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index f97cf0ca0d..e2fb8fbc30 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -1,5 +1,5 @@ # -# WARNING: To install Immich, follow our guide: https://immich.app/docs/install/docker-compose +# WARNING: To install Immich, follow our guide: https://docs.immich.app/install/docker-compose # # Make sure to use the docker-compose.yml of the current release: # @@ -8,8 +8,8 @@ # The compose file on main may not be compatible with the latest release. # For development see: -# - https://immich.app/docs/developer/setup -# - https://immich.app/docs/developer/troubleshooting +# - https://docs.immich.app/developer/setup +# - https://docs.immich.app/developer/troubleshooting name: immich-dev @@ -41,6 +41,7 @@ services: - app-node_modules:/usr/src/app/node_modules - sveltekit:/usr/src/app/web/.svelte-kit - coverage:/usr/src/app/web/coverage + - ../plugins:/build/corePlugin env_file: - .env environment: @@ -55,8 +56,8 @@ services: IMMICH_BUILD_IMAGE_URL: https://github.com/immich-app/immich/pkgs/container/immich-server IMMICH_THIRD_PARTY_SOURCE_URL: https://github.com/immich-app/immich/ IMMICH_THIRD_PARTY_BUG_FEATURE_URL: https://github.com/immich-app/immich/issues - IMMICH_THIRD_PARTY_DOCUMENTATION_URL: https://immich.app/docs - IMMICH_THIRD_PARTY_SUPPORT_URL: https://immich.app/docs/community-guides + IMMICH_THIRD_PARTY_DOCUMENTATION_URL: https://docs.immich.app + IMMICH_THIRD_PARTY_SUPPORT_URL: https://docs.immich.app/community-guides ulimits: nofile: soft: 1048576 @@ -122,7 +123,7 @@ services: ports: - 3003:3003 volumes: - - ../machine-learning:/usr/src/app + - ../machine-learning/immich_ml:/usr/src/immich_ml - model-cache:/cache env_file: - .env @@ -134,13 +135,13 @@ services: redis: container_name: immich_redis - image: docker.io/valkey/valkey:8-bookworm@sha256:fea8b3e67b15729d4bb70589eb03367bab9ad1ee89c876f54327fc7c6e618571 + image: docker.io/valkey/valkey:8@sha256:81db6d39e1bba3b3ff32bd3a1b19a6d69690f94a3954ec131277b9a26b95b3aa healthcheck: test: redis-cli ping || exit 1 database: container_name: immich_postgres - image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:c44be5f2871c59362966d71eab4268170eb6f5653c0e6170184e72b38ffdf107 + image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23 env_file: - .env environment: diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml index c3fb9c7736..e01f4ead22 100644 --- a/docker/docker-compose.prod.yml +++ b/docker/docker-compose.prod.yml @@ -1,5 +1,5 @@ # -# WARNING: To install Immich, follow our guide: https://immich.app/docs/install/docker-compose +# WARNING: To install Immich, follow our guide: https://docs.immich.app/install/docker-compose # # Make sure to use the docker-compose.yml of the current release: # @@ -56,14 +56,14 @@ services: redis: container_name: immich_redis - image: docker.io/valkey/valkey:8-bookworm@sha256:fea8b3e67b15729d4bb70589eb03367bab9ad1ee89c876f54327fc7c6e618571 + image: docker.io/valkey/valkey:8@sha256:81db6d39e1bba3b3ff32bd3a1b19a6d69690f94a3954ec131277b9a26b95b3aa healthcheck: test: redis-cli ping || exit 1 restart: always database: container_name: immich_postgres - image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:c44be5f2871c59362966d71eab4268170eb6f5653c0e6170184e72b38ffdf107 + image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23 env_file: - .env environment: @@ -83,7 +83,7 @@ services: container_name: immich_prometheus ports: - 9090:9090 - image: prom/prometheus@sha256:63805ebb8d2b3920190daf1cb14a60871b16fd38bed42b857a3182bc621f4996 + image: prom/prometheus@sha256:49214755b6153f90a597adcbff0252cc61069f8ab69ce8411285cd4a560e8038 volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - prometheus-data:/prometheus @@ -95,7 +95,7 @@ services: command: ['./run.sh', '-disable-reporting'] ports: - 3000:3000 - image: grafana/grafana:12.1.1-ubuntu@sha256:d1da838234ff2de93e0065ee1bf0e66d38f948dcc5d718c25fa6237e14b4424a + image: grafana/grafana:12.2.1-ubuntu@sha256:797530c642f7b41ba7848c44cfda5e361ef1f3391a98bed1e5d448c472b6826a volumes: - grafana-data:/var/lib/grafana diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 3316c17839..e4e0f964d3 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,5 +1,5 @@ # -# WARNING: To install Immich, follow our guide: https://immich.app/docs/install/docker-compose +# WARNING: To install Immich, follow our guide: https://docs.immich.app/install/docker-compose # # Make sure to use the docker-compose.yml of the current release: # @@ -36,7 +36,7 @@ services: # For hardware acceleration, add one of -[armnn, cuda, rocm, openvino, rknn] to the image tag. # Example tag: ${IMMICH_VERSION:-release}-cuda image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release} - # extends: # uncomment this section for hardware acceleration - see https://immich.app/docs/features/ml-hardware-acceleration + # extends: # uncomment this section for hardware acceleration - see https://docs.immich.app/features/ml-hardware-acceleration # file: hwaccel.ml.yml # service: cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference - use the `-wsl` version for WSL2 where applicable volumes: @@ -49,14 +49,14 @@ services: redis: container_name: immich_redis - image: docker.io/valkey/valkey:8-bookworm@sha256:fea8b3e67b15729d4bb70589eb03367bab9ad1ee89c876f54327fc7c6e618571 + image: docker.io/valkey/valkey:8@sha256:81db6d39e1bba3b3ff32bd3a1b19a6d69690f94a3954ec131277b9a26b95b3aa healthcheck: test: redis-cli ping || exit 1 restart: always database: container_name: immich_postgres - image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:c44be5f2871c59362966d71eab4268170eb6f5653c0e6170184e72b38ffdf107 + image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23 environment: POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_USER: ${DB_USERNAME} diff --git a/docker/example.env b/docker/example.env index 0450dc0805..6641cceaaa 100644 --- a/docker/example.env +++ b/docker/example.env @@ -1,4 +1,4 @@ -# You can find documentation for all the supported env variables at https://immich.app/docs/install/environment-variables +# You can find documentation for all the supported env variables at https://docs.immich.app/install/environment-variables # The location where your uploaded files are stored UPLOAD_LOCATION=./library @@ -9,8 +9,8 @@ DB_DATA_LOCATION=./postgres # To set a timezone, uncomment the next line and change Etc/UTC to a TZ identifier from this list: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List # TZ=Etc/UTC -# The Immich version to use. You can pin this to a specific version like "v1.71.0" -IMMICH_VERSION=release +# The Immich version to use. You can pin this to a specific version like "v2.1.0" +IMMICH_VERSION=v2 # Connection secret for postgres. You should change it to a random password # Please use only the characters `A-Za-z0-9`, without special characters or spaces diff --git a/docker/hwaccel.ml.yml b/docker/hwaccel.ml.yml index 111202d022..c95ac7ee4c 100644 --- a/docker/hwaccel.ml.yml +++ b/docker/hwaccel.ml.yml @@ -4,7 +4,7 @@ # you can inline the config for a backend by copying its contents # into the immich-machine-learning service in the docker-compose.yml file. -# See https://immich.app/docs/features/ml-hardware-acceleration for info on usage. +# See https://docs.immich.app/features/ml-hardware-acceleration for info on usage. services: armnn: diff --git a/docker/hwaccel.transcoding.yml b/docker/hwaccel.transcoding.yml index 60ee7e8fa3..0857faf465 100644 --- a/docker/hwaccel.transcoding.yml +++ b/docker/hwaccel.transcoding.yml @@ -4,7 +4,7 @@ # you can inline the config for a backend by copying its contents # into the immich-microservices service in the docker-compose.yml file. -# See https://immich.app/docs/features/hardware-transcoding for more info on using hardware transcoding. +# See https://docs.immich.app/features/hardware-transcoding for more info on using hardware transcoding. services: cpu: {} diff --git a/docs/.nvmrc b/docs/.nvmrc index e2228113dd..0a492611a0 100644 --- a/docs/.nvmrc +++ b/docs/.nvmrc @@ -1 +1 @@ -22.19.0 +24.11.0 diff --git a/docs/blog/2022/11-10/release-1.36.mdx b/docs/blog/2022/11-10/release-1.36.mdx deleted file mode 100644 index 5f5643196c..0000000000 --- a/docs/blog/2022/11-10/release-1.36.mdx +++ /dev/null @@ -1,110 +0,0 @@ ---- -slug: release-1-36 -title: Release v1.36.0 -authors: [alextran] -tags: [release] -date: 2022-11-10 ---- - -Hello everyone, it is my pleasure to deliver the new release of Immich to you. The team has been working hard to bring you the new features and improvements. This release includes some big features that the community has been asking since the beginning of Immich. We hope you will enjoy it. - -Some notable features are: - -- OAuth integration -- LivePhoto support on iOS -- User config system - - - -## LivePhoto iOS Support 🎉 - -LivePhoto on iOS is now supported in Immich. - -The motion part will now be uploaded and can be played on the mobile app and the web. - -:::caution - -- The server and the app has to be on version **1.36.x** for the application to work correctly. -- Previous uploaded photos will not be updated automatically, you will have to remove and reupload them if you want to keep the LivePhoto functionality. - -::: - - - -## OAuth Integration 🎉 - -I want to borrow this chance to express my gratitude to [@EnricoBilla](https://github.com/EnricoBilla), who has been the trailblazer for this feature since the beginning days of Immich. His PR has sparked ideas, suggestions, and discussion among the team member on how to integrate this feature successfully into the app. Thank you so much for your work and your time. - -OAuth is now integrated into the system. Please follow the guide [here](https://immich.app/docs/usage/oauth) to set up your OAuth integration - -After setting up the correct environment variables in the `.env` file, as shown below - -| Key | Type | Default | Description | -| ------------------- | ------- | -------------------- | ------------------------------------------------------------------------- | -| OAUTH_ENABLED | boolean | false | Enable/disable OAuth2 | -| OAUTH_ISSUER_URL | URL | (required) | Required. Self-discovery URL for client | -| OAUTH_CLIENT_ID | string | (required) | Required. Client ID | -| OAUTH_CLIENT_SECRET | string | (required) | Required. Client Secret | -| OAUTH_SCOPE | string | openid email profile | Full list of scopes to send with the request (space delimited) | -| OAUTH_AUTO_REGISTER | boolean | true | When true, will automatically register a user the first time they sign in | -| OAUTH_BUTTON_TEXT | string | Login with OAuth | Text for the OAuth button on the web | - -```bash title="Authentik Example" -OAUTH_ENABLED=true -OAUTH_ISSUER_URL=http://10.1.15.216:9000/application/o/immich-test/ -OAUTH_CLIENT_ID=30596v8f78a4b6a97d5985c3076b6b4c4d12ddc33 -OAUTH_CLIENT_SECRET=50f1eafdec353b95b1c638db390db4ab67ef035a51212dbec2f56175e2eb272b5d572c099176e6fe116ecf47ffdd544bgdb9e2edc588307ee0339d25eeccd88 -OAUTH_BUTTON_TEXT=Login with Authentik -``` - -The web will have the option to sign in with OAuth. - - - -The mobile app will check if the server has OAuth enabled before displaying the OAuth -sign-in button. - - - -## Support - - - -If you find the project helpful and it helps you in some ways, you can support the project [one time](https://github.com/sponsors/alextran1502?frequency=one-time&sponsor=alextran1502) or [monthly](https://github.com/sponsors/alextran1502) from GitHub Sponsor - -It is a great way to let me know that you want me to continue developing and working on this project for years to come. - -## Details - -For more details, please check out the [release note](https://github.com/immich-app/immich/releases/tag/v1.36.0_55-dev) diff --git a/docs/blog/2023/06-24/update.mdx b/docs/blog/2023/06-24/update.mdx deleted file mode 100644 index 464d3e44d9..0000000000 --- a/docs/blog/2023/06-24/update.mdx +++ /dev/null @@ -1,103 +0,0 @@ ---- -title: Immich Update - June 2023 -authors: [alextran] -tags: [update] ---- - -Hello everybody, Alex here! - -I am back with another update on Immich. It has been only a month since my last update (May 18th, 2023), but it seems forever. I think the rapid releases of Immich and the amount of work make the perspective of time change in Immich’s world. We have some exciting updates that I think you will like. - -Before going into detail, on behalf of the core team, I would like to thank all of you for loving Immich and contributing to the project. Thank you for helping me make Immich an enjoyable alternative solution to Google Photos so that you have complete control of your data and privacy. I know we are still young and have a lot of work to do, but I am confident we will get there with help from the community. I appreciate all of you from the bottom of my heart! - - - -And now, to the exciting part, what is new in Immich’s world? - -- Initial support for existing gallery. -- Memory feature. -- Support XMP sidecar. -- Support more raw formats. -- Justified layout for web timeline and blurred thumbnail hash. -- Mechanism to host machine learning on a completely different machine. - -## Support for existing gallery - -I know this is the most controversial feature when it comes to Immich’s way of ingesting photos and videos. For many users, having to upload photos and videos to Immich is simply not working. We listen, discuss, and digest this feature internally more than you imagine because it is not a simple feature to tackle while keeping the performance and the user experience at the top level, which is Immich’s primary goal. - -Thankfully, we have many great contributors and developers that want to make this come true. So we came up with an initial implementation of this feature in the form of a supporting read-only gallery. - -To be concise, Immich can now read in the gallery files, register the path into the database, and then generate necessary files and put them through Immich’s machine learning pipeline so you can use all the goodness of Immich without the need to upload them. Since this is the initial implementation, some actions/behavior are not yet supported, and we aim to build toward them in future releases, namely: - -- Assets are not automatically synced and must instead be manually synced with the CLI tool. -- Only new files that are added to the gallery will be detected. -- Deleted and moved files will not be detected. - -## Memory feature - -This is considered a fun feature that the team and I wanted to build for so long, but we had to put it off because of the refactoring of the code base. The code base is now in a good enough form to circle back and add more exciting features. - -This memory feature is very much similar to GPhotos' implementation of “x years since…”. We are aiming to add more categories of memories in the future, such as “Spotlight of the day” or “Day of the Week highlights” - - - -This feature is now available on the web and will be ported to the mobile app in the near future. - -## Support XMP Sidecar - -Immich can now import/upload XMP sidecars from the CLI and use the information as the metadata of assets. - -## Support more raw formats. - -With the recent updates on the dependencies of Immich, we are now extending and hardening support for multiple raw formats. So users with DSLR or mirrorless cameras can now upload their original files to Immich and have them displayed in high-quality thumbnails on the web and mobile view. - -## Justified layout for web timeline and blurred thumbnail hash - -This is an aesthetic improvement in user experience when browsing the timeline. Photos and videos are now displayed correctly with perspective orientation, making the browsing experience more pleasurable. - -To further improve the browsing experience, we now added a blur hash to the thumbnail, so the transition is more natural with a dreamy fade in effect, similar to how our brain goes from faded to vivid memory - - - -## Hosting machine learning container on a different machine - -With more capabilities Immich is building toward, machine learning will get more powerful and therefore require more resources to run effectively. However, we understand that users might not have the best server resources where they host the Immich instance. Therefore, we changed how machine learning interacts and receives the photos and videos to run through its inference pipeline. - -The machine learning container is now a headless system that can run on any machine. As long as your Immich instance can communicate with the system running the machine learning container, it can send the files and receive the required information to make Immich powerful in terms of searching and intelligence. This helps you to utilize a more powerful machine in your home/infrastructure to perform the CPU-intensive tasks while letting Immich only handle the I/O operations for a pleasant and smooth experience. - ---- - -So, those are the highlights for the team and the community after a busy month. There are a lot more changes and improvements. I encourage you to read some release notes, starting from version [v1.57.0](https://github.com/immich-app/immich/releases/tag/v1.57.0) to now. - -Thank you, and I am asking for your support for the project. I hope to be a full-time maintainer of Immich one day to dedicate myself to the project as my life works for the community and my family. You can find the support channels below: - -- Monthly donation via [GitHub Sponsors](https://github.com/sponsors/alextran1502) -- One-time donation via [GitHub Sponsors](https://github.com/sponsors/alextran1502?frequency=one-time&sponsor=alextran1502) -- [Liberapay](https://liberapay.com/alex.tran1502/) -- [buymeacoffee](https://www.buymeacoffee.com/altran1502) -- Bitcoin: 3QVAb9dCHutquVejeNXitPqZX26Yg5kxb7 -- Give a project a star - the contributors love gazing at the stars and seeing their creations shining in the sky. - -Join our friendly [Discord](https://discord.immich.app) to talk and discuss Immich, tech, or anything - -Cheer! - -Until next time! - -Alex diff --git a/docs/blog/2023/07-29/images/web-shortcuts-panel.png b/docs/blog/2023/07-29/images/web-shortcuts-panel.png deleted file mode 100644 index 5a16c9f289..0000000000 Binary files a/docs/blog/2023/07-29/images/web-shortcuts-panel.png and /dev/null differ diff --git a/docs/blog/2023/07-29/update.mdx b/docs/blog/2023/07-29/update.mdx deleted file mode 100644 index 6d50ddfdc0..0000000000 --- a/docs/blog/2023/07-29/update.mdx +++ /dev/null @@ -1,151 +0,0 @@ ---- -title: Immich Update - July 2023 -authors: [alextran] -tags: [update, v1.64.0-v1.71.0] ---- - -Hello, Immich fans, another month, another milestone. We hope you are staying cool and safe in this scorching hot summer across the globe. - -Immich recently got some good recognition when getting to the front page of HackerNews, which helped to let more people know about the project's existence. The project will help more and more people find a solution to control the privacy of their most precious moments. And with the gain in popularity and recognition, we have gotten new users and more questions from the community than ever. - -I want to express my gratitude to all the contributors and the community who have been tremendously helpful to new users' questions and provided technical support. - -Below are the highlights of new features we added to the application over the past month, along with countless bug fixes and improvements across the board, from developer experience to resource optimization and UI/UX improvement. I hope you find these topics as exciting as I am. - -## Highlights - -- Memories feature. -- Facial recognition improvements. -- Improvements on multi selection behavior on the web. -- Shortcuts for common actions on the web. -- Support viewer for 360-panorama photos. - - - ---- - -### Memories feature - -We've added the memory feature on the mobile app, so you can reminisce about your past memories. - - - -### Facial recognition improvements - -Over the past few releases, we have added many UI improvements to the facial recognition feature to help you manage the recognized people better. Some of the highlights: - -#### Choose a new feature photo for a person. - - - -#### Hide and show faces. - -You can now select irrelevant faces to hide them. The hidden faces won’t be displayed in search results and the people section in the info panel. - -#### Merge faces. - -This is useful when you have multiple faces of the same person in your photos, and you want to merge them into one. - - - -We also added a nifty mechanism that when naming a face, similar names will prompt you a merge face option for the convenience. - - - -### Improvements on multi selection behavior on the web - -We have added a new multi selection behavior on the web to help you select multiple items easier. You can now select a range of photos and videos by holding the `Shift` key. - - - -### Shortcuts for common actions on the web. - -Some of us only navigate the world and the web with a keyboard (looking at you, Vim and Emacs users). So it would take away the sacred weapon of choice to require many clicks to perform repetitive actions. So we added quick shortcuts for the following action on the web. - -Dot Env Example - -### Support viewer for 360-panorama photos. - -Photos with the EXIF property of `ProjectionType` will now have a special viewer on the web to view all the angles of the panorama. - -The thumbnail of the 360 degrees panoramas will have a special icon on the top right of the thumbnail - -Dot Env Example - -Panorama in the detail view - -Dot Env Example - ---- - -Thank you, and I am asking for your support for the project. I hope to be a full-time maintainer of Immich one day to dedicate myself to the project as my life's work for the community and my family. You can find the support channels below: - -- Monthly donation via [GitHub Sponsors](https://github.com/sponsors/alextran1502) -- One-time donation via [GitHub Sponsors](https://github.com/sponsors/alextran1502?frequency=one-time&sponsor=alextran1502) -- [Liberapay](https://liberapay.com/alex.tran1502/) -- [buymeacoffee](https://www.buymeacoffee.com/altran1502) -- Bitcoin: 3QVAb9dCHutquVejeNXitPqZX26Yg5kxb7 -- Give a project a star - the contributors love gazing at the stars and seeing their creations shining in the sky. - -Join our friendly [Discord](https://discord.immich.app) to talk and discuss Immich, tech, or anything - -Cheer! - -Until next time! - -Alex diff --git a/docs/blog/2023/2023-recap.mdx b/docs/blog/2023/2023-recap.mdx deleted file mode 100644 index e9d93a52be..0000000000 --- a/docs/blog/2023/2023-recap.mdx +++ /dev/null @@ -1,71 +0,0 @@ ---- -title: Immich Recap 2023 -authors: [alextran] -tags: [update, recap-2023] -date: 2023-12-30T00:00 ---- - -Hi everyone, - -Alex from Immich here. - -We are entering the last few weeks of 2023, and it has been quite a year for Immich. The project has grown so much in terms of users, developers, features, maturity, and the community around it. When I started working on Immich, it was simply a challenge for myself and an opportunity to learn new technologies, crafting something fun and useful for my wife during my free time to satisfy my urge to build and create things. I never thought it would become so popular and help so many people. At the end of the day, all we have is memory. I am proud that the team and I have created something to make storing and viewing those precious memories easier without restrictions and without sacrificing our privacy. As the year closes, here’s a recap of everything the project accomplished in 2023. - -# Milestones - -- Public shared links -- Favorites page -- Immich turned 1 -- Material Design 3 on the mobile app -- Auto-link LivePhotos server-side -- iOS background backup -- Explore page -- CLIP search -- Search by metadata -- Responsive web app -- Archive page -- Asset descriptions -- 10,000 stars on GitHub -- Manage auth devices -- Map view -- Facial recognition, clustering, searching, renaming, and person management -- Partner sharing and unifying timeline between partners' users -- Custom storage label -- XMP sidecar reading -- RAW file formats -- Justified layout on the web -- Memories -- Multi-select via SHIFT -- Android Motion Photos -- 360° Photos -- Album description -- Album performance improvements (time buckets) -- Video hardware transcoding -- Slideshow mode on the web -- Configuration file -- External libraries -- Trash page -- Custom theme -- Asset Stacking -- 20,000 stars on GitHub -- Shared album activity and comments -- CLI v2 -- Down to 5 containers (from 8) - -# Fun Statistics - -- We have gone from the release version `1.41.0` to `1.90.0` at the time of writing. On average, we see a release every 7 days. -- According to GitHub's metrics, the `immich-server` container image has been pulled almost _4 million_ times. -- According to mobile app store metrics, we have 22,000 installations on Android and 6700 installation units on iOS (opt-in only). -- Immich is making around $1200/month on average from donations. (Thank you all so much!) -- We were guests on two podcasts: - - [Self-hosted](https://selfhosted.show/110) - - [The Vergecast](https://www.theverge.com/23938533/self-hosting-local-first-software-vergecast) -- There are over 4,500 members on the Discord server. -- We have over 22,000 stars on the main GitHub repository, gaining 15,000 stars since January 2023. - -Diving into the next year, the team will continue to build on the foundation we have laid out over the past year, implementing more advanced features for searching, organizing, and sharing between users. Bugs will continue to be squashed and conquered. “Shit Alex wrote'' code will continue to be replaced by beautiful, clean code from Jason, Zack, Boet, Daniel, Osorin, Mert, Fynn, Marty, Martin, and Jonathan. The team has my eternal gratitude for creating a welcoming environment for new contributors, helping, teaching, and learning from each other. I’ve realized that hardly a day has gone by where the team hasn’t been in communication about Immich related topics over the past year. - -My long-term goal is to help hone Immich into a diamond in the FOSS space, where the UI, UX, development experiences, documentation, and quality are at a high standard while remaining free for everybody to use. - -I hope you enjoy Immich and have a happy and peaceful holiday. diff --git a/docs/blog/2024/immich-core-team-goes-fulltime.mdx b/docs/blog/2024/immich-core-team-goes-fulltime.mdx deleted file mode 100644 index 0cba2b467c..0000000000 --- a/docs/blog/2024/immich-core-team-goes-fulltime.mdx +++ /dev/null @@ -1,75 +0,0 @@ ---- -title: The Immich core team goes full-time -authors: [alextran] -tags: [update, announcement, FUTO] -date: 2024-05-01T00:00 ---- - -**Immich is joining [FUTO](https://futo.org/)!** - -Since the beginning of this adventure, my goal has always been to create a better world for my children. Memories are priceless, and privacy should not be a luxury. However, building quality open source has its challenges. Over the past two years, it has taken significant dedication, time, and effort. - -Recently, a company in Austin, Texas, called FUTO contacted the team. FUTO strives to develop quality and sustainable open software. They build software alternatives that focus on giving control to users. From their mission statement: - -“Computers should belong to you, the people. We develop and fund technology to give them back.” - -FUTO loved Immich and wanted to see if we’d consider working with them to take the project to the next level. In short, FUTO offered to: - -- Pay the core team to work on Immich full-time -- Let us keep full autonomy about the project’s direction and leadership -- Continue to license Immich under AGPL -- Keep Immich’s development direction with no paywalled features -- Keep Immich “built for the people” (no ads, data mining/selling, or alternative motives) -- Provide us with financial, technical, legal, and administrative support - -After careful deliberation, the team decided that FUTO’s vision closely aligns with our own: to build a better future by providing a polished, performant, and privacy-preserving open-source software solution for photo and video management delivered in a sustainable way. - -Immich’s future has never looked brighter, and we look forward to realizing our vision for Immich as part of FUTO. - -If you have more questions, we’ll host a Q&A live stream on May 9th at 3PM UTC (10AM CST). [You can ask questions here](https://www.live-ask.com/event/01HWP2SB99A1K8EXFBDKZ5Z9CF), and the stream will be live [here on our YouTube channel](https://youtube.com/live/cwz2iZwYpgg). - -Cheers, - -The Immich Team - ---- - -## FAQs - -### What is FUTO? - -[https://futo.org/what-is-futo/](https://futo.org/what-is-futo/) - -### Will the license change? - -No. Immich will continue to be licensed under AGPL without a CLA. - -### Will Immich continue to be free? - -Yes. The Immich source code will remain freely available under the AGPL license. - -### Is Immich getting VC funding? - -No. Venture capital implies investment in a business, often with the expectation of a future payout (exit plan). Immich is neither a business that can be acquired nor comes with a money-making exit plan. - -### I am currently supporting Immich through GitHub sponsors. What will happen to my donation? - -Effective immediately, all donations to the Immich organization will be canceled. In the future, we will offer an optional, modest payment option instead. Thank you to everyone who donated to help us get this far! - -### How is funding sustainable? - -Immich and FUTO believe a sustainable future requires a model that does not rely on users-as-a-product. To this end, FUTO advocates that users pay for good, open software. In keeping with this model, we will adopt a purchase price. This means we no longer accept donations, but — _without limiting features for those who do not pay_ — we will soon allow you to purchase Immich through a modest payment. We encourage you to pay for the high-quality software you use to foster a healthy software culture where developers build great applications without hidden motives for their users. - -### When does this change take effect? - -This change takes effect immediately. - -### What will change? - -The following things will change as Immich joins FUTO: - -- The brand, logo, and other Immich trademarks will be transferred to FUTO. -- We will stop all donations to the project. -- The core team can now dedicate our full attention to Immich -- Before the end of the year, we plan to have a roadmap for what it will take to get Immich to a stable release. -- Bugs will be squashed, and features will be delivered faster. diff --git a/docs/blog/2024/immich-licensing.mdx b/docs/blog/2024/immich-licensing.mdx deleted file mode 100644 index 773abcb666..0000000000 --- a/docs/blog/2024/immich-licensing.mdx +++ /dev/null @@ -1,91 +0,0 @@ ---- -title: Licensing announcement - Purchase a license to support Immich -authors: [alextran] -tags: [update, announcement, FUTO] -date: 2024-07-18T00:00 ---- - -Hello everybody, - -Firstly, on behalf of the Immich team, I'd like to thank everybody for your continuous support of Immich since the very first day! Your contributions, encouragement, and community engagement have helped bring Immich to its current state. The team and I are forever grateful for that. - -Since our [last announcement of the core team joining FUTO to work on Immich full-time](https://immich.app/blog/2024/immich-core-team-goes-fulltime), one of the goals of our new position is to foster a healthy relationship between the developers and the users. We believe that this enables us to create great software, establish transparent policies and build trust. - -We want to build a great software application that brings value to you and your loved ones' lives. We are not using you as a product, i.e., selling or tracking your data. We are not putting annoying ads into our software. We respect your privacy. We want to be compensated for the hard work we put in to build Immich for you. - -With those notes, we have enabled a way for you to financially support the continued development of Immich, ensuring the software can move forward and will be maintained, by offering a lifetime license of the software. We think if you like and use software, you should pay for it, but _we're never going to force anyone to pay or try to limit Immich for those who don't._ - -There are two types of license that you can choose to purchase: **Server License** and **Individual License**. - -### Server License - -This is a lifetime license costing **$99.99**. The license is applied to the whole server. You and all users that use your server are licensed. - -### Individual License - -This is a lifetime license costing **$24.99**. The license is applied to a single user, and can be used on any server they choose to connect to. - -license-social-gh - -You can purchase the license on [our page - https://buy.immich.app](https://buy.immich.app). - -Starting with release `v1.109.0` you can purchase and enter your purchased license key directly in the app. - -license-page-gh - -## Thank you - -Thank you again for your support, this will help create a strong foundation and stability for the Immich team to continue developing and maintaining the project that you love to use. - -

- -

- -
-
- -Cheers! 🎉 - -Immich team - -# FAQ - -### 1. Where can I purchase a license? - -There are several places where you can purchase the license from - -- [https://buy.immich.app](https://buy.immich.app) -- [https://pay.futo.org](https://pay.futo.org/) -- or directly from the app. - -### 2. Do I need both _Individual License_ and _Server License_? - -No, - -If you are the admin and the sole user, or your instance has less than a total of 4 users, you can buy the **Individual License** for each user. - -If your instance has more than 4 users, it is more cost-effective to buy the **Server License**, which will license all the users on your instance. - -### 3. What do I do if I don't pay? - -You can continue using Immich without any restriction. - -### 4. Will there be any paywalled features? - -No, there will never be any paywalled features. - -### 5. Where can I get support regarding payment issues? - -You can email us with your `orderId` and your email address `billing@futo.org` or on our Discord server. diff --git a/docs/blog/2024/update-july-2024.mdx b/docs/blog/2024/update-july-2024.mdx deleted file mode 100644 index cbe99177e7..0000000000 --- a/docs/blog/2024/update-july-2024.mdx +++ /dev/null @@ -1,78 +0,0 @@ ---- -title: Immich Update - July 2024 -authors: [alextran] -date: 2024-07-01T00:00 -tags: [update, v1.106.0] ---- - -Hello everybody! Alex from Immich here and I am back with another development progress update for the project. - -Summer has returned once again, and the night sky is filled with stars, thank you for **38_000 shining stars** you have sent to our [GitHub repo](https://github.com/immich-app/immich)! Since the last announcement several core contributors have started full time. Everything is going great with development, PRs get merged with _brrrrrrr_ rate, conversation exchange between team members is on a new high, we met and are working with the great engineers at FUTO. The spirit is high and we have a lot of things brewing that we think you will like. - -Let's go over some of the updates we had since the last post. - -### Container consolidation - -Reduced the number of total containers from 5 to 4 by making the microservices thread get spawned directly in the server container. Woohoo, remember when Immich had 7 containers? - -### Email notifications - -![smtp](https://github.com/immich-app/immich/assets/27055614/949cba85-d3f1-4cd3-b246-a6f5fb5d3ae8) - -We added email notifications to the app with SMTP settings that you can configure for the following events - -- A new account is created for you. -- You are added to a shared album. -- New media is added to an album. - -### Versioned docs - -You can now jump back into the past or take a peek at the unreleased version of the documentation by selecting the version on the website. - -![version-doc](https://github.com/immich-app/immich/assets/27055614/6d22898a-5093-41ad-b416-4573d7ce6e03) - -### Similarity deduplication - -With more machine learning and CLIP magic, we now have similarity deduplication built into the application where it will search for closely similar images and let you decide what to do with them; i.e keep or trash. - -![similarity-deduplication](https://github.com/immich-app/immich/assets/27055614/3cac8478-fbf7-47ea-acb6-0146901dc67e) - -### Permanent URL for asset on the web - -The detail view for an asset now has a permanent URL so you can easily share them with your loved ones. - -### Web app translations - -We now have a public Weblate project which the community can use to translate the webapp to their native languages. We are planning to port the mobile app translation to this platform as well. If you would like to contribute, you can take a look [here](https://hosted.weblate.org/projects/immich/immich/). We're already close to 50% translations -- we really appreciate everyone contributing to that! - -![web-translation](https://github.com/immich-app/immich/assets/27055614/363df2ed-656c-4584-bd82-0708a693c5bc) - -### Read-only/Editor mode on shared album - -As the owner of the album, you can choose if the shared user can edit the album or to only view the content of the album without any modification. - -![read-only-album](https://github.com/immich-app/immich/assets/27055614/c6f66375-b869-495a-9a86-3e87b316d109) - -### Better video thumbnails - -Immich now tries to find a descriptive video thumbnail instead of simply using the first frame. No more black images for thumbnails! - -### Public Roadmap - -We now have a [public roadmap](https://immich.app/roadmap), giving you a high-level overview of things the team is working on. The first goal of this roadmap is to bring Immich to a stable release, which is expected sometime later this year. Some of the highlights include - -- Auto stacking - Auto stacking of burst photos -- Basic editor - Basic photo editing capabilities -- Workflows - Automate tasks with workflows -- Fine grained access controls - Granular access controls for users and api keys -- Better background backups - Rework background backups to be more reliable -- Private/locked photos - Private assets with extra protections - -Beyond the items in the roadmap, we have _many many_ more ideas for Immich. The team and I hope that you are enjoying the application, find it helpful in your life and we have nothing but the intention of building out great software for you all! - -Have an amazing Summer or Winter for those in the southern hemisphere! :D - -Until next time, - -Cheers! -Alex diff --git a/docs/blog/authors.yml b/docs/blog/authors.yml deleted file mode 100644 index f331efa927..0000000000 --- a/docs/blog/authors.yml +++ /dev/null @@ -1,5 +0,0 @@ -alextran: - name: Alex Tran - title: Maintainer of Immich - url: https://github.com/alextran1502 - image_url: https://github.com/alextran1502.png diff --git a/docs/docs/FAQ.mdx b/docs/docs/FAQ.mdx index b2f2e85775..e3df672d35 100644 --- a/docs/docs/FAQ.mdx +++ b/docs/docs/FAQ.mdx @@ -22,7 +22,7 @@ For organizations seeking to resell Immich, we have established the following gu - Do not misrepresent your reseller site or services as being officially affiliated with or endorsed by Immich or our development team. -- For small resellers who wish to contribute financially to Immich's development, we recommend directing your customers to purchase licenses directy from us rather than attempting to broker revenue-sharing arrangements. We ask that you refrain from misrepresenting reseller activities as directly supporting our development work. +- For small resellers who wish to contribute financially to Immich's development, we recommend directing your customers to purchase licenses directly from us rather than attempting to broker revenue-sharing arrangements. We ask that you refrain from misrepresenting reseller activities as directly supporting our development work. When in doubt or if you have an edge case scenario, we encourage you to contact us directly via email to discuss the use of our trademark. We can provide clear guidance on what is acceptable and what is not. You can reach out at: questions@immich.app @@ -30,11 +30,11 @@ When in doubt or if you have an edge case scenario, we encourage you to contact ### How can I reset the admin password? -The admin password can be reset by running the [reset-admin-password](/docs/administration/server-commands.md) command on the immich-server. +The admin password can be reset by running the [reset-admin-password](/administration/server-commands.md) command on the immich-server. ### How can I see a list of all users in Immich? -You can see the list of all users by running [list-users](/docs/administration/server-commands.md) Command on the Immich-server. +You can see the list of all users by running [list-users](/administration/server-commands.md) Command on the Immich-server. --- @@ -106,20 +106,20 @@ However, Immich will delete original files that have been trashed when the trash When Storage Template is off (default) Immich saves the file names in a random string (also known as random UUIDs) to prevent duplicate file names. To retrieve the original file names, you must enable the Storage Template and then run the STORAGE TEMPLATE MIGRATION job. -It is recommended to read about [Storage Template](https://immich.app/docs/administration/storage-template) before activation. +It is recommended to read about [Storage Template](/administration/storage-template) before activation. ### Can I add my existing photo library? -Yes, with an [External Library](/docs/features/libraries.md). +Yes, with an [External Library](/features/libraries.md). -### What happens to existing files after I choose a new [Storage Template](/docs/administration/storage-template.mdx)? +### What happens to existing files after I choose a new [Storage Template](/administration/storage-template.mdx)? -Template changes will only apply to _new_ assets. To retroactively apply the template to previously uploaded assets, run the Storage Migration Job, available on the [Jobs](/docs/administration/jobs-workers/#jobs) page. +Template changes will only apply to _new_ assets. To retroactively apply the template to previously uploaded assets, run the Storage Migration Job, available on the [Jobs](/administration/jobs-workers/#jobs) page. ### Why are only photos and not videos being uploaded to Immich? This often happens when using a reverse proxy in front of Immich. -Make sure to [set your reverse proxy](/docs/administration/reverse-proxy/) to allow large requests. +Make sure to [set your reverse proxy](/administration/reverse-proxy/) to allow large requests. Also, check the disk space of your reverse proxy. In some cases, proxies cache requests to disk before passing them on, and if disk space runs out, the request fails. @@ -139,7 +139,7 @@ You can _archive_ them. ### How can I backup data from Immich? -See [Backup and Restore](/docs/administration/backup-and-restore.md). +See [Backup and Restore](/administration/backup-and-restore.md). ### Does Immich support reading existing face tag metadata? @@ -225,7 +225,7 @@ volumes: ### Can I keep my existing album structure while importing assets into Immich? -Yes, by using the [Immich CLI](/docs/features/command-line-interface) along with the `--album` flag. +Yes, by using the [Immich CLI](/features/command-line-interface) along with the `--album` flag. ### Is there a way to reorder photos within an album? @@ -266,7 +266,7 @@ Immich uses CLIP models. An ML model converts each image to an "embedding", whic ### How does facial recognition work? -See [How Facial Recognition Works](/docs/features/facial-recognition#how-facial-recognition-works) for details. +See [How Facial Recognition Works](/features/facial-recognition#how-facial-recognition-works) for details. ### How can I disable machine learning? @@ -288,7 +288,7 @@ No, this is not supported. Only models listed in the [Hugging Face][huggingface] ### I want to be able to search in other languages besides English. How can I do that? -You can change to a multilingual CLIP model. See [here](/docs/features/searching#clip-models) for instructions. +You can change to a multilingual CLIP model. See [here](/features/searching#clip-models) for instructions. ### Does Immich support Facial Recognition for videos? @@ -299,7 +299,7 @@ Scanning the entire video for faces may be implemented in the future. No. :::tip -You can use [Smart Search](/docs/features/searching.md) for this to some extent. For example, if you have a Golden Retriever and a Chihuahua, type these words in the smart search and watch the results. +You can use [Smart Search](/features/searching.md) for this to some extent. For example, if you have a Golden Retriever and a Chihuahua, type these words in the smart search and watch the results. ::: ### I'm getting a lot of "faces" that aren't faces, what can I do? @@ -329,7 +329,7 @@ ls clip/ facial-recognition/ ### Why is Immich slow on low-memory systems like the Raspberry Pi? -Immich optionally uses transcoding and machine learning for several features. However, it can be too heavy to run on a Raspberry Pi. You can [mitigate](/docs/FAQ#can-i-lower-cpu-and-ram-usage) this or host Immich's machine-learning container on a [more powerful system](/docs/guides/remote-machine-learning), or [disable](/docs/FAQ#how-can-i-disable-machine-learning) machine learning entirely. +Immich optionally uses transcoding and machine learning for several features. However, it can be too heavy to run on a Raspberry Pi. You can [mitigate](/FAQ#can-i-lower-cpu-and-ram-usage) this or host Immich's machine-learning container on a [more powerful system](/guides/remote-machine-learning), or [disable](/FAQ#how-can-i-disable-machine-learning) machine learning entirely. ### Can I lower CPU and RAM usage? @@ -339,9 +339,9 @@ The initial backup is the most intensive due to the number of jobs running. The - Under Settings > Transcoding Settings > Threads, set the number of threads to a low number like 1 or 2. - Under Settings > Machine Learning Settings > Facial Recognition > Model Name, you can change the facial recognition model to `buffalo_s` instead of `buffalo_l`. The former is a smaller and faster model, albeit not as good. - For facial recognition on new images to work properly, You must re-run the Face Detection job for all images after this. -- At the container level, you can [set resource constraints](/docs/FAQ#can-i-limit-cpu-and-ram-usage) to lower usage further. +- At the container level, you can [set resource constraints](/FAQ#can-i-limit-cpu-and-ram-usage) to lower usage further. - It's recommended to only apply these constraints _after_ taking some of the measures here for best performance. -- If these changes are not enough, see [above](/docs/FAQ#how-can-i-disable-machine-learning) for instructions on how to disable machine learning. +- If these changes are not enough, see [above](/FAQ#how-can-i-disable-machine-learning) for instructions on how to disable machine learning. ### Can I limit CPU and RAM usage? @@ -383,7 +383,7 @@ Do not exaggerate with the job concurrency because you're probably thoroughly ov ### My server shows Server Status Offline | Version Unknown. What can I do? -You need to [enable WebSockets](/docs/administration/reverse-proxy/) on your reverse proxy. +You need to [enable WebSockets](/administration/reverse-proxy/) on your reverse proxy. --- @@ -391,7 +391,7 @@ You need to [enable WebSockets](/docs/administration/reverse-proxy/) on your rev ### How can I see Immich logs? -Immich components are typically deployed using docker. To see logs for deployed docker containers, you can use the [Docker CLI](https://docs.docker.com/engine/reference/commandline/cli/), specifically the `docker logs` command. For examples, see [Docker Help](/docs/guides/docker-help.md). +Immich components are typically deployed using docker. To see logs for deployed docker containers, you can use the [Docker CLI](https://docs.docker.com/engine/reference/commandline/cli/), specifically the `docker logs` command. For examples, see [Docker Help](/guides/docker-help.md). ### How can I reduce the log verbosity of Redis? @@ -435,7 +435,7 @@ cap_drop: Data for Immich comes in two forms: 1. **Metadata** stored in a Postgres database, stored in the `DB_DATA_LOCATION` folder (previously `pg_data` Docker volume). -2. **Files** (originals, thumbs, profile, etc.), stored in the `UPLOAD_LOCATION` folder, more [info](/docs/administration/backup-and-restore#asset-types-and-storage-locations). +2. **Files** (originals, thumbs, profile, etc.), stored in the `UPLOAD_LOCATION` folder, more [info](/administration/backup-and-restore#asset-types-and-storage-locations). :::warning This will destroy your database and reset your instance, meaning that you start from scratch. @@ -473,7 +473,7 @@ If it mentions SIGILL (note the lack of a K) or error code 132, it most likely m ### Why am I getting database ownership errors? If you get database errors such as `FATAL: data directory "/var/lib/postgresql/data" has wrong ownership` upon database startup, this is likely due to an issue with your filesystem. -NTFS and ex/FAT/32 filesystems are not supported. See [here](/docs/install/requirements#special-requirements-for-windows-users) for more details. +NTFS and ex/FAT/32 filesystems are not supported. See [here](/install/requirements#special-requirements-for-windows-users) for more details. ### How can I verify the integrity of my database? diff --git a/docs/docs/administration/backup-and-restore.md b/docs/docs/administration/backup-and-restore.md index deeefa5635..fbb4cd8c23 100644 --- a/docs/docs/administration/backup-and-restore.md +++ b/docs/docs/administration/backup-and-restore.md @@ -3,7 +3,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -A [3-2-1 backup strategy](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) is recommended to protect your data. You should keep copies of your uploaded photos/videos as well as the Immich database for a comprehensive backup solution. This page provides an overview on how to backup the database and the location of user-uploaded pictures and videos. A template bash script that can be run as a cron job is provided [here](/docs/guides/template-backup-script.md) +A [3-2-1 backup strategy](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) is recommended to protect your data. You should keep copies of your uploaded photos/videos as well as the Immich database for a comprehensive backup solution. This page provides an overview on how to backup the database and the location of user-uploaded pictures and videos. A template bash script that can be run as a cron job is provided [here](/guides/template-backup-script.md) :::danger The instructions on this page show you how to prepare your Immich instance to be backed up, and which files to take a backup of. You still need to take care of using an actual backup tool to make a backup yourself. @@ -57,6 +57,7 @@ Then please follow the steps in the following section for restoring the database ```bash title='Backup' +# Replace with the database username - usually postgres unless you have changed it. docker exec -t immich_postgres pg_dumpall --clean --if-exists --username= | gzip > "/path/to/backup/dump.sql.gz" ``` @@ -69,16 +70,18 @@ docker compose create # Create Docker containers for Immich apps witho docker start immich_postgres # Start Postgres server sleep 10 # Wait for Postgres server to start up # Check the database user if you deviated from the default +# Replace with the database username - usually postgres unless you have changed it. gunzip --stdout "/path/to/backup/dump.sql.gz" \ | sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" \ | docker exec -i immich_postgres psql --dbname=postgres --username= # Restore Backup docker compose up -d # Start remainder of Immich apps ``` - + ```powershell title='Backup' +# Replace with the database username - usually postgres unless you have changed it. [System.IO.File]::WriteAllLines("C:\absolute\path\to\backup\dump.sql", (docker exec -t immich_postgres pg_dumpall --clean --if-exists --username=)) ``` @@ -92,13 +95,15 @@ docker compose create # Create Docker containers for docker start immich_postgres # Start Postgres server sleep 10 # Wait for Postgres server to start up docker exec -it immich_postgres bash # Enter the Docker shell and run the following command -# Check the database user if you deviated from the default. If your backup ends in `.gz`, replace `cat` with `gunzip --stdout` +# If your backup ends in `.gz`, replace `cat` with `gunzip --stdout` +# Replace with the database username - usually postgres unless you have changed it. + cat "/dump.sql" | sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" | psql --dbname=postgres --username= exit # Exit the Docker shell docker compose up -d # Start remainder of Immich apps ``` - + Note that for the database restore to proceed properly, it requires a completely fresh install (i.e. the Immich server has never run since creating the Docker containers). If the Immich app has run, Postgres conflicts may be encountered upon database restoration (relation already exists, violated foreign key constraints, multiple primary keys, etc.), in which case you need to delete the `DB_DATA_LOCATION` folder to reset the database. @@ -160,7 +165,7 @@ for more info read the [release notes](https://github.com/immich-app/immich/rele :::danger A backup of this folder does not constitute a backup of your database! - Follow the instructions listed [here](/docs/administration/backup-and-restore#database) to learn how to perform a proper backup. + Follow the instructions listed [here](/administration/backup-and-restore#database) to learn how to perform a proper backup. ::: @@ -205,7 +210,7 @@ When you turn off the storage template engine, it will leave the assets in `UPLO :::danger A backup of this folder does not constitute a backup of your database! - Follow the instructions listed [here](/docs/administration/backup-and-restore#database) to learn how to perform a proper backup. + Follow the instructions listed [here](/administration/backup-and-restore#database) to learn how to perform a proper backup. ::: diff --git a/docs/docs/administration/email-notification.mdx b/docs/docs/administration/email-notification.mdx index 2ad4fba2be..0da132161f 100644 --- a/docs/docs/administration/email-notification.mdx +++ b/docs/docs/administration/email-notification.mdx @@ -12,7 +12,7 @@ You can access the settings panel from the web at `Administration -> Settings -> Under Email, enter the required details to connect with an SMTP server. -You can use [this guide](/docs/guides/smtp-gmail) to use Gmail's SMTP server. +You can use [this guide](/guides/smtp-gmail) to use Gmail's SMTP server. ## User's notifications settings diff --git a/docs/docs/administration/jobs-workers.md b/docs/docs/administration/jobs-workers.md index 4634151b9a..8ed3ba2694 100644 --- a/docs/docs/administration/jobs-workers.md +++ b/docs/docs/administration/jobs-workers.md @@ -11,7 +11,7 @@ The `immich-server` container contains multiple workers: ## Split workers -If you prefer to throttle or distribute the workers, you can do this using the [environment variables](/docs/install/environment-variables) to specify which container should pick up which tasks. +If you prefer to throttle or distribute the workers, you can do this using the [environment variables](/install/environment-variables) to specify which container should pick up which tasks. For example, for a simple setup with one container for the Web/API and one for all other microservices, you can do the following: @@ -53,5 +53,21 @@ Additionally, some jobs (such as memories generation) run on a schedule, which i :::note -Some jobs ([External Libraries](/docs/features/libraries) scanning, Database Dump) are configured in their own sections in System Settings. +Some jobs ([External Libraries](/features/libraries) scanning, Database Dump) are configured in their own sections in System Settings. ::: + +## Job processing order + +The below diagram shows the job run order for newly uploaded files + +```mermaid +graph TD + A[Asset Upload] --> B[Metadata Extraction] + B --> C[Storage Template Migration] + C --> D["Thumbnail Generation (Large, small, blurred and person)"] + D --> E[Smart Search] + D --> F[Face Detection] + D --> G[Video Transcoding] + E --> H[Duplicate Detection] + F --> I[Facial Recognition] +``` diff --git a/docs/docs/administration/oauth.md b/docs/docs/administration/oauth.md index 55a0ce9469..47f4a96c6a 100644 --- a/docs/docs/administration/oauth.md +++ b/docs/docs/administration/oauth.md @@ -28,7 +28,7 @@ Before enabling OAuth in Immich, a new client application needs to be configured 2. Configure Redirect URIs/Origins The **Sign-in redirect URIs** should include: - - `app.immich:///oauth-callback` - for logging in with OAuth from the [Mobile App](/docs/features/mobile-app.mdx) + - `app.immich:///oauth-callback` - for logging in with OAuth from the [Mobile App](/features/mobile-app.mdx) - `http://DOMAIN:PORT/auth/login` - for logging in with OAuth from the Web Client - `http://DOMAIN:PORT/user-settings` - for manually linking OAuth in the Web Client @@ -98,7 +98,7 @@ The redirect URI for the mobile app is `app.immich:///oauth-callback`, which is 2. Whitelist the new endpoint as a valid redirect URI with your provider. 3. Specify the new endpoint as the `Mobile Redirect URI Override`, in the OAuth settings. -With these steps in place, you should be able to use OAuth from the [Mobile App](/docs/features/mobile-app.mdx) without a custom scheme redirect URI. +With these steps in place, you should be able to use OAuth from the [Mobile App](/features/mobile-app.mdx) without a custom scheme redirect URI. :::info Immich has a route (`/api/oauth/mobile-redirect`) that is already configured to forward requests to `app.immich:///oauth-callback`, and can be used for step 1. diff --git a/docs/docs/administration/postgres-standalone.md b/docs/docs/administration/postgres-standalone.md index fd9b8a5e4d..2b7527623f 100644 --- a/docs/docs/administration/postgres-standalone.md +++ b/docs/docs/administration/postgres-standalone.md @@ -10,16 +10,19 @@ Running with a pre-existing Postgres server can unlock powerful administrative f ## Prerequisites -You must install `pgvector` (`>= 0.7.0, < 1.0.0`), as it is a prerequisite for `vchord`. +You must install pgvector as it is a prerequisite for VectorChord. The easiest way to do this on Debian/Ubuntu is by adding the [PostgreSQL Apt repository][pg-apt] and then running `apt install postgresql-NN-pgvector`, where `NN` is your Postgres version (e.g., `16`). You must install VectorChord into your instance of Postgres using their [instructions][vchord-install]. After installation, add `shared_preload_libraries = 'vchord.so'` to your `postgresql.conf`. If you already have some `shared_preload_libraries` set, you can separate each extension with a comma. For example, `shared_preload_libraries = 'pg_stat_statements, vchord.so'`. -:::note -Immich is known to work with Postgres versions `>= 14, < 18`. +:::note Supported versions +Immich is known to work with Postgres versions `>= 14, < 19`. -Make sure the installed version of VectorChord is compatible with your version of Immich. The current accepted range for VectorChord is `>= 0.3.0, < 0.5.0`. +VectorChord is known to work with pgvector versions `>= 0.7, < 0.9`. + +The Immich server will check the VectorChord version on startup to ensure compatibility, and refuse to start if a compatible version is not found. +The current accepted range for VectorChord is `>= 0.3, < 0.6`. ::: ## Specifying the connection URL diff --git a/docs/docs/administration/reverse-proxy.md b/docs/docs/administration/reverse-proxy.md index f13814d91f..5c9e47b010 100644 --- a/docs/docs/administration/reverse-proxy.md +++ b/docs/docs/administration/reverse-proxy.md @@ -6,6 +6,10 @@ Users can deploy a custom reverse proxy that forwards requests to Immich. This w Immich does not support being served on a sub-path such as `location /immich {`. It has to be served on the root path of a (sub)domain. ::: +:::info +If your reverse proxy uses the [Let's Encrypt](https://letsencrypt.org/) [http-01 challenge](https://letsencrypt.org/docs/challenge-types/#http-01-challenge), you may want to verify that the Immich well-known endpoint (`/.well-known/immich`) gets correctly routed to Immich, otherwise it will likely be routed elsewhere and the mobile app may run into connection issues. +::: + ### Nginx example config Below is an example config for nginx. Make sure to set `public_url` to the front-facing URL of your instance, and `backend_url` to the path of the Immich server. @@ -37,29 +41,14 @@ server { location / { proxy_pass http://:2283; } + + # useful when using Let's Encrypt http-01 challenge + # location = /.well-known/immich { + # proxy_pass http://:2283; + # } } ``` -#### Compatibility with Let's Encrypt - -In the event that your nginx configuration includes a section for Let's Encrypt, it's likely that you have a segment similar to the following: - -```nginx -location ~ /.well-known { - ... -} -``` - -This particular `location` directive can inadvertently prevent mobile clients from reaching the `/.well-known/immich` path, which is crucial for discovery. Usual error message for this case is: "Your app major version is not compatible with the server". To remedy this, you should introduce an additional location block specifically for this path, ensuring that requests are correctly proxied to the Immich server: - -```nginx -location = /.well-known/immich { - proxy_pass http://:2283; -} -``` - -By doing so, you'll maintain the functionality of Let's Encrypt while allowing mobile clients to access the necessary Immich path without obstruction. - ### Caddy example config As an alternative to nginx, you can also use [Caddy](https://caddyserver.com/) as a reverse proxy (with automatic HTTPS configuration). Below is an example config. diff --git a/docs/docs/administration/server-commands.md b/docs/docs/administration/server-commands.md index a25673abf2..3838635c24 100644 --- a/docs/docs/administration/server-commands.md +++ b/docs/docs/administration/server-commands.md @@ -16,7 +16,7 @@ The `immich-server` docker image comes preinstalled with an administrative CLI ( ## How to run a command -To run a command, [connect](/docs/guides/docker-help.md#attach-to-a-container) to the `immich_server` container and then execute the command via `immich-admin `. +To run a command, [connect](/guides/docker-help.md#attach-to-a-container) to the `immich_server` container and then execute the command via `immich-admin `. ## Examples diff --git a/docs/docs/administration/system-settings.md b/docs/docs/administration/system-settings.md index f241050136..fdfdad29ea 100644 --- a/docs/docs/administration/system-settings.md +++ b/docs/docs/administration/system-settings.md @@ -12,14 +12,14 @@ Manage password, OAuth, and other authentication settings ### OAuth Authentication -Immich supports OAuth Authentication. Read more about this feature and its configuration [here](/docs/administration/oauth). +Immich supports OAuth Authentication. Read more about this feature and its configuration [here](/administration/oauth). ### Password Authentication -The administrator can choose to disable login with username and password for the entire instance. This means that **no one**, including the system administrator, will be able to log using this method. If [OAuth Authentication](/docs/administration/oauth) is also disabled, no users will be able to login using **any** method. Changing this setting does not affect existing sessions, just new login attempts. +The administrator can choose to disable login with username and password for the entire instance. This means that **no one**, including the system administrator, will be able to log using this method. If [OAuth Authentication](/administration/oauth) is also disabled, no users will be able to login using **any** method. Changing this setting does not affect existing sessions, just new login attempts. :::tip -You can always use the [Server CLI](/docs/administration/server-commands) to re-enable password login. +You can always use the [Server CLI](/administration/server-commands) to re-enable password login. ::: ## Image Settings (thumbnails and previews) @@ -108,7 +108,7 @@ If more than one URL is provided, each server will be attempted one-at-a-time un ### Smart Search -The [smart search](/docs/features/searching) settings allow you to change the [CLIP model](https://openai.com/research/clip). Larger models will typically provide [more accurate search results](https://github.com/immich-app/immich/discussions/11862) but consume more processing power and RAM. When [changing the CLIP model](/docs/FAQ#can-i-use-a-custom-clip-model) it is mandatory to re-run the Smart Search job on all images to fully apply the change. +The [smart search](/features/searching) settings allow you to change the [CLIP model](https://openai.com/research/clip). Larger models will typically provide [more accurate search results](https://github.com/immich-app/immich/discussions/11862) but consume more processing power and RAM. When [changing the CLIP model](/FAQ#can-i-use-a-custom-clip-model) it is mandatory to re-run the Smart Search job on all images to fully apply the change. :::info Internet connection Changing models requires a connection to the Internet to download the model. @@ -132,7 +132,7 @@ Editable settings: - **Max Recognition Distance** - **Min Recognized Faces** -You can learn more about these options on the [Facial Recognition page](/docs/features/facial-recognition#how-face-detection-works) +You can learn more about these options on the [Facial Recognition page](/features/facial-recognition#how-face-detection-works) :::info When changing the values in Min Detection Score, Max Recognition Distance, and Min Recognized Faces. @@ -154,15 +154,15 @@ The map can be adjusted via [OpenMapTiles](https://openmaptiles.org/styles/) for ### Reverse Geocoding Settings -Immich supports [Reverse Geocoding](/docs/features/reverse-geocoding) using data from the [GeoNames](https://www.geonames.org/) geographical database. +Immich supports [Reverse Geocoding](/features/reverse-geocoding) using data from the [GeoNames](https://www.geonames.org/) geographical database. ## Notification Settings -SMTP server setup, for user creation notifications, new albums, etc. More information can be found [here](/docs/administration/email-notification) +SMTP server setup, for user creation notifications, new albums, etc. More information can be found [here](/administration/email-notification) ## Notification Templates -Override the default notifications text with notification templates. More information can be found [here](/docs/administration/email-notification) +Override the default notifications text with notification templates. More information can be found [here](/administration/email-notification) ## Server Settings @@ -176,7 +176,7 @@ The administrator can set a custom message on the login screen (the message will ## Storage Template -Immich supports a custom [Storage Template](/docs/administration/storage-template). Learn more about this feature and its configuration [here](/docs/administration/storage-template). +Immich supports a custom [Storage Template](/administration/storage-template). Learn more about this feature and its configuration [here](/administration/storage-template). ## Theme Settings diff --git a/docs/docs/community-guides.mdx b/docs/docs/community-guides.mdx deleted file mode 100644 index 505ec93e77..0000000000 --- a/docs/docs/community-guides.mdx +++ /dev/null @@ -1,12 +0,0 @@ -# Community Guides - -This page lists community guides that are written around Immich, but not officially supported by the development team. - -:::warning -This list comes with no guarantees about security, performance, reliability, or accuracy. Use at your own risk. -::: - -import CommunityGuides from '../src/components/community-guides.tsx'; -import React from 'react'; - - diff --git a/docs/docs/community-projects.mdx b/docs/docs/community-projects.mdx deleted file mode 100644 index eb41090cd6..0000000000 --- a/docs/docs/community-projects.mdx +++ /dev/null @@ -1,12 +0,0 @@ -# Community Projects - -This page lists community projects that are built around Immich, but not officially supported by the development team. - -:::warning -This list comes with no guarantees about security, performance, reliability, or accuracy. Use at your own risk. -::: - -import CommunityProjects from '../src/components/community-projects.tsx'; -import React from 'react'; - - diff --git a/docs/docs/developer/architecture.mdx b/docs/docs/developer/architecture.mdx index a8d38ba5c1..42d9c1b974 100644 --- a/docs/docs/developer/architecture.mdx +++ b/docs/docs/developer/architecture.mdx @@ -44,7 +44,7 @@ The web app is a [TypeScript](https://www.typescriptlang.org/) project that uses ### CLI -The Immich CLI is an [npm](https://www.npmjs.com/) package that lets users control their Immich instance from the command line. It uses the API to perform various tasks, especially uploading assets. See the [CLI documentation](/docs/features/command-line-interface.md) for more information. +The Immich CLI is an [npm](https://www.npmjs.com/) package that lets users control their Immich instance from the command line. It uses the API to perform various tasks, especially uploading assets. See the [CLI documentation](/features/command-line-interface.md) for more information. ## Server @@ -83,11 +83,11 @@ Immich uses a [worker](https://github.com/immich-app/immich/blob/main/server/src - Smart Search - Facial Recognition - Storage Template Migration -- Sidecar (see [XMP Sidecars](/docs/features/xmp-sidecars.md)) +- Sidecar (see [XMP Sidecars](/features/xmp-sidecars.md)) - Background jobs (file deletion, user deletion) :::info -This list closely matches what is available on the [Administration > Jobs](/docs/administration/jobs-workers/#jobs) page, which provides some remote queue management capabilities. +This list closely matches what is available on the [Administration > Jobs](/administration/jobs-workers/#jobs) page, which provides some remote queue management capabilities. ::: ### Machine Learning diff --git a/docs/docs/developer/database-migrations.md b/docs/docs/developer/database-migrations.md index f032048b7a..a73e7e747c 100644 --- a/docs/docs/developer/database-migrations.md +++ b/docs/docs/developer/database-migrations.md @@ -12,3 +12,13 @@ pnpm run migrations:generate 3. Move the migration file to folder `./server/src/schema/migrations` in your code editor. The server will automatically detect `*.ts` file changes and restart. Part of the server start-up process includes running any new migrations, so it will be applied immediately. + +## Reverting a Migration + +If you need to undo the most recently applied migration—for example, when developing or testing on schema changes—run: + +```bash +pnpm run migrations:revert +``` + +This command rolls back the latest migration and brings the database schema back to its previous state. diff --git a/docs/docs/developer/devcontainers.md b/docs/docs/developer/devcontainers.md index c7c48acf2b..0a1946e6c1 100644 --- a/docs/docs/developer/devcontainers.md +++ b/docs/docs/developer/devcontainers.md @@ -431,7 +431,7 @@ While the Dev Container focuses on server and web development, you can connect m - Server URL: `http://YOUR_IP:2283/api` - Ensure firewall allows port 2283 -3. **For full mobile development**, see the [mobile development guide](/docs/developer/setup) which covers: +3. **For full mobile development**, see the [mobile development guide](/developer/setup) which covers: - Flutter setup - Running on simulators/devices - Mobile-specific debugging @@ -474,7 +474,7 @@ Recommended minimums: ## Next Steps -- Read the [architecture overview](/docs/developer/architecture) -- Learn about [database migrations](/docs/developer/database-migrations) -- Explore [API documentation](/docs/api) +- Read the [architecture overview](/developer/architecture) +- Learn about [database migrations](/developer/database-migrations) +- Explore [API documentation](https://api.immich.app/) - Join `#immich` on [Discord](https://discord.immich.app) diff --git a/docs/docs/developer/open-api.md b/docs/docs/developer/open-api.md index 2c29c7365b..f627b2c459 100644 --- a/docs/docs/developer/open-api.md +++ b/docs/docs/developer/open-api.md @@ -1,6 +1,6 @@ # OpenAPI -Immich uses the [OpenAPI](https://swagger.io/specification/) standard to generate API documentation. To view the published docs see [here](/docs/api). +Immich uses the [OpenAPI](https://swagger.io/specification/) standard to generate API documentation. To view the published docs see [here](https://api.immich.app/). ## Generator diff --git a/docs/docs/developer/pr-checklist.md b/docs/docs/developer/pr-checklist.md index ea44367742..f855e854c4 100644 --- a/docs/docs/developer/pr-checklist.md +++ b/docs/docs/developer/pr-checklist.md @@ -53,8 +53,8 @@ You can use `dart fix --apply` and `dcm fix lib` to potentially correct some iss ## OpenAPI -The OpenAPI client libraries need to be regenerated whenever there are changes to the `immich-openapi-specs.json` file. Note that you should not modify this file directly as it is auto-generated. See [OpenAPI](/docs/developer/open-api.md) for more details. +The OpenAPI client libraries need to be regenerated whenever there are changes to the `immich-openapi-specs.json` file. Note that you should not modify this file directly as it is auto-generated. See [OpenAPI](/developer/open-api.md) for more details. ## Database Migrations -A database migration needs to be generated whenever there are changes to `server/src/infra/src/entities`. See [Database Migration](/docs/developer/database-migrations.md) for more details. +A database migration needs to be generated whenever there are changes to `server/src/infra/src/entities`. See [Database Migration](/developer/database-migrations.md) for more details. diff --git a/docs/docs/developer/setup.md b/docs/docs/developer/setup.md index bf1003b781..f262743dcd 100644 --- a/docs/docs/developer/setup.md +++ b/docs/docs/developer/setup.md @@ -5,7 +5,7 @@ sidebar_position: 2 # Setup :::note -If there's a feature you're planning to work on, just give us a heads up in [Discord](https://discord.com/channels/979116623879368755/1071165397228855327) so we can: +If there's a feature you're planning to work on, just give us a heads up in [#contributing](https://discord.com/channels/979116623879368755/1071165397228855327) on [our Discord](https://discord.immich.app) so we can: 1. Let you know if it's something we would accept into Immich 2. Provide any guidance on how something like that would ideally be implemented diff --git a/docs/docs/features/automatic-backup.md b/docs/docs/features/automatic-backup.md index 8fcbedaa6e..30d132cef8 100644 --- a/docs/docs/features/automatic-backup.md +++ b/docs/docs/features/automatic-backup.md @@ -16,7 +16,7 @@ If foreground backup is enabled: whenever the app is opened or resumed, it will ## Background backup -This feature is intended for everyday use. For initial bulk uploading, please use the foreground upload feature. For more information on why background upload is not working as expected, please refer to the [FAQ](/docs/FAQ#why-does-foreground-backup-stop-when-i-navigate-away-from-the-app-shouldnt-it-transfer-the-job-to-background-backup). +This feature is intended for everyday use. For initial bulk uploading, please use the foreground upload feature. For more information on why background upload is not working as expected, please refer to the [FAQ](/FAQ#why-does-foreground-backup-stop-when-i-navigate-away-from-the-app-shouldnt-it-transfer-the-job-to-background-backup). If background backup is enabled. The app will periodically check if there are any new photos or videos in the selected album(s) to be uploaded to the server. If there are, it will upload them to the cloud in the background. diff --git a/docs/docs/features/casting.md b/docs/docs/features/casting.md index bca85cb28c..2a6785dc6c 100644 --- a/docs/docs/features/casting.md +++ b/docs/docs/features/casting.md @@ -4,7 +4,7 @@ Immich supports the Google's Cast protocol so that photos and videos can be cast ## Enable Google Cast Support -Google Cast support is disabled by default. The web UI uses Google-provided scripts and must retreive them from Google servers when the page loads. This is a privacy concern for some and is thus opt-in. +Google Cast support is disabled by default. The web UI uses Google-provided scripts and must retrieve them from Google servers when the page loads. This is a privacy concern for some and is thus opt-in. You can enable Google Cast support through `Account Settings > Features > Cast > Google Cast` diff --git a/docs/docs/features/command-line-interface.md b/docs/docs/features/command-line-interface.md index 436b499e50..9a00cb50e1 100644 --- a/docs/docs/features/command-line-interface.md +++ b/docs/docs/features/command-line-interface.md @@ -103,6 +103,7 @@ Options: -c, --concurrency Number of assets to upload at the same time (default: 4, env: IMMICH_UPLOAD_CONCURRENCY) -j, --json-output Output detailed information in json format (default: false, env: IMMICH_JSON_OUTPUT) --delete Delete local assets after upload (env: IMMICH_DELETE_ASSETS) + --delete-duplicates Delete local assets that are duplicates (already exist on server) (env: IMMICH_DELETE_DUPLICATES) --no-progress Hide progress bars (env: IMMICH_PROGRESS_BAR) --watch Watch for changes and upload automatically (default: false, env: IMMICH_WATCH_CHANGES) --help display help for command @@ -182,7 +183,7 @@ For example to get a list of files that would be uploaded for further processing: ```bash -immich upload --dry-run . | tail -n +4 | jq .newFiles[] +immich upload --dry-run . | tail -n +6 | jq .newFiles[] ``` ### Obtain the API Key diff --git a/docs/docs/features/facial-recognition.md b/docs/docs/features/facial-recognition.md index f0dec55484..85712ef5f6 100644 --- a/docs/docs/features/facial-recognition.md +++ b/docs/docs/features/facial-recognition.md @@ -70,7 +70,7 @@ Navigating to Administration > Settings > Machine Learning Settings > Facial Rec :::tip It's better to only tweak the parameters here than to set them to something very different unless you're ready to test a variety of options. If you do need to set a parameter to a strict setting, relaxing other settings can be a good option to compensate, and vice versa. -You can learn how the tune the result in this [Guide](/docs/guides/better-facial-clusters) +You can learn how the tune the result in this [Guide](/guides/better-facial-clusters) ::: ### Facial recognition model diff --git a/docs/docs/features/libraries.md b/docs/docs/features/libraries.md index e68bcdc272..9f1cef0bc4 100644 --- a/docs/docs/features/libraries.md +++ b/docs/docs/features/libraries.md @@ -1,5 +1,9 @@ # External Libraries +:::info +Currently an external library can only belong to a single user which is selected when the library is initially created. +::: + External libraries track assets stored in the filesystem outside of Immich. When the external library is scanned, Immich will load videos and photos from disk and create the corresponding assets. These assets will then be shown in the main timeline, and they will look and behave like any other asset, including viewing on the map, adding to albums, etc. Later, if a file is modified outside of Immich, you need to scan the library for the changes to show up. If an external asset is deleted from disk, Immich will move it to trash on rescan. To restore the asset, you need to restore the original file. After 30 days the file will be removed from trash, and any changes to metadata within Immich will be lost. @@ -103,7 +107,7 @@ The `immich-server` container will need access to the gallery. Modify your docke :::tip The `ro` flag at the end only gives read-only access to the volumes. -This will disallow the images from being deleted in the web UI, or adding metadata to the library ([XMP sidecars](/docs/features/xmp-sidecars)). +This will disallow the images from being deleted in the web UI, or adding metadata to the library ([XMP sidecars](/features/xmp-sidecars)). ::: :::info diff --git a/docs/docs/features/ml-hardware-acceleration.md b/docs/docs/features/ml-hardware-acceleration.md index a94f8c8c64..685f23932c 100644 --- a/docs/docs/features/ml-hardware-acceleration.md +++ b/docs/docs/features/ml-hardware-acceleration.md @@ -35,7 +35,7 @@ You do not need to redo any machine learning jobs after enabling hardware accele - Where and how you can get this file depends on device and vendor, but typically, the device vendor also supplies these - The `hwaccel.ml.yml` file assumes the path to it is `/usr/lib/libmali.so`, so update accordingly if it is elsewhere - The `hwaccel.ml.yml` file assumes an additional file `/lib/firmware/mali_csffw.bin`, so update accordingly if your device's driver does not require this file -- Optional: Configure your `.env` file, see [environment variables](/docs/install/environment-variables) for ARM NN specific settings +- Optional: Configure your `.env` file, see [environment variables](/install/environment-variables) for ARM NN specific settings - In particular, the `MACHINE_LEARNING_ANN_FP16_TURBO` can significantly improve performance at the cost of very slightly lower accuracy #### CUDA @@ -49,14 +49,30 @@ You do not need to redo any machine learning jobs after enabling hardware accele - The GPU must be supported by ROCm. If it isn't officially supported, you can attempt to use the `HSA_OVERRIDE_GFX_VERSION` environmental variable: `HSA_OVERRIDE_GFX_VERSION=
`. If this doesn't work, you might need to also set `HSA_USE_SVM=0`. - The ROCm image is quite large and requires at least 35GiB of free disk space. However, pulling later updates to the service through Docker will generally only amount to a few hundred megabytes as the rest will be cached. -- This backend is new and may experience some issues. For example, GPU power consumption can be higher than usual after running inference, even if the machine learning service is idle. In this case, it will only go back to normal after being idle for 5 minutes (configurable with the [MACHINE_LEARNING_MODEL_TTL](/docs/install/environment-variables) setting). +- This backend is new and may experience some issues. For example, GPU power consumption can be higher than usual after running inference, even if the machine learning service is idle. In this case, it will only go back to normal after being idle for 5 minutes (configurable with the [MACHINE_LEARNING_MODEL_TTL](/install/environment-variables) setting). #### OpenVINO - Integrated GPUs are more likely to experience issues than discrete GPUs, especially for older processors or servers with low RAM. -- Ensure the server's kernel version is new enough to use the device for hardware accceleration. +- Ensure the server's kernel version is new enough to use the device for hardware acceleration. - Expect higher RAM usage when using OpenVINO compared to CPU processing. +#### OpenVINO-WSL + +- Ensure your container can access the /dev/dri directory, you can verify this by doing `docker exec -t immich_machine_learning ls -la /dev/dri`. If this is not the case execute `getent group render` and `getent group video` on the WSL host, then add those groups to hwaccel.ml.yaml + ```yaml + openvino-wsl: + devices: + - /dev/dri:/dev/dri + - /dev/dxg:/dev/dxg + volumes: + - /dev/bus/usb:/dev/bus/usb + - /usr/lib/wsl:/usr/lib/wsl + group_add: + - 44 # Replace this number with the number you found with getent group video + - 992 # Replace this number with the number you found with getent group render + ``` + #### RKNN - You must have a supported Rockchip SoC: only RK3566, RK3568, RK3576 and RK3588 are supported at this moment. @@ -64,7 +80,7 @@ You do not need to redo any machine learning jobs after enabling hardware accele - This is usually pre-installed on the device vendor's Linux images - RKNPU driver V0.9.8 or later must be available in the host server - You may confirm this by running `cat /sys/kernel/debug/rknpu/version` to check the version -- Optional: Configure your `.env` file, see [environment variables](/docs/install/environment-variables) for RKNN specific settings +- Optional: Configure your `.env` file, see [environment variables](/install/environment-variables) for RKNN specific settings - In particular, setting `MACHINE_LEARNING_RKNN_THREADS` to 2 or 3 can _dramatically_ improve performance for RK3576 and RK3588 compared to the default of 1, at the expense of multiplying the amount of RAM each model uses by that amount. ## Setup diff --git a/docs/docs/features/mobile-app.mdx b/docs/docs/features/mobile-app.mdx index cd837741f1..8b9a204741 100644 --- a/docs/docs/features/mobile-app.mdx +++ b/docs/docs/features/mobile-app.mdx @@ -3,7 +3,6 @@ import { mdiCloudOffOutline, mdiCloudCheckOutline } from '@mdi/js'; import MobileAppDownload from '/docs/partials/_mobile-app-download.md'; import MobileAppLogin from '/docs/partials/_mobile-app-login.md'; import MobileAppBackup from '/docs/partials/_mobile-app-backup.md'; -import { cloudDonePath, cloudOffPath } from '@site/src/components/svg-paths'; # Mobile App @@ -11,6 +10,16 @@ import { cloudDonePath, cloudOffPath } from '@site/src/components/svg-paths'; +:::info Android verification +Below are the SHA-256 fingerprints for the certificates signing the android applications. + +- Playstore / Github releases: + `86:C5:C4:55:DF:AF:49:85:92:3A:8F:35:AD:B3:1D:0C:9E:0B:95:7D:7F:94:C2:D2:AF:6A:24:38:AA:96:00:20` +- F-Droid releases: + `FA:8B:43:95:F4:A6:47:71:A0:53:D1:C7:57:73:5F:A2:30:13:74:F5:3D:58:0D:D1:75:AA:F7:A1:35:72:9C:BF` + +::: + :::info Beta Program The beta release channel allows users to test upcoming changes before they are officially released. To join the channel use the links below. @@ -28,7 +37,7 @@ The beta release channel allows users to test upcoming changes before they are o :::info -You can enable automatic backup on supported devices. For more information see [Automatic Backup](/docs/features/automatic-backup.md). +You can enable automatic backup on supported devices. For more information see [Automatic Backup](/features/automatic-backup.md). ::: ## Sync only selected photos @@ -75,7 +84,7 @@ You can sync or mirror an album from your phone to the Immich server on your acc - **User-Specific Sync:** Album synchronization is unique to each server user and does not sync between different users or partners. -- **Mobile-Only Feature:** Album synchronization is currently only available on mobile. For similar options on a computer, refer to [Libraries](/docs/features/libraries) for further details. +- **Mobile-Only Feature:** Album synchronization is currently only available on mobile. For similar options on a computer, refer to [Libraries](/features/libraries) for further details. ### Synchronizing albums from the past diff --git a/docs/docs/features/monitoring.md b/docs/docs/features/monitoring.md index c80f66902b..f087a3306f 100644 --- a/docs/docs/features/monitoring.md +++ b/docs/docs/features/monitoring.md @@ -28,7 +28,7 @@ The metrics in immich are grouped into API (endpoint calls and response times), Immich will not expose an endpoint for metrics by default. To enable this endpoint, you can add the `IMMICH_TELEMETRY_INCLUDE=all` environmental variable to your `.env` file. Note that only the server container currently use this variable. :::tip -`IMMICH_TELEMETRY_INCLUDE=all` enables all metrics. For a more granular configuration you can enumerate the telemetry metrics that should be included as a comma separated list (e.g. `IMMICH_TELEMETRY_INCLUDE=repo,api`). Alternatively, you can also exclude specific metrics with `IMMICH_TELEMETRY_EXCLUDE`. For more information refer to the [environment section](/docs/install/environment-variables.md#prometheus). +`IMMICH_TELEMETRY_INCLUDE=all` enables all metrics. For a more granular configuration you can enumerate the telemetry metrics that should be included as a comma separated list (e.g. `IMMICH_TELEMETRY_INCLUDE=repo,api`). Alternatively, you can also exclude specific metrics with `IMMICH_TELEMETRY_EXCLUDE`. For more information refer to the [environment section](/install/environment-variables.md#prometheus). ::: The next step is to configure a new or existing Prometheus instance to scrape this endpoint. The following steps assume that you do not have an existing Prometheus instance, but the steps will be similar either way. @@ -68,7 +68,7 @@ After bringing down the containers with `docker compose down` and back up with ` :::note To see exactly what metrics are made available, you can additionally add `8081:8081` (API metrics) and `8082:8082` (microservices metrics) to the immich_server container's ports. Visiting the `/metrics` endpoint for these services will show the same raw data that Prometheus collects. -To configure these ports see [`IMMICH_API_METRICS_PORT` & `IMMICH_MICROSERVICES_METRICS_PORT`](/docs/install/environment-variables/#general). +To configure these ports see [`IMMICH_API_METRICS_PORT` & `IMMICH_MICROSERVICES_METRICS_PORT`](/install/environment-variables/#general). ::: ### Usage diff --git a/docs/docs/features/reverse-geocoding.md b/docs/docs/features/reverse-geocoding.md index 399bdd9b48..b1aee74a99 100644 --- a/docs/docs/features/reverse-geocoding.md +++ b/docs/docs/features/reverse-geocoding.md @@ -8,7 +8,7 @@ During Exif Extraction, assets with latitudes and longitudes are reverse geocode ## Usage -Data from a reverse geocode is displayed in the image details, and used in [Smart Search](/docs/features/searching.md). +Data from a reverse geocode is displayed in the image details, and used in [Smart Search](/features/searching.md). diff --git a/docs/docs/features/sharing.md b/docs/docs/features/sharing.md index ff0a03beea..c19b4f48e1 100644 --- a/docs/docs/features/sharing.md +++ b/docs/docs/features/sharing.md @@ -24,11 +24,11 @@ After creating an album, you can access the sharing options by clicking on the s Partner sharing allows you to share your _entire_ library with other users of your choice. They can then view your library and download the assets. -You can read this guide to learn more about [partner sharing](/docs/features/partner-sharing). +You can read this guide to learn more about [partner sharing](/features/partner-sharing). ## Public sharing -You can create a public link to share a group of photos or videos, or an album, with anyone. The public link can be shared via email, social media, or any other method. There are a varierity of options to customize the public link, such as setting an expiration date, password protection, and more. Public shared link is handy when you want to share a group of photos or videos with someone who doesn't have an Immich account and allow the shared user to upload their photos or videos to your account. +You can create a public link to share a group of photos or videos, or an album, with anyone. The public link can be shared via email, social media, or any other method. There are a variety of options to customize the public link, such as setting an expiration date, password protection, and more. Public shared link is handy when you want to share a group of photos or videos with someone who doesn't have an Immich account and allow the shared user to upload their photos or videos to your account. The public shared link is generated with a random URL, which acts as as a secret to avoid the link being guessed by unwanted parties, for instance. diff --git a/docs/docs/features/tags.md b/docs/docs/features/tags.md index a5b6752c81..79a9696d9a 100644 --- a/docs/docs/features/tags.md +++ b/docs/docs/features/tags.md @@ -1,6 +1,6 @@ # Tags -Immich supports hierarchical tags, with the ability to read existing tags from the XMP `TagsList` field and IPTC `Keywords` field. Any changes to tags made through Immich are also written back to a [sidecar](/docs/features/xmp-sidecars) file. You can re-run the metadata extraction jobs for all assets to import your existing tags. +Immich supports hierarchical tags, with the ability to read existing tags from the XMP `TagsList` field and IPTC `Keywords` field. Any changes to tags made through Immich are also written back to a [sidecar](/features/xmp-sidecars) file. You can re-run the metadata extraction jobs for all assets to import your existing tags. ## Enable tags feature diff --git a/docs/docs/features/user-settings.md b/docs/docs/features/user-settings.md index a2d0308541..402105cd43 100644 --- a/docs/docs/features/user-settings.md +++ b/docs/docs/features/user-settings.md @@ -15,9 +15,9 @@ You can access the [user settings](https://my.immich.app/user-settings) by click --- :::tip Reset Password -The admin can reset a user password through the [User Management](/docs/administration/user-management.mdx) screen. +The admin can reset a user password through the [User Management](/administration/user-management.mdx) screen. ::: :::tip Reset Admin Password -The admin password can be reset using a [Server Command](/docs/administration/server-commands.md) +The admin password can be reset using a [Server Command](/administration/server-commands.md) ::: diff --git a/docs/docs/guides/better-facial-clusters.md b/docs/docs/guides/better-facial-clusters.md index f4409b441c..40796983a5 100644 --- a/docs/docs/guides/better-facial-clusters.md +++ b/docs/docs/guides/better-facial-clusters.md @@ -10,7 +10,7 @@ This guide explains how to optimize facial recognition in systems with large ima - **Best Suited For:** Large image libraries after importing a significant number of images. - **Warning:** This method deletes all previously assigned names. -- **Tip:** **Always take a [backup](/docs/administration/backup-and-restore#database) before proceeding!** +- **Tip:** **Always take a [backup](/administration/backup-and-restore#database) before proceeding!** --- diff --git a/docs/docs/guides/custom-locations.md b/docs/docs/guides/custom-locations.md index af8ca438e7..e0274d3bd9 100644 --- a/docs/docs/guides/custom-locations.md +++ b/docs/docs/guides/custom-locations.md @@ -9,7 +9,7 @@ It is important to remember to update the backup settings after following the gu In our `.env` file, we will define the paths we want to use. Note that you don't have to define all of these: UPLOAD_LOCATION will be the base folder that files are stored in by default, with the other paths acting as overrides. ```diff title=".env" -# You can find documentation for all the supported environment variables [here](/docs/install/environment-variables) +# You can find documentation for all the supported environment variables [here](/install/environment-variables) # Custom location where your uploaded, thumbnails, and transcoded video files are stored - UPLOAD_LOCATION=./library diff --git a/docs/docs/guides/database-queries.md b/docs/docs/guides/database-queries.md index 1a5c2ed193..c2328d2bb8 100644 --- a/docs/docs/guides/database-queries.md +++ b/docs/docs/guides/database-queries.md @@ -7,7 +7,7 @@ Keep in mind that mucking around in the database might set the Moon on fire. Avo :::tip Run `docker exec -it immich_postgres psql --dbname= --username=` to connect to the database via the container directly. -(Replace `` and `` with the values from your [`.env` file](/docs/install/environment-variables#database)). +(Replace `` and `` with the values from your [`.env` file](/install/environment-variables#database)). ::: ## Assets @@ -106,14 +106,14 @@ SELECT "user"."email", "asset"."type", COUNT(*) FROM "asset" ```sql title="Count by tag" SELECT "t"."value" AS "tag_name", COUNT(*) AS "number_assets" FROM "tag" "t" - JOIN "tag_asset" "ta" ON "t"."id" = "ta"."tagsId" JOIN "asset" "a" ON "ta"."assetsId" = "a"."id" + JOIN "tag_asset" "ta" ON "t"."id" = "ta"."tagId" JOIN "asset" "a" ON "ta"."assetId" = "a"."id" WHERE "a"."visibility" != 'hidden' GROUP BY "t"."value" ORDER BY "number_assets" DESC; ``` ```sql title="Count by tag (per user)" SELECT "t"."value" AS "tag_name", "u"."email" as "user_email", COUNT(*) AS "number_assets" FROM "tag" "t" - JOIN "tag_asset" "ta" ON "t"."id" = "ta"."tagsId" JOIN "asset" "a" ON "ta"."assetsId" = "a"."id" JOIN "user" "u" ON "a"."ownerId" = "u"."id" + JOIN "tag_asset" "ta" ON "t"."id" = "ta"."tagId" JOIN "asset" "a" ON "ta"."assetId" = "a"."id" JOIN "user" "u" ON "a"."ownerId" = "u"."id" WHERE "a"."visibility" != 'hidden' GROUP BY "t"."value", "u"."email" ORDER BY "number_assets" DESC; ``` @@ -142,7 +142,7 @@ DELETE FROM "person" WHERE "name" = 'PersonNameHere'; SELECT "key", "value" FROM "system_metadata" WHERE "key" = 'system-config'; ``` -(Only used when not using the [config file](/docs/install/config-file)) +(Only used when not using the [config file](/install/config-file)) ### File properties diff --git a/docs/docs/guides/external-library.md b/docs/docs/guides/external-library.md index 7921843297..3f366bb0d4 100644 --- a/docs/docs/guides/external-library.md +++ b/docs/docs/guides/external-library.md @@ -1,13 +1,13 @@ # External Library -This guide walks you through adding an [External Library](/docs/features/libraries). +This guide walks you through adding an [External Library](/features/libraries). This guide assumes you are running Immich in Docker and that the files you wish to access are stored in a directory on the same machine. # Mount the directory into the containers. Edit `docker-compose.yml` to add one or more new mount points in the section `immich-server:` under `volumes:`. -If you want Immich to be able to delete the images in the external library or add metadata ([XMP sidecars](/docs/features/xmp-sidecars)), remove `:ro` from the end of the mount point. +If you want Immich to be able to delete the images in the external library or add metadata ([XMP sidecars](/features/xmp-sidecars)), remove `:ro` from the end of the mount point. ```diff immich-server: @@ -21,6 +21,10 @@ Restart Immich by running `docker compose up -d`. # Create the library +:::info +External library management requires administrator access and the steps below assume you are using an admin account. +::: + In the Immich web UI: - click the **Administration** link in the upper right corner. @@ -33,7 +37,7 @@ In the Immich web UI: - In the dialog, select which user should own the new library - + - Click the three-dots menu and select **Edit Import Paths** diff --git a/docs/docs/guides/remote-access.md b/docs/docs/guides/remote-access.md index 6f401dfc5a..518b003c3a 100644 --- a/docs/docs/guides/remote-access.md +++ b/docs/docs/guides/remote-access.md @@ -46,7 +46,7 @@ You can learn how to set up Tailscale together with Immich with the [tutorial vi A reverse proxy is a service that sits between web servers and clients. A reverse proxy can either be hosted on the server itself or remotely. Clients can connect to the reverse proxy via https, and the proxy relays data to Immich. This setup makes most sense if you have your own domain and want to access your Immich instance just like any other website, from outside your LAN. You can also use a DDNS provider like DuckDNS or no-ip if you don't have a domain. This configuration allows the Immich Android and iphone apps to connect to your server without a VPN or tailscale app on the client side. -If you're hosting your own reverse proxy, [Nginx](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/) is a great option. An example configuration for Nginx is provided [here](/docs/administration/reverse-proxy.md). +If you're hosting your own reverse proxy, [Nginx](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/) is a great option. An example configuration for Nginx is provided [here](/administration/reverse-proxy.md). You'll also need your own certificate to authenticate https connections. If you're making Immich publicly accessible, [Let's Encrypt](https://letsencrypt.org/) can provide a free certificate for your domain and is the recommended option. Alternatively, a [self-signed certificate](https://en.wikipedia.org/wiki/Self-signed_certificate) allows you to encrypt your connection to Immich, but it raises a security warning on the client's browser. diff --git a/docs/docs/guides/remote-machine-learning.md b/docs/docs/guides/remote-machine-learning.md index 72ae0e3fa1..0a8ddf2577 100644 --- a/docs/docs/guides/remote-machine-learning.md +++ b/docs/docs/guides/remote-machine-learning.md @@ -1,6 +1,6 @@ # Remote Machine Learning -To alleviate [performance issues on low-memory systems](/docs/FAQ.mdx#why-is-immich-slow-on-low-memory-systems-like-the-raspberry-pi) like the Raspberry Pi, you may also host Immich's machine learning container on a more powerful system, such as your laptop or desktop computer. The server container will send requests containing the image preview to the remote machine learning container for processing. The machine learning container does not persist this data or associate it with a particular user. +To alleviate [performance issues on low-memory systems](/FAQ.mdx#why-is-immich-slow-on-low-memory-systems-like-the-raspberry-pi) like the Raspberry Pi, you may also host Immich's machine learning container on a more powerful system, such as your laptop or desktop computer. The server container will send requests containing the image preview to the remote machine learning container for processing. The machine learning container does not persist this data or associate it with a particular user. :::info Smart Search and Face Detection will use this feature, but Facial Recognition will not. This is because Facial Recognition uses the _outputs_ of these models that have already been saved to the database. As such, its processing is between the server container and the database. @@ -14,7 +14,7 @@ Image previews are sent to the remote machine learning container. Use this optio 2. Copy the following `docker-compose.yml` to the remote server :::info -If using hardware acceleration, the [hwaccel.ml.yml](https://github.com/immich-app/immich/releases/latest/download/hwaccel.ml.yml) file also needs to be added and the `docker-compose.yml` needs to be configured as described in the [hardware acceleration documentation](/docs/features/ml-hardware-acceleration) +If using hardware acceleration, the [hwaccel.ml.yml](https://github.com/immich-app/immich/releases/latest/download/hwaccel.ml.yml) file also needs to be added and the `docker-compose.yml` needs to be configured as described in the [hardware acceleration documentation](/features/ml-hardware-acceleration) ::: ```yaml diff --git a/docs/docs/guides/template-backup-script.md b/docs/docs/guides/template-backup-script.md index 34381dd0ee..19647d4ae1 100644 --- a/docs/docs/guides/template-backup-script.md +++ b/docs/docs/guides/template-backup-script.md @@ -7,7 +7,7 @@ This script assumes you have a second hard drive connected to your server for on The database is saved to your Immich upload folder in the `database-backup` subdirectory. The database is then backed up and versioned with your assets by Borg. This ensures that the database backup is in sync with your assets in every snapshot. :::info -This script makes backups of your database along with your photo/video library. This is redundant with the [automatic database backup tool](https://immich.app/docs/administration/backup-and-restore#automatic-database-backups) built into Immich. Using this script to backup your database has two advantages over the built-in backup tool: +This script makes backups of your database along with your photo/video library. This is redundant with the [automatic database backup tool](/administration/backup-and-restore#automatic-database-dumps) built into Immich. Using this script to backup your database has two advantages over the built-in backup tool: - This script uses storage more efficiently by versioning your backups instead of making multiple copies. - The database backups are performed at the same time as the library backup, ensuring that the backups of your database and the library are always in sync. diff --git a/docs/docs/install/config-file.md b/docs/docs/install/config-file.md index 54d7c61bb3..a6aaae149b 100644 --- a/docs/docs/install/config-file.md +++ b/docs/docs/install/config-file.md @@ -16,48 +16,76 @@ The default configuration looks like this: ```json { - "ffmpeg": { - "crf": 23, - "threads": 0, - "preset": "ultrafast", - "targetVideoCodec": "h264", - "acceptedVideoCodecs": ["h264"], - "targetAudioCodec": "aac", - "acceptedAudioCodecs": ["aac", "mp3", "libopus", "pcm_s16le"], - "acceptedContainers": ["mov", "ogg", "webm"], - "targetResolution": "720", - "maxBitrate": "0", - "bframes": -1, - "refs": 0, - "gopSize": 0, - "temporalAQ": false, - "cqMode": "auto", - "twoPass": false, - "preferredHwDevice": "auto", - "transcode": "required", - "tonemap": "hable", - "accel": "disabled", - "accelDecode": false - }, "backup": { "database": { - "enabled": true, "cronExpression": "0 02 * * *", + "enabled": true, "keepLastAmount": 14 } }, + "ffmpeg": { + "accel": "disabled", + "accelDecode": false, + "acceptedAudioCodecs": ["aac", "mp3", "libopus"], + "acceptedContainers": ["mov", "ogg", "webm"], + "acceptedVideoCodecs": ["h264"], + "bframes": -1, + "cqMode": "auto", + "crf": 23, + "gopSize": 0, + "maxBitrate": "0", + "preferredHwDevice": "auto", + "preset": "ultrafast", + "refs": 0, + "targetAudioCodec": "aac", + "targetResolution": "720", + "targetVideoCodec": "h264", + "temporalAQ": false, + "threads": 0, + "tonemap": "hable", + "transcode": "required", + "twoPass": false + }, + "image": { + "colorspace": "p3", + "extractEmbedded": false, + "fullsize": { + "enabled": false, + "format": "jpeg", + "quality": 80 + }, + "preview": { + "format": "jpeg", + "quality": 80, + "size": 1440 + }, + "thumbnail": { + "format": "webp", + "quality": 80, + "size": 250 + } + }, "job": { "backgroundTask": { "concurrency": 5 }, - "smartSearch": { + "faceDetection": { "concurrency": 2 }, + "library": { + "concurrency": 5 + }, "metadataExtraction": { "concurrency": 5 }, - "faceDetection": { - "concurrency": 2 + "migration": { + "concurrency": 5 + }, + "notifications": { + "concurrency": 5 + }, + "ocr": { + "concurrency": 1 }, "search": { "concurrency": 5 @@ -65,20 +93,23 @@ The default configuration looks like this: "sidecar": { "concurrency": 5 }, - "library": { - "concurrency": 5 - }, - "migration": { - "concurrency": 5 + "smartSearch": { + "concurrency": 2 }, "thumbnailGeneration": { "concurrency": 3 }, "videoConversion": { "concurrency": 1 + } + }, + "library": { + "scan": { + "cronExpression": "0 0 * * *", + "enabled": true }, - "notifications": { - "concurrency": 5 + "watch": { + "enabled": false } }, "logging": { @@ -86,8 +117,11 @@ The default configuration looks like this: "level": "log" }, "machineLearning": { - "enabled": true, - "urls": ["http://immich-machine-learning:3003"], + "availabilityChecks": { + "enabled": true, + "interval": 30000, + "timeout": 2000 + }, "clip": { "enabled": true, "modelName": "ViT-B-32__openai" @@ -96,27 +130,59 @@ The default configuration looks like this: "enabled": true, "maxDistance": 0.01 }, + "enabled": true, "facialRecognition": { "enabled": true, - "modelName": "buffalo_l", - "minScore": 0.7, "maxDistance": 0.5, - "minFaces": 3 - } + "minFaces": 3, + "minScore": 0.7, + "modelName": "buffalo_l" + }, + "ocr": { + "enabled": true, + "maxResolution": 736, + "minDetectionScore": 0.5, + "minRecognitionScore": 0.8, + "modelName": "PP-OCRv5_mobile" + }, + "urls": ["http://immich-machine-learning:3003"] }, "map": { + "darkStyle": "https://tiles.immich.cloud/v1/style/dark.json", "enabled": true, - "lightStyle": "https://tiles.immich.cloud/v1/style/light.json", - "darkStyle": "https://tiles.immich.cloud/v1/style/dark.json" - }, - "reverseGeocoding": { - "enabled": true + "lightStyle": "https://tiles.immich.cloud/v1/style/light.json" }, "metadata": { "faces": { "import": false } }, + "newVersionCheck": { + "enabled": true + }, + "nightlyTasks": { + "clusterNewFaces": true, + "databaseCleanup": true, + "generateMemories": true, + "missingThumbnails": true, + "startTime": "00:00", + "syncQuotaUsage": true + }, + "notifications": { + "smtp": { + "enabled": false, + "from": "", + "replyTo": "", + "transport": { + "host": "", + "ignoreCert": false, + "password": "", + "port": 587, + "secure": false, + "username": "" + } + } + }, "oauth": { "autoLaunch": false, "autoRegister": true, @@ -128,70 +194,44 @@ The default configuration looks like this: "issuerUrl": "", "mobileOverrideEnabled": false, "mobileRedirectUri": "", + "profileSigningAlgorithm": "none", + "roleClaim": "immich_role", "scope": "openid email profile", "signingAlgorithm": "RS256", - "profileSigningAlgorithm": "none", "storageLabelClaim": "preferred_username", - "storageQuotaClaim": "immich_quota" + "storageQuotaClaim": "immich_quota", + "timeout": 30000, + "tokenEndpointAuthMethod": "client_secret_post" }, "passwordLogin": { "enabled": true }, + "reverseGeocoding": { + "enabled": true + }, + "server": { + "externalDomain": "", + "loginPageMessage": "", + "publicUsers": true + }, "storageTemplate": { "enabled": false, "hashVerificationEnabled": true, "template": "{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}" }, - "image": { - "thumbnail": { - "format": "webp", - "size": 250, - "quality": 80 - }, - "preview": { - "format": "jpeg", - "size": 1440, - "quality": 80 - }, - "colorspace": "p3", - "extractEmbedded": false - }, - "newVersionCheck": { - "enabled": true - }, - "trash": { - "enabled": true, - "days": 30 + "templates": { + "email": { + "albumInviteTemplate": "", + "albumUpdateTemplate": "", + "welcomeTemplate": "" + } }, "theme": { "customCss": "" }, - "library": { - "scan": { - "enabled": true, - "cronExpression": "0 0 * * *" - }, - "watch": { - "enabled": false - } - }, - "server": { - "externalDomain": "", - "loginPageMessage": "" - }, - "notifications": { - "smtp": { - "enabled": false, - "from": "", - "replyTo": "", - "transport": { - "ignoreCert": false, - "host": "", - "port": 587, - "username": "", - "password": "" - } - } + "trash": { + "days": 30, + "enabled": true }, "user": { "deleteDelay": 7 @@ -209,7 +249,7 @@ So you can just grab it from there, paste it into a file and you're pretty much ### Step 2 - Specify the file location In your `.env` file, set the variable `IMMICH_CONFIG_FILE` to the path of your config. -For more information, refer to the [Environment Variables](/docs/install/environment-variables.md) section. +For more information, refer to the [Environment Variables](/install/environment-variables.md) section. :::tip YAML-formatted config files are also supported. diff --git a/docs/docs/install/docker-compose.mdx b/docs/docs/install/docker-compose.mdx index 7a0b566f5d..46b144eb4a 100644 --- a/docs/docs/install/docker-compose.mdx +++ b/docs/docs/install/docker-compose.mdx @@ -29,4 +29,4 @@ If you get an error `can't set healthcheck.start_interval as feature require Doc ## Next Steps -Read the [Post Installation](/docs/install/post-install.mdx) steps and [upgrade instructions](/docs/install/upgrading.md). +Read the [Post Installation](/install/post-install.mdx) steps and [upgrade instructions](/install/upgrading.md). diff --git a/docs/docs/install/environment-variables.md b/docs/docs/install/environment-variables.md index 4e081c8966..55c226d507 100644 --- a/docs/docs/install/environment-variables.md +++ b/docs/docs/install/environment-variables.md @@ -42,7 +42,7 @@ These environment variables are used by the `docker-compose.yml` file and do **N | `IMMICH_MICROSERVICES_METRICS_PORT` | Port for the OTEL metrics | `8082` | server | microservices | | `IMMICH_PROCESS_INVALID_IMAGES` | When `true`, generate thumbnails for invalid images | | server | microservices | | `IMMICH_TRUSTED_PROXIES` | List of comma-separated IPs set as trusted proxies | | server | api | -| `IMMICH_IGNORE_MOUNT_CHECK_ERRORS` | See [System Integrity](/docs/administration/system-integrity) | | server | api, microservices | +| `IMMICH_IGNORE_MOUNT_CHECK_ERRORS` | See [System Integrity](/administration/system-integrity) | | server | api, microservices | \*1: `TZ` should be set to a `TZ identifier` from [this list][tz-list]. For example, `TZ="Etc/UTC"`. `TZ` is used by `exiftool` as a fallback in case the timezone cannot be determined from the image metadata. It is also used for logfile timestamps and cron job execution. @@ -57,7 +57,7 @@ These environment variables are used by the `docker-compose.yml` file and do **N | `IMMICH_WORKERS_EXCLUDE` | Do not run these workers. Matches against default workers, or `IMMICH_WORKERS_INCLUDE` if specified. | | server | :::info -Information on the current workers can be found [here](/docs/administration/jobs-workers). +Information on the current workers can be found [here](/administration/jobs-workers). ::: ## Ports @@ -149,28 +149,31 @@ Redis (Sentinel) URL example JSON before encoding: ## Machine Learning -| Variable | Description | Default | Containers | -| :---------------------------------------------------------- | :-------------------------------------------------------------------------------------------------- | :-----------------------------: | :--------------- | -| `MACHINE_LEARNING_MODEL_TTL` | Inactivity time (s) before a model is unloaded (disabled if \<= 0) | `300` | machine learning | -| `MACHINE_LEARNING_MODEL_TTL_POLL_S` | Interval (s) between checks for the model TTL (disabled if \<= 0) | `10` | machine learning | -| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning | -| `MACHINE_LEARNING_REQUEST_THREADS`\*1 | Thread count of the request thread pool (disabled if \<= 0) | number of CPU cores | machine learning | -| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning | -| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning | -| `MACHINE_LEARNING_WORKERS`\*2 | Number of worker processes to spawn | `1` | machine learning | -| `MACHINE_LEARNING_HTTP_KEEPALIVE_TIMEOUT_S`\*3 | HTTP Keep-alive time in seconds | `2` | machine learning | -| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `120` (`300` if using OpenVINO) | machine learning | -| `MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL` | Comma-separated list of (textual) CLIP model(s) to preload and cache | | machine learning | -| `MACHINE_LEARNING_PRELOAD__CLIP__VISUAL` | Comma-separated list of (visual) CLIP model(s) to preload and cache | | machine learning | -| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION` | Comma-separated list of (recognition) facial recognition model(s) to preload and cache | | machine learning | -| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION` | Comma-separated list of (detection) facial recognition model(s) to preload and cache | | machine learning | -| `MACHINE_LEARNING_ANN` | Enable ARM-NN hardware acceleration if supported | `True` | machine learning | -| `MACHINE_LEARNING_ANN_FP16_TURBO` | Execute operations in FP16 precision: increasing speed, reducing precision (applies only to ARM-NN) | `False` | machine learning | -| `MACHINE_LEARNING_ANN_TUNING_LEVEL` | ARM-NN GPU tuning level (1: rapid, 2: normal, 3: exhaustive) | `2` | machine learning | -| `MACHINE_LEARNING_DEVICE_IDS`\*4 | Device IDs to use in multi-GPU environments | `0` | machine learning | -| `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning | -| `MACHINE_LEARNING_RKNN` | Enable RKNN hardware acceleration if supported | `True` | machine learning | -| `MACHINE_LEARNING_RKNN_THREADS` | How many threads of RKNN runtime should be spinned up while inferencing. | `1` | machine learning | +| Variable | Description | Default | Containers | +| :---------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------------------------: | :--------------- | +| `MACHINE_LEARNING_MODEL_TTL` | Inactivity time (s) before a model is unloaded (disabled if \<= 0) | `300` | machine learning | +| `MACHINE_LEARNING_MODEL_TTL_POLL_S` | Interval (s) between checks for the model TTL (disabled if \<= 0) | `10` | machine learning | +| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning | +| `MACHINE_LEARNING_REQUEST_THREADS`\*1 | Thread count of the request thread pool (disabled if \<= 0) | number of CPU cores | machine learning | +| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning | +| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning | +| `MACHINE_LEARNING_WORKERS`\*2 | Number of worker processes to spawn | `1` | machine learning | +| `MACHINE_LEARNING_HTTP_KEEPALIVE_TIMEOUT_S`\*3 | HTTP Keep-alive time in seconds | `2` | machine learning | +| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `120` (`300` if using OpenVINO) | machine learning | +| `MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL` | Comma-separated list of (textual) CLIP model(s) to preload and cache | | machine learning | +| `MACHINE_LEARNING_PRELOAD__CLIP__VISUAL` | Comma-separated list of (visual) CLIP model(s) to preload and cache | | machine learning | +| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION` | Comma-separated list of (recognition) facial recognition model(s) to preload and cache | | machine learning | +| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION` | Comma-separated list of (detection) facial recognition model(s) to preload and cache | | machine learning | +| `MACHINE_LEARNING_ANN` | Enable ARM-NN hardware acceleration if supported | `True` | machine learning | +| `MACHINE_LEARNING_ANN_FP16_TURBO` | Execute operations in FP16 precision: increasing speed, reducing precision (applies only to ARM-NN) | `False` | machine learning | +| `MACHINE_LEARNING_ANN_TUNING_LEVEL` | ARM-NN GPU tuning level (1: rapid, 2: normal, 3: exhaustive) | `2` | machine learning | +| `MACHINE_LEARNING_DEVICE_IDS`\*4 | Device IDs to use in multi-GPU environments | `0` | machine learning | +| `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning | +| `MACHINE_LEARNING_MAX_BATCH_SIZE__OCR` | Set the maximum number of boxes that will be processed at once by the OCR model | `6` | machine learning | +| `MACHINE_LEARNING_RKNN` | Enable RKNN hardware acceleration if supported | `True` | machine learning | +| `MACHINE_LEARNING_RKNN_THREADS` | How many threads of RKNN runtime should be spun up while inferencing. | `1` | machine learning | +| `MACHINE_LEARNING_MODEL_ARENA` | Pre-allocates CPU memory to avoid memory fragmentation | true | machine learning | +| `MACHINE_LEARNING_OPENVINO_PRECISION` | If set to FP16, uses half-precision floating-point operations for faster inference with reduced accuracy (one of [`FP16`, `FP32`], applies only to OpenVINO) | `FP32` | machine learning | \*1: It is recommended to begin with this parameter when changing the concurrency levels of the machine learning service and then tune the other ones. diff --git a/docs/docs/install/one-click.md b/docs/docs/install/one-click.md new file mode 100644 index 0000000000..53fcb20d21 --- /dev/null +++ b/docs/docs/install/one-click.md @@ -0,0 +1,32 @@ +--- +sidebar_position: 65 +--- + +# One-Click [Cloud Service] + +:::note +This version of Immich is provided via cloud service providers' one-click marketplaces. Hosting costs are set by the cloud service providers. +Support for these are provided by the individual cloud service providers. + +**Please report issues to the corresponding [Github Repository][github].** +::: + +## Installation + +Go to the provider's marketplace and choose Immich, then follow the provided instructions. + +## One-Click Immich marketplace providers + +### DigitalOcean + +https://marketplace.digitalocean.com/apps/immich + +### Vultr + +https://www.vultr.com/marketplace/apps/immich + +## Issues + +For issues, open an issue on the associated [GitHub Repository][github]. + +[github]: https://github.com/immich-app/immich/ diff --git a/docs/docs/install/portainer.md b/docs/docs/install/portainer.md index 916d89a0d5..07fd255292 100644 --- a/docs/docs/install/portainer.md +++ b/docs/docs/install/portainer.md @@ -45,5 +45,5 @@ alt="Dot Env Example" 11. Click on "**Deploy the stack**". :::tip -For more information on how to use the application, please refer to the [Post Installation](/docs/install/post-install.mdx) guide. +For more information on how to use the application, please refer to the [Post Installation](/install/post-install.mdx) guide. ::: diff --git a/docs/docs/install/post-install.mdx b/docs/docs/install/post-install.mdx index 636274aaea..b30e91f3cd 100644 --- a/docs/docs/install/post-install.mdx +++ b/docs/docs/install/post-install.mdx @@ -44,6 +44,6 @@ A list of common steps to take after installing Immich include: ## Setting up optional features -- [External Libraries](/docs/features/libraries.md): Adding your existing photo library to Immich -- [Hardware Transcoding](/docs/features/hardware-transcoding.md): Speeding up video transcoding -- [Hardware-Accelerated Machine Learning](/docs/features/ml-hardware-acceleration.md): Speeding up various machine learning tasks in Immich +- [External Libraries](/features/libraries.md): Adding your existing photo library to Immich +- [Hardware Transcoding](/features/hardware-transcoding.md): Speeding up video transcoding +- [Hardware-Accelerated Machine Learning](/features/ml-hardware-acceleration.md): Speeding up various machine learning tasks in Immich diff --git a/docs/docs/install/script.md b/docs/docs/install/script.md index 93d1fb166c..ce05dc82d9 100644 --- a/docs/docs/install/script.md +++ b/docs/docs/install/script.md @@ -5,12 +5,12 @@ sidebar_position: 20 # Install script [Experimental] :::caution -This method is experimental and not currently recommended for production use. For production, please refer to installing with [Docker Compose](/docs/install/docker-compose.mdx). +This method is experimental and not currently recommended for production use. For production, please refer to installing with [Docker Compose](/install/docker-compose.mdx). ::: ## Requirements -Follow the [requirements page](/docs/install/requirements) to get started. +Follow the [requirements page](/install/requirements) to get started. The install script only supports Linux operating systems and requires Docker to be already installed on the system. @@ -32,5 +32,5 @@ The web application and mobile app will be available at `http:// + Updating Immich using Container Manager +Check the post installation and upgrade instructions at the links above before proceeding with this section. + +## Step 1. Backup + +Ensure your photos and videos are backed up. Your `.env` settings will define where they are stored. There is no need to delete any files or folders within the `docker` folder when doing a release upgrade unless instructed in the release notes. + +## Step 2. Check release notes + +Always check the [release notes](https://github.com/immich-app/immich/releases) before proceeding with an update! + +## Step 3. Stop containers & clean up + +Open **Container Manager**. Select **Project** then your Immich app + +![Select project](../../static/img/synology-select-proj.png) + +Select **Stop** + +![Stop project](../../static/img/synology-project-stop.png) + +Select **Action** then **Clean**. This removes the containers. + +![Clean project](../../static/img/synology-action-clean.png) + +Go to **Image** and select **Remove Unused Images**. + +![Remove unused](../../static/img/synology-remove-unused.png) + +## Step 4. Build + +Go to **Project**, select **Action** then **Build**. This will download, unpack, install and start the containers. + +![Build](../../static/img/synology-build.png) + +## Step 5. Update firewall rule + +The default behavior is to automatically start the containers once installed. If `immich_server` runs for a few seconds and then stops, it may be because the firewall rule no longer matches the server IP address. + +Go to the **Container** section. Click on `immich_server` and scroll down on **General** to find the IP address. +![Container IP](../../static/img/synology-container-ip.png) + +Go to Synology **Control Panel**. Select **Security** and **Firewall**. + +![Firewall](../../static/img/synology-fw-rules.png) + +In this example, the IP addresses mismatch and the firewall rule needs to be edited to match above. + +![Edit IP](../../static/img/synology-fw-ipedit.png) + + diff --git a/docs/docs/install/truenas.md b/docs/docs/install/truenas.md index a13147a1be..9135b72fe6 100644 --- a/docs/docs/install/truenas.md +++ b/docs/docs/install/truenas.md @@ -60,13 +60,13 @@ For an easy setup: :::tip To improve performance, Immich recommends using SSDs for the database. If you have a pool made of SSDs, you can create the `pgData` dataset on that pool. -Thumbnails can also be stored on the SSDs for faster access. This is an advanced option and not required for Immich to run. More information on how you can use multiple datasets to manage Immich storage in a finer-grained manner can be found in the [Advanced: Multiple Datasets for Immich Storage](#advanced-multiple-datasets-for-immich-storage) section below. +Thumbnails can also be stored on the SSDs for faster access. This is an advanced option and not required for Immich to run. More information on how you can use multiple datasets to manage Immich storage in a finer-grained manner can be found in the [Additional Storage: Multiple Datasets for Immich Storage](#additional-storage-advanced-users) section below. ::: :::warning If you just created the datasets using the **Apps** preset, you can skip this warning section. -If the **data** dataset uses ACL it must have [ACL mode](https://www.truenas.com/docs/scale/scaletutorials/datasets/permissionsscale/) set to `Passthrough` if you plan on using a [storage template](/docs/administration/storage-template.mdx) and the dataset is configured for network sharing (its ACL type is set to `SMB/NFSv4`). When the template is applied and files need to be moved from **upload** to **library** (internal folder created by Immich within the **data** dataset), Immich performs `chmod` internally and must be allowed to execute the command. [More info.](https://github.com/immich-app/immich/pull/13017) +If the **data** dataset uses ACL it must have [ACL mode](https://www.truenas.com/docs/scale/scaletutorials/datasets/permissionsscale/) set to `Passthrough` if you plan on using a [storage template](/administration/storage-template.mdx) and the dataset is configured for network sharing (its ACL type is set to `SMB/NFSv4`). When the template is applied and files need to be moved from **upload** to **library** (internal folder created by Immich within the **data** dataset), Immich performs `chmod` internally and must be allowed to execute the command. [More info.](https://github.com/immich-app/immich/pull/13017) To change or verify the ACL mode, go to the **Datasets** screen, select the **library** dataset, click on the **Edit** button next to **Dataset Details**, then click on the **Advanced Options** tab, scroll down to the **ACL Mode** section, and select `Passthrough` from the dropdown menu. Click **Save** to apply the changes. If the option is greyed out, set the **ACL Type** to `SMB/NFSv4` first, then you can change the **ACL Mode** to `Passthrough`. ::: @@ -129,7 +129,7 @@ The **Timezone** is set to the system default, which usually matches your local **Enable Machine Learning** is enabled by default. It allows Immich to use machine learning features such as face recognition, image search, and smart duplicate detection. Untick this option if you do not want to use these features. -Select the **Machine Learning Image Type** based on the hardware you have. More details here: [Hardware-Accelerated Machine Learning](/docs/features/ml-hardware-acceleration.md) +Select the **Machine Learning Image Type** based on the hardware you have. More details here: [Hardware-Accelerated Machine Learning](/features/ml-hardware-acceleration.md) **Database Password** should be set to a custom value using only the characters `A-Za-z0-9`. This password is used to secure the Postgres database. @@ -156,7 +156,7 @@ className="border rounded-xl" /> These are used to add custom configuration options or to enable specific features. -More information on available environment variables can be found in the **[environment variables documentation](/docs/install/environment-variables/)**. +More information on available environment variables can be found in the **[environment variables documentation](/install/environment-variables/)**. :::info Some environment variables are not available for the TrueNAS Community Edition app as they can be configured through GUI options in the [Edit Immich screen](#edit-app-settings). @@ -242,7 +242,7 @@ alt="Add External Libraries with Additional Storage" className="border rounded-xl" /> -You may configure [external libraries](/docs/features/libraries) by mounting them using **Additional Storage**. +You may configure [external libraries](/features/libraries) by mounting them using **Additional Storage**. The dataset that contains your external library files must at least give **read** access to the user running Immich (Default: `apps` (UID 568), `apps` (GID 568)). If you want to be able to delete files or edit metadata in the external library using Immich, you will need to give the **modify** permission to the user running Immich. @@ -266,7 +266,7 @@ A general recommendation is to mount any external libraries to a path beginning This feature should only be used by advanced users. ::: -Immich can use multiple datasets for its storage, allowing you to manage your data more granularly, similar to the old storage configuration. This is useful if you want to separate your data into different datasets for performance or organizational reasons. There is a general guide for this [here](/docs/guides/custom-locations), but read on for the TrueNAS guide. +Immich can use multiple datasets for its storage, allowing you to manage your data more granularly, similar to the old storage configuration. This is useful if you want to separate your data into different datasets for performance or organizational reasons. There is a general guide for this [here](/guides/custom-locations), but read on for the TrueNAS guide. Each additional dataset has to give the permission **_modify_** to the user who will run Immich (Default: `apps` (UID 568), `apps` (GID 568)) As described in the [Setting up Storage Datasets](#setting-up-storage-datasets) section above, you have to create the datasets with the **Apps** preset to ensure the correct permissions are set, or you can set the permissions manually after creating the datasets. @@ -309,7 +309,7 @@ className="border rounded-xl" Both **CPU** and **Memory** are limits, not reservations. This means that Immich can use up to the specified amount of CPU threads and RAM, but it will not reserve that amount of resources at all times. The system will allocate resources as needed, and Immich will use less than the specified amount most of the time. -- Enable **GPU Configuration** options if you have a GPU or CPU with integrated graphics that you will use for [Hardware Transcoding](/docs/features/hardware-transcoding) and/or [Hardware-Accelerated Machine Learning](/docs/features/ml-hardware-acceleration.md). +- Enable **GPU Configuration** options if you have a GPU or CPU with integrated graphics that you will use for [Hardware Transcoding](/features/hardware-transcoding) and/or [Hardware-Accelerated Machine Learning](/features/ml-hardware-acceleration.md). The process for NVIDIA GPU passthrough requires additional steps. More details here: [GPU Passthrough Docs for TrueNAS Apps](https://apps.truenas.com/managing-apps/installing-apps/#gpu-passthrough) @@ -332,7 +332,7 @@ Click **Web Portal** on the **Application Info** widget, or go to the URL `http: After that, you can start using Immich to upload and manage your photos and videos. :::tip -For more information on how to use the application once installed, please refer to the [Post Install](/docs/install/post-install.mdx) guide. +For more information on how to use the application once installed, please refer to the [Post Install](/install/post-install.mdx) guide. ::: ## Edit App Settings @@ -347,7 +347,7 @@ For more information on how to use the application once installed, please refer ## Updating the App :::danger -Make sure to read the general [upgrade instructions](/docs/install/upgrading.md). +Make sure to read the general [upgrade instructions](/install/upgrading.md). ::: When updates become available, TrueNAS alerts and provides easy updates. @@ -387,27 +387,35 @@ To migrate from the old storage configuration to the new one, you will need to c 3. **Copy the data** from the old datasets to the new dataset. We advise using the `rsync` command to copy the data, as it will preserve the permissions and ownership of the files. The following commands are examples: ```bash -rsync -av /mnt/tank/immich/library/ /mnt/tank/immich/data/library/ -rsync -av /mnt/tank/immich/upload/ /mnt/tank/immich/data/upload/ -rsync -av /mnt/tank/immich/thumbs/ /mnt/tank/immich/data/thumbs/ -rsync -av /mnt/tank/immich/profile/ /mnt/tank/immich/data/profile/ -rsync -av /mnt/tank/immich/video/ /mnt/tank/immich/data/encoded-video/ -rsync -av /mnt/tank/immich/backups/ /mnt/tank/immich/data/backups/ +sudo rsync -av /mnt/tank/immich/library/ /mnt/tank/immich/data/library/ +sudo rsync -av /mnt/tank/immich/upload/ /mnt/tank/immich/data/upload/ +sudo rsync -av /mnt/tank/immich/thumbs/ /mnt/tank/immich/data/thumbs/ +sudo rsync -av /mnt/tank/immich/profile/ /mnt/tank/immich/data/profile/ +sudo rsync -av /mnt/tank/immich/video/ /mnt/tank/immich/data/encoded-video/ +sudo rsync -av /mnt/tank/immich/backups/ /mnt/tank/immich/data/backups/ ``` Make sure to replace `/mnt/tank/immich/` with the correct path to your old datasets and `/mnt/tank/immich/data/` with the correct path to your new dataset. :::tip -If you were using **ixVolume (dataset created automatically by the system)** for Immich data storage, the path to the data should be `/mnt/.ix-apps/app_mounts/immich/`. You have to use this path instead of `/mnt/tank/immich/` in the `rsync` command above, for example: +If you were using **ixVolume (dataset created automatically by the system)** for some of Immich data storage, the path to the data should be `/mnt/.ix-apps/app_mounts/immich/`. You have to use this path instead of `/mnt/tank/immich/` in the `rsync` command above, for example: ```bash -rsync -av /mnt/.ix-apps/app_mounts/immich/library/ /mnt/tank/immich/data/library/ +sudo rsync -av /mnt/.ix-apps/app_mounts/immich/library/ /mnt/tank/immich/data/library/ ``` +If you also were storing your files in the **ixVolume**, the **_upload_** folder is named `uploads` instead of `upload`, so the command to run should be: + +```bash +sudo rsync -av /mnt/.ix-apps/app_mounts/immich/uploads/ /mnt/tank/immich/data/upload/ +``` + +This means that depending on your old storage configuration, you might have to use a mix of paths in the `rsync` commands above. + If you were also using an ixVolume for Postgres data storage, you also should, first create the pgData dataset, as described in the [Setting up Storage Datasets](#setting-up-storage-datasets) section above, and then you can use the following command to copy the Postgres data: ```bash -rsync -av /mnt/.ix-apps/app_mounts/immich/pgData/ /mnt/tank/immich/pgData/ +sudo rsync -av /mnt/.ix-apps/app_mounts/immich/pgData/ /mnt/tank/immich/pgData/ ``` ::: @@ -416,7 +424,7 @@ rsync -av /mnt/.ix-apps/app_mounts/immich/pgData/ /mnt/tank/immich/pgData/ Make sure that for each folder, the `.immich` file is copied as well, as it contains important metadata for Immich. If for some reason the `.immich` file is not copied, you can copy it manually with the `rsync` command, for example: ```bash -rsync -av /mnt/tank/immich/library/.immich /mnt/tank/immich/data/library/ +sudo rsync -av /mnt/tank/immich/library/.immich /mnt/tank/immich/data/library/ ``` Replace `library` with the name of the folder where you are copying the file. @@ -437,38 +445,37 @@ This will recreate the Immich container with the new storage configuration and s If everything went well, you should now be able to access Immich with the new storage configuration. You can verify that the data has been copied correctly by checking the Immich web interface and ensuring that all your photos and videos are still available. You may delete the old datasets, if you no longer need them, using the TrueNAS web interface. +:::tip If you were using **ixVolume (dataset created automatically by the system)** or folders for Immich data storage, you can delete the old datasets using the following commands: ```bash -rm -r /mnt/.ix-apps/app_mounts/immich/library -rm -r /mnt/.ix-apps/app_mounts/immich/uploads -rm -r /mnt/.ix-apps/app_mounts/immich/thumbs -rm -r /mnt/.ix-apps/app_mounts/immich/profile -rm -r /mnt/.ix-apps/app_mounts/immich/video -rm -r /mnt/.ix-apps/app_mounts/immich/backups +sudo rm -r /mnt/.ix-apps/app_mounts/immich/* ``` +::: + - + To migrate from the old storage configuration to the new one without creating new datasets. + 1. **Stop the Immich app** from the TrueNAS web interface to ensure no data is being written while you are updating the app. -2. **Update the datasets permissions**: Ensure that the datasets used for Immich data storage (`library`, `upload`, `thumbs`, `profile`, `video`, `backups`) have the correct permissions set for the user who will run Immich. The user should have ***modify*** permissions on these datasets. The default user for Immich is `apps` (UID 568) and the default group is `apps` (GID 568). If you are using a different user, make sure to set the permissions accordingly. You can do this from the TrueNAS web interface by going to the **Datasets** screen, selecting each dataset, clicking on the **Edit** button next to **Permissions**, and adding the user with ***modify*** permissions. +2. **Update the datasets permissions**: Ensure that the datasets used for Immich data storage (`library`, `upload`, `thumbs`, `profile`, `video`, `backups`) have the correct permissions set for the user who will run Immich. The user should have **_modify_** permissions on these datasets. The default user for Immich is `apps` (UID 568) and the default group is `apps` (GID 568). If you are using a different user, make sure to set the permissions accordingly. You can do this from the TrueNAS web interface by going to the **Datasets** screen, selecting each dataset, clicking on the **Edit** button next to **Permissions**, and adding the user with **_modify_** permissions. 3. **Update the Immich app** to use the existing datasets: - - Go to the **Installed Applications** screen and select Immich from the list of installed applications. - - Click **Edit** on the **Application Info** widget. - - In the **Storage Configuration** section, untick the **Use Old Storage Configuration (Deprecated)** checkbox. - - For the **Data Storage**, you can keep the **ixVolume (dataset created automatically by the system)** as no data will be directly written to it. We recommend selecting **Host Path (Path that already exists on the system)** and then select a **new** dataset you created for Immich data storage, for example, `data`. - - For the **Postgres Data Storage**, keep **Host Path (Path that already exists on the system)** and then select the existing dataset you used for Postgres data storage, for example, `pgData`. - - Following the instructions in the [Multiple Datasets for Immich Storage](#additional-storage-advanced-users) section, you can add, **for each old dataset**, a new Additional Storage with the following settings: - - **Type**: `Host Path (Path that already exists on the system)` - - **Mount Path**: `/data/` (e.g. `/data/library`) - - **Host Path**: `/mnt//` (e.g. `/mnt/tank/immich/library`) - :::danger Ensure using the correct paths names - Make sure to replace `` with the actual name of the folder used by Immich: `library`, `upload`, `thumbs`, `profile`, `encoded-video`, and `backups`. Also, replace `` and `` with the actual names of your pool and dataset. - ::: - - **Read Only**: Keep it unticked as Immich needs to write to these datasets. - - Click **Update** at the bottom of the page to save changes. + - Go to the **Installed Applications** screen and select Immich from the list of installed applications. + - Click **Edit** on the **Application Info** widget. + - In the **Storage Configuration** section, untick the **Use Old Storage Configuration (Deprecated)** checkbox. + - For the **Data Storage**, you can keep the **ixVolume (dataset created automatically by the system)** as no data will be directly written to it. We recommend selecting **Host Path (Path that already exists on the system)** and then select a **new** dataset you created for Immich data storage, for example, `data`. + - For the **Postgres Data Storage**, keep **Host Path (Path that already exists on the system)** and then select the existing dataset you used for Postgres data storage, for example, `pgData`. + - Following the instructions in the [Multiple Datasets for Immich Storage](#additional-storage-advanced-users) section, you can add, **for each old dataset**, a new Additional Storage with the following settings: + - **Type**: `Host Path (Path that already exists on the system)` + - **Mount Path**: `/data/` (e.g. `/data/library`) + - **Host Path**: `/mnt//` (e.g. `/mnt/tank/immich/library`) + :::danger Ensure using the correct paths names + Make sure to replace `` with the actual name of the folder used by Immich: `library`, `upload`, `thumbs`, `profile`, `encoded-video`, and `backups`. Also, replace `` and `` with the actual names of your pool and dataset. + ::: + - **Read Only**: Keep it unticked as Immich needs to write to these datasets. + - Click **Update** at the bottom of the page to save changes. 4. **Start the Immich app** from the TrueNAS web interface. This will recreate the Immich container with the new storage configuration and start the app. If everything went well, you should now be able to access Immich with the new storage configuration. You can verify that the data is still available by checking the Immich web interface and ensuring that all your photos and videos are still accessible. diff --git a/docs/docs/install/unraid.md b/docs/docs/install/unraid.md index efb493f267..ca7263a1e8 100644 --- a/docs/docs/install/unraid.md +++ b/docs/docs/install/unraid.md @@ -125,13 +125,13 @@ alt="Go to Docker Tab and visit the address listed next to immich-web" :::tip -For more information on how to use the application once installed, please refer to the [Post Install](/docs/install/post-install.mdx) guide. +For more information on how to use the application once installed, please refer to the [Post Install](/install/post-install.mdx) guide. ::: ## Updating Steps :::danger -Make sure to read the general [upgrade instructions](/docs/install/upgrading.md). +Make sure to read the general [upgrade instructions](/install/upgrading.md). ::: Updating is extremely easy however it's important to be aware that containers managed via the Docker Compose Manager plugin do not integrate with Unraid's native dockerman UI, the label "_update ready_" will always be present on containers installed via the Docker Compose Manager. diff --git a/docs/docs/install/upgrading.md b/docs/docs/install/upgrading.md index d638a6f7d1..bf788cb680 100644 --- a/docs/docs/install/upgrading.md +++ b/docs/docs/install/upgrading.md @@ -4,9 +4,7 @@ sidebar_position: 95 # Upgrading -:::danger Read the release notes -Immich is currently under heavy development, which means you can expect [breaking changes][breaking] and bugs. You should read the release notes prior to updating and take special care when using automated tools like [Watchtower][watchtower]. - +:::tip Breaking changes You can see versions that had breaking changes [here][breaking]. ::: @@ -40,7 +38,7 @@ If you do not deploy Immich using Docker Compose and see a deprecation warning f Immich has migrated off of the deprecated pgvecto.rs database extension to its successor, [VectorChord](https://github.com/tensorchord/VectorChord), which comes with performance improvements in almost every aspect. This section will guide you on how to make this change in a Docker Compose setup. -Before making any changes, please [back up your database](/docs/administration/backup-and-restore). While every effort has been made to make this migration as smooth as possible, there’s always a chance that something can go wrong. +Before making any changes, please [back up your database](/administration/backup-and-restore). While every effort has been made to make this migration as smooth as possible, there’s always a chance that something can go wrong. After making a backup, please modify your `docker-compose.yml` file with the following information. @@ -89,7 +87,7 @@ After making a backup, please modify your `docker-compose.yml` file with the fol If you deviated from the defaults of pg14 or pgvectors0.2.0, you must adjust the pg major version and pgvecto.rs version. If you are still using the default `docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0` image, you can just follow the changes above. For example, if the previous image is `docker.io/tensorchord/pgvecto-rs:pg16-v0.3.0`, the new image should be `ghcr.io/immich-app/postgres:16-vectorchord0.3.0-pgvectors0.3.0` instead of the image specified in the diff. ::: -After making these changes, you can start Immich as normal. Immich will make some changes to the DB during startup, which can take seconds to minutes to finish, depending on hardware and library size. In particular, it’s normal for the server logs to be seemingly stuck at `Reindexing clip_index` and `Reindexing face_index`for some time if you have over 100k assets in Immich and/or Immich is on a relatively weak server. If you see these logs and there are no errors, just give it time. +After making these changes, you can start Immich as normal. Immich will make some changes to the DB during startup, which can take seconds to minutes to finish, depending on hardware and library size. In particular, it’s normal for the server logs to be seemingly stuck at `Reindexing clip_index` and `Reindexing face_index` for some time if you have over 100k assets in Immich and/or Immich is on a relatively weak server. If you see these logs and there are no errors, just give it time. :::danger After switching to VectorChord, you should not downgrade Immich below 1.133.0. @@ -101,7 +99,7 @@ Please don’t hesitate to contact us on [GitHub](https://github.com/immich-app/ #### I have a separate PostgreSQL instance shared with multiple services. How can I switch to VectorChord? -Please see the [standalone PostgreSQL documentation](/docs/administration/postgres-standalone#migrating-to-vectorchord) for migration instructions. The migration path will be different depending on whether you’re currently using pgvecto.rs or pgvector, as well as whether Immich has superuser DB permissions. +Please see the [standalone PostgreSQL documentation](/administration/postgres-standalone#migrating-to-vectorchord) for migration instructions. The migration path will be different depending on whether you’re currently using pgvecto.rs or pgvector, as well as whether Immich has superuser DB permissions. #### Why are so many lines removed from the `docker-compose.yml` file? Does this mean the health check is removed? diff --git a/docs/docs/overview/help.md b/docs/docs/overview/help.md index f38ecde168..e6523547fa 100644 --- a/docs/docs/overview/help.md +++ b/docs/docs/overview/help.md @@ -6,7 +6,7 @@ sidebar_position: 6 Running into an issue or have a question? Try the following: -1. Check the [FAQs](/docs/FAQ.mdx). +1. Check the [FAQs](/FAQ.mdx). 2. Read through the [Release Notes][github-releases]. 3. Search through existing [GitHub Issues][github-issues]. 4. Open a help ticket on [Discord][discord-link]. diff --git a/docs/docs/overview/img/social-preview-light.webp b/docs/docs/overview/img/social-preview-light.webp deleted file mode 100644 index 3d088f6522..0000000000 Binary files a/docs/docs/overview/img/social-preview-light.webp and /dev/null differ diff --git a/docs/docs/overview/quick-start.mdx b/docs/docs/overview/quick-start.mdx index 28cee15007..d80a194ad2 100644 --- a/docs/docs/overview/quick-start.mdx +++ b/docs/docs/overview/quick-start.mdx @@ -13,7 +13,7 @@ to install and use it. - A system with at least 4GB of RAM and 2 CPU cores. - [Docker](https://docs.docker.com/engine/install/) -> For a more detailed list of requirements, see the [requirements page](/docs/install/requirements). +> For a more detailed list of requirements, see the [requirements page](/install/requirements). --- @@ -61,7 +61,7 @@ import MobileAppBackup from '/docs/partials/_mobile-app-backup.md'; The backup time differs depending on how many photos are on your mobile device. Large uploads may take quite a while. -To quickly get going, you can selectively upload few photos first, by following this [guide](/docs/features/mobile-app#sync-only-selected-photos). +To quickly get going, you can selectively upload few photos first, by following this [guide](/features/mobile-app#sync-only-selected-photos). You can select the **Jobs** tab to see Immich processing your photos. @@ -72,7 +72,7 @@ You can select the **Jobs** tab to see Immich processing your photos. ## Review the database backup and restore process Immich has built-in database backups. You can refer to the -[database backup](/docs/administration/backup-and-restore) for more information. +[database backup](/administration/backup-and-restore) for more information. :::danger The database only contains metadata and user information. You must setup manual backups of the images and videos stored in `UPLOAD_LOCATION`. @@ -86,8 +86,8 @@ You may decide you'd like to install the server a different way; the Install cat You may decide you'd like to add the _rest_ of your photos from Google Photos, even those not on your mobile device, via Google Takeout. You can use [immich-go](https://github.com/simulot/immich-go) for this. -You may want to [upload photos from your own archive](/docs/features/command-line-interface). +You may want to [upload photos from your own archive](/features/command-line-interface). -You may want to incorporate a pre-existing archive of photos from an [External Library](/docs/features/libraries); there's a [guide](/docs/guides/external-library) for that. +You may want to incorporate a pre-existing archive of photos from an [External Library](/features/libraries); there's a [guide](/guides/external-library) for that. -You may want your mobile device to [back photos up to your server automatically](/docs/features/automatic-backup). +You may want your mobile device to [back photos up to your server automatically](/features/automatic-backup). diff --git a/docs/docs/overview/support-the-project.md b/docs/docs/overview/support-the-project.md index a439893a7e..ae24a3f1ce 100644 --- a/docs/docs/overview/support-the-project.md +++ b/docs/docs/overview/support-the-project.md @@ -10,11 +10,11 @@ By far the easiest way to help make Immich better it to use it and report issues ## Translations -Support the project by localizing on [Weblate](https://hosted.weblate.org/projects/immich/immich/). For more information, see the [Translations](/docs/developer/translations) section. +Support the project by localizing on [Weblate](https://hosted.weblate.org/projects/immich/immich/). For more information, see the [Translations](/developer/translations) section. ## Development -If you are a programmer or developer, take a look at Immich's [technology stack](/docs/developer/architecture.mdx) and consider fixing bugs or building new features. The team and I are always looking for new contributors. For information about how to contribute as a developer, see the [Developer](/docs/developer/architecture.mdx) section. +If you are a programmer or developer, take a look at Immich's [technology stack](/developer/architecture.mdx) and consider fixing bugs or building new features. The team and I are always looking for new contributors. For information about how to contribute as a developer, see the [Developer](/developer/architecture.mdx) section. ## Purchase Immich diff --git a/docs/docs/overview/welcome.mdx b/docs/docs/overview/welcome.mdx deleted file mode 100644 index 93ce705369..0000000000 --- a/docs/docs/overview/welcome.mdx +++ /dev/null @@ -1,27 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Welcome to Immich - -Immich - Self-hosted photos and videos backup tool - -## Welcome! - -Hello, I am glad you are here. - -My name is Alex. I am an Electrical Engineer by schooling, then turned into a Software Engineer by trade and the pure love of problem solving. - -We were lying in bed with our newborn, and my wife said, "We are starting to accumulate a lot of photos and videos of our baby, and I don't want to pay for **_App-Which-Must-Not-Be-Named_** anymore. You always want to build something for me, so why don't you build me an app which can do that?" - -That was how the idea started to grow in my head. After that, I began to find existing solutions in the self-hosting space with similar backup functionality and the performance level of the **_App-Which-Must-Not-Be-Named_**. I found that the current solutions mainly focus on the gallery-type application. However, I want a simple-to-use backup tool with a native mobile app that can view photos and videos efficiently. So I set sail on this journey as a hungry engineer on the hunt. - -Another motivation that pushed me to deliver my execution of the **_App-Which-Must-Not-Be-Named_** alternative or replacement is for contributing back to the open source community that I have greatly benefited from over the years. - -I'm proud to share this creation with you, which values privacy, memories, and the joy of looking back at those moments in an easy-to-use and friendly interface. - -If you like the application or it helps you in some way, please consider [supporting](./support-the-project.md) the project. It will help me to continue to develop and maintain the application. diff --git a/docs/docs/partials/_mobile-app-download.md b/docs/docs/partials/_mobile-app-download.md index 72a3053440..31cf62bf81 100644 --- a/docs/docs/partials/_mobile-app-download.md +++ b/docs/docs/partials/_mobile-app-download.md @@ -1,5 +1,6 @@ The mobile app can be downloaded from the following places: +- Obtainium: You can get your Obtainium config link from the [Utilities page of your Immich server](https://my.immich.app/utilities). - [Google Play Store](https://play.google.com/store/apps/details?id=app.alextran.immich) - [Apple App Store](https://apps.apple.com/us/app/immich/id1613945652) - [F-Droid](https://f-droid.org/packages/app.alextran.immich) diff --git a/docs/docs/partials/_server-backup.md b/docs/docs/partials/_server-backup.md index b9479600aa..34e09670e9 100644 --- a/docs/docs/partials/_server-backup.md +++ b/docs/docs/partials/_server-backup.md @@ -1,7 +1,6 @@ Now that you have imported some pictures, you should setup server backups to preserve your memories. -You can do so by following our [backup guide](/docs/administration/backup-and-restore.md). +You can do so by following our [backup guide](/administration/backup-and-restore.md). -:::danger -Immich is still under heavy development _and_ handles very important data. -It is essential that you set up good backups, and test them. +:::info +A 3-2-1 backup strategy is still crucial. The team has the responsibility to ensure that the application doesn’t cause loss of your precious memories; however, we cannot guarantee that hard drives will not fail, or an electrical event causes unexpected shutdown of your server/system, leading to data loss. Therefore, we still encourage users to follow best practices when safeguarding their data. Keep multiple copies of your most precious data: at least two local copies and one copy offsite in cold storage. ::: diff --git a/docs/docs/partials/_storage-template.md b/docs/docs/partials/_storage-template.md index 20e9caac43..84236e0ac1 100644 --- a/docs/docs/partials/_storage-template.md +++ b/docs/docs/partials/_storage-template.md @@ -1,7 +1,7 @@ -Immich allows the admin user to set the uploaded filename pattern at the directory and filename level as well as the [storage label for a user](/docs/administration/user-management/#set-storage-label-for-user). +Immich allows the admin user to set the uploaded filename pattern at the directory and filename level as well as the [storage label for a user](/administration/user-management/#set-storage-label-for-user). :::tip -You can read more about the differences between storage template engine on and off [here](/docs/administration/backup-and-restore#asset-types-and-storage-locations) +You can read more about the differences between storage template engine on and off [here](/administration/backup-and-restore#asset-types-and-storage-locations) ::: The admin user can set the template by using the template builder in the `Administration -> Settings -> Storage Template`. Immich provides a set of variables that you can use in constructing the template, along with additional custom text. If the template produces [multiple files with the same filename, they won't be overwritten](https://github.com/immich-app/immich/discussions/3324) as a sequence number is appended to the filename. diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index d612dda253..70e0189a00 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -7,7 +7,7 @@ const prism = require('prism-react-renderer'); const config = { title: 'Immich', tagline: 'High performance self-hosted photo and video backup solution directly from your mobile phone', - url: 'https://immich.app', + url: 'https://docs.immich.app', baseUrl: '/', onBrokenLinks: 'throw', onBrokenMarkdownLinks: 'warn', @@ -42,26 +42,19 @@ const config = { ], presets: [ [ - 'docusaurus-preset-openapi', - /** @type {import('docusaurus-preset-openapi').Options} */ + 'classic', + /** @type {import('@docusaurus/preset-classic').Options} */ ({ docs: { showLastUpdateAuthor: true, showLastUpdateTime: true, + routeBasePath: '/', sidebarPath: require.resolve('./sidebars.js'), // Please change this to your repo. // Remove this to remove the "edit this page" links. editUrl: 'https://github.com/immich-app/immich/tree/main/docs/', }, - api: { - path: '../open-api/immich-openapi-specs.json', - routeBasePath: '/docs/api', - }, - // blog: { - // showReadingTime: true, - // editUrl: "https://github.com/immich-app/immich/tree/main/docs/", - // }, theme: { customCss: require.resolve('./src/css/custom.css'), }, @@ -72,11 +65,6 @@ const config = { themeConfig: /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ ({ - announcementBar: { - id: 'site_announcement_immich', - content: `⚠️ The project is under very active development. Expect bugs and changes. Do not use it as the only way to store your photos and videos!`, - isCloseable: false, - }, docs: { sidebar: { autoCollapseCategories: false, @@ -95,17 +83,17 @@ const config = { position: 'right', }, { - to: '/docs/overview/welcome', + to: '/overview/quick-start', position: 'right', label: 'Docs', }, { - to: '/roadmap', + href: 'https://immich.app/roadmap', position: 'right', label: 'Roadmap', }, { - to: '/docs/api', + href: 'https://api.immich.app/', position: 'right', label: 'API', }, @@ -139,16 +127,16 @@ const config = { title: 'Overview', items: [ { - label: 'Welcome', - to: '/docs/overview/welcome', + label: 'Quick start', + to: '/overview/quick-start', }, { label: 'Installation', - to: '/docs/install/requirements', + to: '/install/requirements', }, { label: 'Contributing', - to: '/docs/overview/support-the-project', + to: '/overview/support-the-project', }, { label: 'Privacy Policy', @@ -161,15 +149,15 @@ const config = { items: [ { label: 'Roadmap', - to: '/roadmap', + href: 'https://immich.app/roadmap', }, { label: 'API', - to: '/docs/api', + href: 'https://api.immich.app/', }, { label: 'Cursed Knowledge', - to: '/cursed-knowledge', + href: 'https://immich.app/cursed-knowledge', }, ], }, diff --git a/docs/mise.toml b/docs/mise.toml new file mode 100644 index 0000000000..4ffb7d5cce --- /dev/null +++ b/docs/mise.toml @@ -0,0 +1,25 @@ +[tasks.install] +run = "pnpm install --filter documentation --frozen-lockfile" + +[tasks.start] +env._.path = "./node_modules/.bin" +run = "docusaurus --port 3005" + +[tasks.build] +env._.path = "./node_modules/.bin" +run = [ + "jq -c < ../open-api/immich-openapi-specs.json > ./static/openapi.json || exit 0", + "docusaurus build", +] + +[tasks.preview] +env._.path = "./node_modules/.bin" +run = "docusaurus serve" + +[tasks.format] +env._.path = "./node_modules/.bin" +run = "prettier --check ." + +[tasks."format-fix"] +env._.path = "./node_modules/.bin" +run = "prettier --write ." diff --git a/docs/package.json b/docs/package.json index 1a1dbcf84c..a7c958351c 100644 --- a/docs/package.json +++ b/docs/package.json @@ -17,15 +17,14 @@ "write-heading-ids": "docusaurus write-heading-ids" }, "dependencies": { - "@docusaurus/core": "~3.8.0", - "@docusaurus/preset-classic": "~3.8.0", - "@docusaurus/theme-common": "~3.8.0", + "@docusaurus/core": "~3.9.0", + "@docusaurus/preset-classic": "~3.9.0", + "@docusaurus/theme-common": "~3.9.0", "@mdi/js": "^7.3.67", "@mdi/react": "^1.6.1", "@mdx-js/react": "^3.0.0", "autoprefixer": "^10.4.17", "docusaurus-lunr-search": "^3.3.2", - "docusaurus-preset-openapi": "^0.7.5", "lunr": "^2.3.9", "postcss": "^8.4.25", "prism-react-renderer": "^2.3.1", @@ -36,7 +35,7 @@ "url": "^0.11.0" }, "devDependencies": { - "@docusaurus/module-type-aliases": "~3.8.0", + "@docusaurus/module-type-aliases": "~3.9.0", "@docusaurus/tsconfig": "^3.7.0", "@docusaurus/types": "^3.7.0", "prettier": "^3.2.4", @@ -58,6 +57,6 @@ "node": ">=20" }, "volta": { - "node": "22.19.0" + "node": "24.11.0" } } diff --git a/docs/src/components/community-guides.tsx b/docs/src/components/community-guides.tsx deleted file mode 100644 index 08c8e096d9..0000000000 --- a/docs/src/components/community-guides.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import Link from '@docusaurus/Link'; -import React from 'react'; - -interface CommunityGuidesProps { - title: string; - description: string; - url: string; -} - -const guides: CommunityGuidesProps[] = [ - { - title: 'Cloudflare Tunnels with SSO/OAuth', - description: `Setting up Cloudflare Tunnels and a SaaS App for Immich.`, - url: 'https://github.com/immich-app/immich/discussions/8299', - }, - { - title: 'Database backup in TrueNAS', - description: `Create a database backup with pgAdmin in TrueNAS.`, - url: 'https://github.com/immich-app/immich/discussions/8809', - }, - { - title: 'Unraid backup scripts', - description: `Back up your assets in Unraid with a pre-prepared script.`, - url: 'https://github.com/immich-app/immich/discussions/8416', - }, - { - title: 'Sync folders with albums', - description: `synchronize folders in imported library with albums having the folders name.`, - url: 'https://github.com/immich-app/immich/discussions/3382', - }, - { - title: 'Immich Podman Quadlets Handbook', - description: - 'A rewrite of the original Immich Docker Compose file using Podman Quadlets, with a set of extra guides in the repository’s wiki.', - url: 'https://github.com/linux-universe/immich-podman-quadlets/blob/main/README.md', - }, - { - title: 'Podman/Quadlets Install', - description: 'Documentation for simple podman setup using quadlets.', - url: 'https://github.com/tbelway/immich-podman-quadlets/blob/main/docs/install/podman-quadlet.md', - }, - { - title: 'Google Photos import + albums', - description: 'Import your Google Photos files into Immich and add your albums.', - url: 'https://github.com/immich-app/immich/discussions/1340', - }, - { - title: 'Access Immich with custom domain', - description: 'Access your local Immich installation over the internet using your own domain.', - url: 'https://github.com/ppr88/immich-guides/blob/main/open-immich-custom-domain.md', - }, - { - title: 'Nginx caching map server', - description: 'Increase privacy by using nginx as a caching proxy in front of a map tile server.', - url: 'https://github.com/pcouy/pcouy.github.io/blob/main/_posts/2024-08-30-proxying-a-map-tile-server-for-increased-privacy.md', - }, - { - title: 'fail2ban setup instructions', - description: 'How to configure an existing fail2ban installation to block incorrect login attempts.', - url: 'https://github.com/immich-app/immich/discussions/3243#discussioncomment-6681948', - }, - { - title: 'Immich remote access with NordVPN Meshnet', - description: 'Access Immich with an end-to-end encrypted connection.', - url: 'https://meshnet.nordvpn.com/how-to/remote-files-media-access/immich-remote-access', - }, - { - title: 'Trust Self Signed Certificates with Immich - OAuth Setup', - description: - 'Set up Certificate Authority trust with Immich, and your private OAuth2/OpenID service, while using a private CA for HTTPS commication.', - url: 'https://github.com/immich-app/immich/discussions/18614', - }, -]; - -function CommunityGuide({ title, description, url }: CommunityGuidesProps): JSX.Element { - return ( -
-
-

- {title} -

- -

{description}

-

- {url} -

-
-
- - View Guide - -
-
- ); -} - -export default function CommunityGuides(): JSX.Element { - return ( -
- {guides.map((guides) => ( - - ))} -
- ); -} diff --git a/docs/src/components/community-projects.tsx b/docs/src/components/community-projects.tsx deleted file mode 100644 index efce831df0..0000000000 --- a/docs/src/components/community-projects.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import Link from '@docusaurus/Link'; -import React from 'react'; - -interface CommunityProjectProps { - title: string; - description: string; - url: string; -} - -const projects: CommunityProjectProps[] = [ - { - title: 'immich-go', - description: `An alternative to the immich-CLI that doesn't depend on nodejs. It specializes in importing Google Photos Takeout archives.`, - url: 'https://github.com/simulot/immich-go', - }, - { - title: 'ImmichFrame', - description: 'Run an Immich slideshow in a photo frame.', - url: 'https://github.com/3rob3/ImmichFrame', - }, - { - title: 'API Album Sync', - description: 'A Python script to sync folders as albums.', - url: 'https://git.orenit.solutions/open/immichalbumpull', - }, - { - title: 'Remove offline files', - description: 'A simple way to remove orphaned offline assets from the Immich database', - url: 'https://github.com/Thoroslives/immich_remove_offline_files', - }, - { - title: 'Immich-Tools', - description: 'Provides scripts for handling problems on the repair page.', - url: 'https://github.com/clumsyCoder00/Immich-Tools', - }, - { - title: 'Lightroom Publisher: mi.Immich.Publisher', - description: 'Lightroom plugin to publish photos from Lightroom collections to Immich albums.', - url: 'https://github.com/midzelis/mi.Immich.Publisher', - }, - { - title: 'Lightroom Immich Plugin: lrc-immich-plugin', - description: - 'Lightroom plugin to publish, export photos from Lightroom to Immich. Import from Immich to Lightroom is also supported.', - url: 'https://blog.fokuspunk.de/lrc-immich-plugin/', - }, - { - title: 'Immich-Tiktok-Remover', - description: 'Script to search for and remove TikTok videos from your Immich library.', - url: 'https://github.com/mxc2/immich-tiktok-remover', - }, - { - title: 'Immich Android TV', - description: 'Unofficial Immich Android TV app.', - url: 'https://github.com/giejay/Immich-Android-TV', - }, - { - title: 'Create albums from folders', - description: 'A Python script to create albums based on the folder structure of an external library.', - url: 'https://github.com/Salvoxia/immich-folder-album-creator', - }, - { - title: 'Powershell Module PSImmich', - description: 'Powershell Module for the Immich API', - url: 'https://github.com/hanpq/PSImmich', - }, - { - title: 'Immich Distribution', - description: 'Snap package for easy install and zero-care auto updates of Immich. Self-hosted photo management.', - url: 'https://immich-distribution.nsg.cc', - }, - { - title: 'Immich Kiosk', - description: 'Lightweight slideshow to run on kiosk devices and browsers.', - url: 'https://github.com/damongolding/immich-kiosk', - }, - { - title: 'Immich Power Tools', - description: 'Power tools for organizing your immich library.', - url: 'https://github.com/varun-raj/immich-power-tools', - }, - { - title: 'Immich Public Proxy', - description: - 'Share your Immich photos and albums in a safe way without exposing your Immich instance to the public.', - url: 'https://github.com/alangrainger/immich-public-proxy', - }, - { - title: 'Immich Kodi', - description: 'Unofficial Kodi plugin for Immich.', - url: 'https://github.com/vladd11/immich-kodi', - }, - { - title: 'Immich Downloader', - description: 'Downloads a configurable number of random photos based on people or album ID.', - url: 'https://github.com/jon6fingrs/immich-dl', - }, - { - title: 'Immich Upload Optimizer', - description: 'Automatically optimize files uploaded to Immich in order to save storage space', - url: 'https://github.com/miguelangel-nubla/immich-upload-optimizer', - }, - { - title: 'Immich Machine Learning Load Balancer', - description: 'Speed up your machine learning by load balancing your requests to multiple computers', - url: 'https://github.com/apetersson/immich_ml_balancer', - }, - { - title: 'Immich Drop Uploader', - description: 'A tiny, zero-login web app for collecting photos/videos from anyone into your Immich server.', - url: 'https://github.com/Nasogaa/immich-drop', - }, - { - title: 'Immich Birthday Sync', - description: 'Bulk-upload and -download birthdays, with CardDAV sync support', - url: 'https://github.com/sid3windr/immich-birthday', - }, - { - title: 'Immich Stack', - description: 'Auto-stack photos with identical filenames and differing extensions (i.e. JPG+RAW)', - url: 'https://github.com/sid3windr/immich-stack', - }, -]; - -function CommunityProject({ title, description, url }: CommunityProjectProps): JSX.Element { - return ( -
-
-

- {title} -

- -

{description}

-

- {url} -

-
-
- - View Link - -
-
- ); -} - -export default function CommunityProjects(): JSX.Element { - return ( -
- {projects.map((project) => ( - - ))} -
- ); -} diff --git a/docs/src/components/svg-paths.ts b/docs/src/components/svg-paths.ts deleted file mode 100644 index 0903392307..0000000000 --- a/docs/src/components/svg-paths.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const discordPath = - 'M81.15,0c-1.2376,2.1973-2.3489,4.4704-3.3591,6.794-9.5975-1.4396-19.3718-1.4396-28.9945,0-.985-2.3236-2.1216-4.5967-3.3591-6.794-9.0166,1.5407-17.8059,4.2431-26.1405,8.0568C2.779,32.5304-1.6914,56.3725.5312,79.8863c9.6732,7.1476,20.5083,12.603,32.0505,16.0884,2.6014-3.4854,4.8998-7.1981,6.8698-11.0623-3.738-1.3891-7.3497-3.1318-10.8098-5.1523.9092-.6567,1.7932-1.3386,2.6519-1.9953,20.281,9.547,43.7696,9.547,64.0758,0,.8587.7072,1.7427,1.3891,2.6519,1.9953-3.4601,2.0457-7.0718,3.7632-10.835,5.1776,1.97,3.8642,4.2683,7.5769,6.8698,11.0623,11.5419-3.4854,22.3769-8.9156,32.0509-16.0631,2.626-27.2771-4.496-50.9172-18.817-71.8548C98.9811,4.2684,90.1918,1.5659,81.1752.0505l-.0252-.0505ZM42.2802,65.4144c-6.2383,0-11.4159-5.6575-11.4159-12.6535s4.9755-12.6788,11.3907-12.6788,11.5169,5.708,11.4159,12.6788c-.101,6.9708-5.026,12.6535-11.3907,12.6535ZM84.3576,65.4144c-6.2637,0-11.3907-5.6575-11.3907-12.6535s4.9755-12.6788,11.3907-12.6788,11.4917,5.708,11.3906,12.6788c-.101,6.9708-5.026,12.6535-11.3906,12.6535Z'; -export const discordViewBox = '0 0 126.644 96'; diff --git a/docs/src/components/version-switcher.tsx b/docs/src/components/version-switcher.tsx index 5cb23891aa..739d7bd001 100644 --- a/docs/src/components/version-switcher.tsx +++ b/docs/src/components/version-switcher.tsx @@ -11,7 +11,7 @@ export default function VersionSwitcher(): JSX.Element { useEffect(() => { async function getVersions() { try { - let baseUrl = 'https://immich.app'; + let baseUrl = 'https://docs.immich.app'; if (window.location.origin === 'http://localhost:3005') { baseUrl = window.location.origin; } @@ -21,12 +21,13 @@ export default function VersionSwitcher(): JSX.Element { const archiveVersions = await response.json(); const allVersions = [ - { label: 'Next', url: 'https://main.preview.immich.app' }, - { label: 'Latest', url: 'https://immich.app' }, + { label: 'Next', url: 'https://docs.main.preview.immich.app' }, + { label: 'Latest', url: 'https://docs.immich.app' }, ...archiveVersions, - ].map(({ label, url }) => ({ + ].map(({ label, url, rootPath }) => ({ label, url: new URL(url), + rootPath, })); setVersions(allVersions); @@ -50,12 +51,18 @@ export default function VersionSwitcher(): JSX.Element { className="version-switcher-34ab39" label={activeLabel} mobile={windowSize === 'mobile'} - items={versions.map(({ label, url }) => ({ - label, - to: new URL(location.pathname + location.search + location.hash, url).href, - target: '_self', - className: label === activeLabel ? 'dropdown__link--active menu__link--active' : '', // workaround because React Router `` only supports using URL path for checking if active: https://v5.reactrouter.com/web/api/NavLink/isactive-func - }))} + items={versions.map(({ label, url, rootPath }) => { + let path = location.pathname + location.search + location.hash; + if (rootPath && !path.startsWith(rootPath)) { + path = rootPath + path; + } + return { + label, + to: new URL(path, url).href, + target: '_self', + className: label === activeLabel ? 'dropdown__link--active menu__link--active' : '', // workaround because React Router `` only supports using URL path for checking if active: https://v5.reactrouter.com/web/api/NavLink/isactive-func + }; + })} /> ) ); diff --git a/docs/src/pages/cursed-knowledge.tsx b/docs/src/pages/cursed-knowledge.tsx deleted file mode 100644 index f3dacc2ce6..0000000000 --- a/docs/src/pages/cursed-knowledge.tsx +++ /dev/null @@ -1,273 +0,0 @@ -import { - mdiBug, - mdiCalendarToday, - mdiCrosshairsOff, - mdiCrop, - mdiDatabase, - mdiLeadPencil, - mdiLockOff, - mdiLockOutline, - mdiMicrosoftWindows, - mdiSecurity, - mdiSpeedometerSlow, - mdiTrashCan, - mdiWeb, - mdiWrap, - mdiCloudKeyOutline, - mdiRegex, - mdiCodeJson, - mdiClockOutline, - mdiAccountOutline, - mdiRestart, -} from '@mdi/js'; -import Layout from '@theme/Layout'; -import React from 'react'; -import { Timeline, Item as TimelineItem } from '../components/timeline'; - -const withLanguage = (date: Date) => (language: string) => date.toLocaleDateString(language); - -type Item = Omit & { date: Date }; - -const items: Item[] = [ - { - icon: mdiClockOutline, - iconColor: 'gray', - title: 'setTimeout is cursed', - description: - 'The setTimeout method in JavaScript is cursed when used with small values because the implementation may or may not actually wait the specified time.', - link: { - url: 'https://github.com/immich-app/immich/pull/20655', - text: '#20655', - }, - date: new Date(2025, 7, 4), - }, - { - icon: mdiAccountOutline, - iconColor: '#DAB1DA', - title: 'PostgreSQL USER is cursed', - description: - 'The USER keyword in PostgreSQL is cursed because you can select from it like a table, which leads to confusion if you have a table name user as well.', - link: { - url: 'https://github.com/immich-app/immich/pull/19891', - text: '#19891', - }, - date: new Date(2025, 7, 4), - }, - { - icon: mdiRestart, - iconColor: '#8395e3', - title: 'PostgreSQL RESET is cursed', - description: - 'PostgreSQL RESET is cursed because it is impossible to RESET a PostgreSQL extension parameter if the extension has been uninstalled.', - link: { - url: 'https://github.com/immich-app/immich/pull/19363', - text: '#19363', - }, - date: new Date(2025, 5, 20), - }, - { - icon: mdiRegex, - iconColor: 'purple', - title: 'Zitadel Actions are cursed', - description: - "Zitadel is cursed because its custom scripting feature is executed with a JS engine that doesn't support regex named capture groups.", - link: { - url: 'https://github.com/dop251/goja', - text: 'Go JS engine', - }, - date: new Date(2025, 5, 4), - }, - { - icon: mdiCloudKeyOutline, - iconColor: '#0078d4', - title: 'Entra is cursed', - description: - "Microsoft Entra supports PKCE, but doesn't include it in its OpenID discovery document. This leads to clients thinking PKCE isn't available.", - link: { - url: 'https://github.com/immich-app/immich/pull/18725', - text: '#18725', - }, - date: new Date(2025, 4, 30), - }, - { - icon: mdiCrop, - iconColor: 'tomato', - title: 'Image dimensions in EXIF metadata are cursed', - description: - 'The dimensions in EXIF metadata can be different from the actual dimensions of the image, causing issues with cropping and resizing.', - link: { - url: 'https://github.com/immich-app/immich/pull/17974', - text: '#17974', - }, - date: new Date(2025, 4, 5), - }, - { - icon: mdiCodeJson, - iconColor: 'yellow', - title: 'YAML whitespace is cursed', - description: 'YAML whitespaces are often handled in unintuitive ways.', - link: { - url: 'https://github.com/immich-app/immich/pull/17309', - text: '#17309', - }, - date: new Date(2025, 3, 1), - }, - { - icon: mdiMicrosoftWindows, - iconColor: '#357EC7', - title: 'Hidden files in Windows are cursed', - description: - 'Hidden files in Windows cannot be opened with the "w" flag. That, combined with SMB option "hide dot files" leads to a lot of confusion.', - link: { - url: 'https://github.com/immich-app/immich/pull/12812', - text: '#12812', - }, - date: new Date(2024, 8, 20), - }, - { - icon: mdiWrap, - iconColor: 'gray', - title: 'Carriage returns in bash scripts are cursed', - description: 'Git can be configured to automatically convert LF to CRLF on checkout and CRLF breaks bash scripts.', - link: { - url: 'https://github.com/immich-app/immich/pull/11613', - text: '#11613', - }, - date: new Date(2024, 7, 7), - }, - { - icon: mdiLockOff, - iconColor: 'red', - title: 'Fetch inside Cloudflare Workers is cursed', - description: - 'Fetch requests in Cloudflare Workers use http by default, even if you explicitly specify https, which can often cause redirect loops.', - link: { - url: 'https://community.cloudflare.com/t/does-cloudflare-worker-allow-secure-https-connection-to-fetch-even-on-flexible-ssl/68051/5', - text: 'Cloudflare', - }, - date: new Date(2024, 7, 7), - }, - { - icon: mdiCrosshairsOff, - iconColor: 'gray', - title: 'GPS sharing on mobile is cursed', - description: - 'Some phones will silently strip GPS data from images when apps without location permission try to access them.', - link: { - url: 'https://github.com/immich-app/immich/discussions/11268', - text: '#11268', - }, - date: new Date(2024, 6, 21), - }, - { - icon: mdiLeadPencil, - iconColor: 'gold', - title: 'PostgreSQL NOTIFY is cursed', - description: - 'PostgreSQL does everything in a transaction, including NOTIFY. This means using the socket.io postgres-adapter writes to WAL every 5 seconds.', - link: { url: 'https://github.com/immich-app/immich/pull/10801', text: '#10801' }, - date: new Date(2024, 6, 3), - }, - { - icon: mdiWeb, - iconColor: 'lightskyblue', - title: 'npm scripts are cursed', - description: - 'npm scripts make a http call to the npm registry each time they run, which means they are a terrible way to execute a health check.', - link: { url: 'https://github.com/immich-app/immich/issues/10796', text: '#10796' }, - date: new Date(2024, 6, 3), - }, - { - icon: mdiSpeedometerSlow, - iconColor: 'brown', - title: '50 extra packages are cursed', - description: - 'There is a user in the JavaScript community who goes around adding "backwards compatibility" to projects. They do this by adding 50 extra package dependencies to your project, which are maintained by them.', - link: { url: 'https://github.com/immich-app/immich/pull/10690', text: '#10690' }, - date: new Date(2024, 5, 28), - }, - { - icon: mdiLockOutline, - iconColor: 'gold', - title: 'Long passwords are cursed', - description: - 'The bcrypt implementation only uses the first 72 bytes of a string. Any characters after that are ignored.', - // link: GHSA-4p64-9f7h-3432 - date: new Date(2024, 5, 25), - }, - { - icon: mdiCalendarToday, - iconColor: 'greenyellow', - title: 'JavaScript Date objects are cursed', - description: 'JavaScript date objects are 1 indexed for years and days, but 0 indexed for months.', - link: { url: 'https://github.com/immich-app/immich/pull/6787', text: '#6787' }, - date: new Date(2024, 0, 31), - }, - { - icon: mdiBug, - iconColor: 'green', - title: 'ESM imports are cursed', - description: - 'Prior to Node.js v20.8 using --experimental-vm-modules in a CommonJS project that imported an ES module that imported a CommonJS modules would create a segfault and crash Node.js', - link: { - url: 'https://github.com/immich-app/immich/pull/6719', - text: '#6179', - }, - date: new Date(2024, 0, 9), - }, - { - icon: mdiDatabase, - iconColor: 'gray', - title: 'PostgreSQL parameters are cursed', - description: `PostgresSQL has a limit of ${Number(65535).toLocaleString()} parameters, so bulk inserts can fail with large datasets.`, - link: { - url: 'https://github.com/immich-app/immich/pull/6034', - text: '#6034', - }, - date: new Date(2023, 11, 28), - }, - { - icon: mdiSecurity, - iconColor: 'gold', - title: 'Secure contexts are cursed', - description: `Some web features like the clipboard API only work in "secure contexts" (ie. https or localhost)`, - link: { - url: 'https://github.com/immich-app/immich/issues/2981', - text: '#2981', - }, - date: new Date(2023, 5, 26), - }, - { - icon: mdiTrashCan, - iconColor: 'gray', - title: 'TypeORM deletes are cursed', - description: `The remove implementation in TypeORM mutates the input, deleting the id property from the original object.`, - link: { - url: 'https://github.com/typeorm/typeorm/issues/7024#issuecomment-948519328', - text: 'typeorm#6034', - }, - date: new Date(2023, 1, 23), - }, -]; - -export default function CursedKnowledgePage(): JSX.Element { - return ( - -
-

- Cursed Knowledge -

-

- Cursed knowledge we have learned as a result of building Immich that we wish we never knew. -

-
- b.date.getTime() - a.date.getTime()) - .map((item) => ({ ...item, getDateLabel: withLanguage(item.date) }))} - /> -
-
-
- ); -} diff --git a/docs/src/pages/index.tsx b/docs/src/pages/index.tsx index 277a1d0b46..37455cde16 100644 --- a/docs/src/pages/index.tsx +++ b/docs/src/pages/index.tsx @@ -1,123 +1,5 @@ -import React from 'react'; -import Link from '@docusaurus/Link'; -import Layout from '@theme/Layout'; -import { discordPath, discordViewBox } from '@site/src/components/svg-paths'; -import ThemedImage from '@theme/ThemedImage'; -import Icon from '@mdi/react'; - -function HomepageHeader() { - return ( -
-
- Immich logo -
-
-
- - - - -
-

- Self-hosted{' '} - - photo and - video management{' '} - - solution -

- -

- Easily back up, organize, and manage your photos on your own server. Immich helps you - browse, search and organize your photos and videos with ease, without - sacrificing your privacy. -

-
-
- - Get Started - - - - Open Demo - -
- -
- - Join our Discord -
- -
-
-
- -
-

Download the mobile app

-

- Download the Immich app and start backing up your photos and videos securely to your own server -

-
-
-
- - Get it on Google Play - -
- -
- - Download on the App Store - -
- -
- - Download APK - -
-
- -
-
- ); -} +import { Redirect } from '@docusaurus/router'; export default function Home(): JSX.Element { - return ( - - -
-

This project is available under GNU AGPL v3 license.

-

Privacy should not be a luxury

-
-
- ); + return ; } diff --git a/docs/src/pages/roadmap.tsx b/docs/src/pages/roadmap.tsx deleted file mode 100644 index e002c4d032..0000000000 --- a/docs/src/pages/roadmap.tsx +++ /dev/null @@ -1,944 +0,0 @@ -import { - mdiAccountGroup, - mdiAccountGroupOutline, - mdiAndroid, - mdiAppleIos, - mdiArchiveOutline, - mdiBash, - mdiBookSearchOutline, - mdiBookmark, - mdiCakeVariant, - mdiCameraBurst, - mdiChartBoxMultipleOutline, - mdiCheckAll, - mdiCheckboxMarked, - mdiCloudUploadOutline, - mdiCollage, - mdiContentDuplicate, - mdiCrop, - mdiDevices, - mdiEmailOutline, - mdiExpansionCard, - mdiEyeOutline, - mdiEyeRefreshOutline, - mdiFaceMan, - mdiFaceManOutline, - mdiFile, - mdiFileSearch, - mdiFlash, - mdiFolder, - mdiFolderMultiple, - mdiForum, - mdiHandshakeOutline, - mdiHeart, - mdiHistory, - mdiImage, - mdiImageAlbum, - mdiImageEdit, - mdiImageMultipleOutline, - mdiImageSearch, - mdiKeyboardSettingsOutline, - mdiLicense, - mdiLockOutline, - mdiMagnify, - mdiMagnifyScan, - mdiMap, - mdiMaterialDesign, - mdiMatrix, - mdiMerge, - mdiMonitor, - mdiMotionPlayOutline, - mdiPalette, - mdiPanVertical, - mdiPartyPopper, - mdiPencil, - mdiRaw, - mdiRocketLaunch, - mdiRotate360, - mdiScaleBalance, - mdiSecurity, - mdiServer, - mdiShare, - mdiShareAll, - mdiShareCircle, - mdiStar, - mdiStarOutline, - mdiTableKey, - mdiTag, - mdiTagMultiple, - mdiText, - mdiThemeLightDark, - mdiTrashCanOutline, - mdiVectorCombine, - mdiFolderSync, - mdiFaceRecognition, - mdiVideo, - mdiWeb, - mdiDatabaseOutline, - mdiLinkEdit, - mdiTagFaces, - mdiMovieOpenPlayOutline, - mdiCast, -} from '@mdi/js'; -import Layout from '@theme/Layout'; -import React from 'react'; -import { Item, Timeline } from '../components/timeline'; - -const releases = { - 'v1.135.0': new Date(2025, 5, 18), - 'v1.133.0': new Date(2025, 4, 21), - 'v1.130.0': new Date(2025, 2, 25), - 'v1.127.0': new Date(2025, 1, 26), - 'v1.122.0': new Date(2024, 11, 5), - 'v1.120.0': new Date(2024, 10, 6), - 'v1.114.0': new Date(2024, 8, 6), - 'v1.113.0': new Date(2024, 7, 30), - 'v1.112.0': new Date(2024, 7, 14), - 'v1.111.0': new Date(2024, 6, 26), - 'v1.110.0': new Date(2024, 5, 11), - 'v1.109.0': new Date(2024, 6, 18), - 'v1.106.1': new Date(2024, 5, 11), - 'v1.104.0': new Date(2024, 4, 13), - 'v1.103.0': new Date(2024, 3, 29), - 'v1.102.0': new Date(2024, 3, 15), - 'v1.99.0': new Date(2024, 2, 20), - 'v1.98.0': new Date(2024, 2, 7), - 'v1.95.0': new Date(2024, 1, 20), - 'v1.94.0': new Date(2024, 0, 31), - 'v1.93.0': new Date(2024, 0, 19), - 'v1.91.0': new Date(2023, 11, 15), - 'v1.90.0': new Date(2023, 11, 7), - 'v1.88.0': new Date(2023, 10, 20), - 'v1.84.0': new Date(2023, 10, 1), - 'v1.83.0': new Date(2023, 9, 28), - 'v1.82.0': new Date(2023, 9, 17), - 'v1.79.0': new Date(2023, 8, 21), - 'v1.76.0': new Date(2023, 7, 29), - 'v1.75.0': new Date(2023, 7, 26), - 'v1.72.0': new Date(2023, 7, 6), - 'v1.71.0': new Date(2023, 6, 29), - 'v1.69.0': new Date(2023, 6, 23), - 'v1.68.0': new Date(2023, 6, 20), - 'v1.67.0': new Date(2023, 6, 14), - 'v1.66.0': new Date(2023, 6, 4), - 'v1.65.0': new Date(2023, 5, 30), - 'v1.63.0': new Date(2023, 5, 24), - 'v1.61.0': new Date(2023, 5, 16), - 'v1.58.0': new Date(2023, 4, 28), - 'v1.57.0': new Date(2023, 4, 23), - 'v1.56.0': new Date(2023, 4, 18), - 'v1.55.0': new Date(2023, 4, 9), - 'v1.54.0': new Date(2023, 3, 18), - 'v1.52.0': new Date(2023, 2, 29), - 'v1.51.0': new Date(2023, 2, 20), - 'v1.48.0': new Date(2023, 1, 21), - 'v1.47.0': new Date(2023, 1, 13), - 'v1.46.0': new Date(2023, 1, 9), - 'v1.43.0': new Date(2023, 1, 3), - 'v1.41.0': new Date(2023, 0, 10), - 'v1.39.0': new Date(2022, 11, 19), - 'v1.36.0': new Date(2022, 10, 20), - 'v1.33.1': new Date(2022, 9, 26), - 'v1.32.0': new Date(2022, 9, 14), - 'v1.27.0': new Date(2022, 8, 6), - 'v1.24.0': new Date(2022, 7, 19), - 'v1.10.0': new Date(2022, 4, 29), - 'v1.7.0': new Date(2022, 3, 24), - 'v1.3.0': new Date(2022, 2, 22), - 'v1.2.0': new Date(2022, 1, 8), -} as const; - -const weirdTags = { - 'v1.41.0': 'v1.41.1_64-dev', - 'v1.39.0': 'v1.39.0_61-dev', - 'v1.36.0': 'v1.36.0_55-dev', - 'v1.33.1': 'v1.33.0_52-dev', - 'v1.32.0': 'v1.32.0_50-dev', - 'v1.27.0': 'v1.27.0_37-dev', - 'v1.24.0': 'v1.24.0_34-dev', - 'v1.10.0': 'v1.10.0_15-dev', - 'v1.7.0': 'v1.7.0_11-dev ', - 'v1.3.0': 'v1.3.0-dev ', - 'v1.2.0': 'v0.2-dev ', -}; - -const title = 'Roadmap'; -const description = 'A list of future plans and goals, as well as past achievements and milestones.'; - -const withLanguage = (date: Date) => (language: string) => date.toLocaleDateString(language); - -type Base = { icon: string; iconColor?: React.CSSProperties['color']; title: string; description: string }; -const withRelease = ({ - icon, - iconColor, - title, - description, - release: version, -}: Base & { release: keyof typeof releases }) => { - return { - icon, - iconColor: iconColor ?? 'gray', - title, - description, - link: { - url: `https://github.com/immich-app/immich/releases/tag/${weirdTags[version] ?? version}`, - text: version, - }, - getDateLabel: withLanguage(releases[version]), - }; -}; - -const roadmap: Item[] = [ - { - done: false, - icon: mdiFlash, - iconColor: 'gold', - title: 'Workflows', - description: 'Automate tasks with workflows', - getDateLabel: () => 'Planned for 2025', - }, - { - done: false, - icon: mdiImageEdit, - iconColor: 'rebeccapurple', - title: 'Basic editor', - description: 'Basic photo editing capabilities', - getDateLabel: () => 'Planned for 2025', - }, - { - done: false, - icon: mdiRocketLaunch, - iconColor: 'indianred', - title: 'Stable release', - description: 'Immich goes stable', - getDateLabel: () => 'Planned for 2025', - }, - { - done: false, - icon: mdiCloudUploadOutline, - iconColor: 'cornflowerblue', - title: 'Better background backups', - description: 'Rework background backups to be more reliable', - getDateLabel: () => 'Planned for 2025', - }, - { - done: false, - icon: mdiCameraBurst, - iconColor: 'rebeccapurple', - title: 'Auto stacking', - description: 'Auto stack burst photos', - getDateLabel: () => 'Planned for 2025', - }, -]; - -const milestones: Item[] = [ - { - icon: mdiStar, - iconColor: 'gold', - title: '70,000 Stars', - description: 'Reached 70K Stars on GitHub!', - getDateLabel: withLanguage(new Date(2025, 6, 9)), - }, - withRelease({ - icon: mdiTableKey, - iconColor: 'gray', - title: 'Fine grained access controls', - description: 'Granular access controls for api keys', - release: 'v1.135.0', - }), - withRelease({ - icon: mdiCast, - iconColor: 'aqua', - title: 'Google Cast (web and mobile)', - description: 'Cast assets to Google Cast/Chromecast compatible devices', - release: 'v1.135.0', - }), - withRelease({ - icon: mdiLockOutline, - iconColor: 'sandybrown', - title: 'Private/locked photos', - description: 'Private assets with extra protections', - release: 'v1.133.0', - }), - withRelease({ - icon: mdiFolderMultiple, - iconColor: 'brown', - title: 'Folders view in the mobile app', - description: 'Browse your photos and videos in their folder structure inside the mobile app', - release: 'v1.130.0', - }), - { - icon: mdiStar, - iconColor: 'gold', - title: '60,000 Stars', - description: 'Reached 60K Stars on GitHub!', - getDateLabel: withLanguage(new Date(2025, 2, 4)), - }, - withRelease({ - icon: mdiTagFaces, - iconColor: 'teal', - title: 'Manual face tagging', - description: - 'Manually tag or remove faces in photos and videos, even when automatic detection misses or misidentifies them.', - release: 'v1.127.0', - }), - withRelease({ - icon: mdiLinkEdit, - iconColor: 'crimson', - title: 'Automatic URL switching', - description: 'The mobile app now supports automatic switching between different server URLs', - release: 'v1.122.0', - }), - withRelease({ - icon: mdiMovieOpenPlayOutline, - iconColor: 'darksalmon', - title: 'Native video player', - description: 'HDR videos are now fully supported using the Immich native video player', - release: 'v1.122.0', - }), - withRelease({ - icon: mdiDatabaseOutline, - iconColor: 'brown', - title: 'Automatic database dumps', - description: 'Database dumps are now integrated into the Immich server', - release: 'v1.120.0', - }), - { - icon: mdiStar, - iconColor: 'gold', - title: '50,000 Stars', - description: 'Reached 50K Stars on GitHub!', - getDateLabel: withLanguage(new Date(2024, 10, 1)), - }, - withRelease({ - icon: mdiFaceRecognition, - title: 'Metadata Face Import', - description: 'Read face metadata in Digikam format during import', - release: 'v1.114.0', - }), - withRelease({ - icon: mdiTagMultiple, - iconColor: 'orange', - title: 'Tags', - description: 'Tag your photos and videos', - release: 'v1.113.0', - }), - withRelease({ - icon: mdiFolderSync, - iconColor: 'green', - title: 'Album sync (mobile)', - description: 'Sync or mirror an album from your phone to the Immich server', - release: 'v1.113.0', - }), - withRelease({ - icon: mdiFolderMultiple, - iconColor: 'brown', - title: 'Folders view', - description: 'Browse your photos and videos in their folder structure', - release: 'v1.113.0', - }), - withRelease({ - icon: mdiPalette, - title: 'Theming (mobile)', - description: 'Pick a primary color for the mobile app', - release: 'v1.112.0', - }), - withRelease({ - icon: mdiStarOutline, - iconColor: 'gold', - title: 'Star rating', - description: 'Rate your photos and videos', - release: 'v1.112.0', - }), - withRelease({ - icon: mdiCrop, - iconColor: 'royalblue', - title: 'Editor (mobile)', - description: 'Crop and rotate on mobile', - release: 'v1.111.0', - }), - withRelease({ - icon: mdiMap, - iconColor: 'green', - title: 'Deploy tiles.immich.cloud', - description: 'Dedicated tile server for Immich', - release: 'v1.111.0', - }), - { - icon: mdiStar, - iconColor: 'gold', - title: '40,000 Stars', - description: 'Reached 40K Stars on GitHub!', - getDateLabel: withLanguage(new Date(2024, 6, 21)), - }, - withRelease({ - icon: mdiShare, - title: 'Deploy my.immich.app', - description: 'Url router for immich links', - release: 'v1.109.0', - }), - withRelease({ - icon: mdiLicense, - iconColor: 'gold', - title: 'Supporter Badge', - description: 'The option to buy Immich to support its development!', - release: 'v1.109.0', - }), - withRelease({ - icon: mdiHistory, - title: 'Versioned documentation', - description: 'View documentation as it was at the time of past releases', - release: 'v1.106.1', - }), - withRelease({ - icon: mdiWeb, - iconColor: 'royalblue', - title: 'Web translations', - description: 'Translate the web application to multiple languages', - release: 'v1.106.1', - }), - withRelease({ - icon: mdiContentDuplicate, - title: 'Similar image detection', - description: "Detect duplicate assets that aren't exactly identical", - release: 'v1.106.1', - }), - withRelease({ - icon: mdiVectorCombine, - title: 'Container consolidation', - description: - 'The microservices container can be run as a worker within the server image, allowing us to remove it from the default stack.', - release: 'v1.106.1', - }), - withRelease({ - icon: mdiPencil, - iconColor: 'saddlebrown', - title: 'Read-write external libraries', - description: 'Edit, update, and delete files in external libraries', - release: 'v1.104.0', - }), - withRelease({ - icon: mdiEmailOutline, - iconColor: 'crimson', - title: 'Email notifications', - description: 'Send emails for important events', - release: 'v1.104.0', - }), - { - icon: mdiHandshakeOutline, - iconColor: 'magenta', - title: 'Immich joins FUTO!', - description: 'Joined Futo and Immich core team goes full-time', - getDateLabel: withLanguage(new Date(2024, 4, 1)), - }, - withRelease({ - icon: mdiEyeOutline, - iconColor: 'darkslategray', - title: 'Read-only albums', - description: 'Share albums with other users as read-only', - release: 'v1.103.0', - }), - withRelease({ - icon: mdiBookmark, - iconColor: 'orangered', - title: 'Permanent URLs (Web)', - description: 'Assets on the web now have permanent URLs', - release: 'v1.103.0', - }), - withRelease({ - icon: mdiStar, - iconColor: 'gold', - title: '30,000 Stars', - description: 'Reached 30K Stars on GitHub!', - release: 'v1.102.0', - }), - withRelease({ - icon: mdiChartBoxMultipleOutline, - iconColor: 'mediumvioletred', - title: 'OpenTelemetry metrics', - description: 'OpenTelemetry metrics for local evaluation and advanced debugging', - release: 'v1.99.0', - }), - withRelease({ - icon: 'immich', - title: 'New logo', - description: 'Immich got its new logo', - release: 'v1.98.0', - }), - withRelease({ - icon: mdiMagnifyScan, - title: 'Search enhancement with advanced filters', - description: 'Advanced search with filters by date, location and more', - release: 'v1.95.0', - }), - withRelease({ - icon: mdiScaleBalance, - iconColor: 'gold', - title: 'AGPL License', - description: 'Immich switches to AGPLv3 license', - release: 'v1.95.0', - }), - withRelease({ - icon: mdiEyeRefreshOutline, - title: 'Library watching', - description: 'Automatically import files in external libraries when the operating system detects changes.', - release: 'v1.94.0', - }), - withRelease({ - icon: mdiExpansionCard, - iconColor: 'green', - title: 'GPU acceleration for machine-learning', - description: 'Hardware acceleration support for Nvidia and Intel devices through CUDA and OpenVINO.', - release: 'v1.94.0', - }), - withRelease({ - icon: mdiAccountGroupOutline, - iconColor: 'gray', - title: '250 unique contributors', - description: '250 amazing people contributed to Immich', - release: 'v1.93.0', - }), - withRelease({ - icon: mdiMatrix, - title: 'Search improvement with pgvecto.rs', - description: 'Moved the search from typesense to pgvecto.rs', - release: 'v1.91.0', - }), - withRelease({ - icon: mdiPencil, - iconColor: 'saddlebrown', - title: 'Edit metadata', - description: "Edit a photo or video's date, time, hours, timezone, and GPS information", - release: 'v1.90.0', - }), - withRelease({ - icon: mdiVectorCombine, - title: 'Container consolidation', - description: - 'The serving of the web app is merged into the server image, allowing us to remove two containers from the stack.', - release: 'v1.88.0', - }), - withRelease({ - icon: mdiBash, - iconColor: 'gray', - title: 'CLI v2', - description: 'Version 2 of the Immich CLI is released, replacing the legacy v1 CLI.', - release: 'v1.88.0', - }), - withRelease({ - icon: mdiForum, - iconColor: 'dodgerblue', - title: 'Activity', - description: 'Comment a photo or a video in a shared album', - release: 'v1.84.0', - }), - withRelease({ - icon: mdiStar, - iconColor: 'gold', - title: '20,000 Stars', - description: 'Reached 20K Stars on GitHub!', - release: 'v1.83.0', - }), - withRelease({ - icon: mdiCameraBurst, - iconColor: 'rebeccapurple', - title: 'Stack assets', - description: 'Manual asset stacking for grouping and hiding related assets in the main timeline.', - release: 'v1.83.0', - }), - withRelease({ - icon: mdiPalette, - iconColor: 'magenta', - title: 'Custom theme', - description: 'Apply your custom CSS for modifying fonts, colors, and styles in the web application.', - release: 'v1.83.0', - }), - withRelease({ - icon: mdiTrashCanOutline, - iconColor: 'brown', - title: 'Trash feature', - description: 'Trash, restore from trash, and automatically empty the recycle bin after 30 days.', - release: 'v1.82.0', - }), - withRelease({ - icon: mdiBookSearchOutline, - title: 'External libraries', - description: 'Automatically import media into Immich based on imports paths and ignore patterns.', - release: 'v1.79.0', - }), - withRelease({ - icon: mdiMap, - iconColor: 'darksalmon', - title: 'Map view (mobile)', - description: 'Heat map implementation in the mobile app.', - release: 'v1.76.0', - }), - withRelease({ - icon: mdiFile, - iconColor: 'lightblue', - title: 'Configuration file', - description: 'Auto-configure an Immich installation via a configuration file.', - release: 'v1.75.0', - }), - withRelease({ - icon: mdiMonitor, - iconColor: 'darkcyan', - title: 'Slideshow mode (web)', - description: 'Start a full-screen slideshow from an Album on the web.', - release: 'v1.75.0', - }), - withRelease({ - icon: mdiServer, - iconColor: 'lightskyblue', - title: 'Hardware transcoding', - description: 'Support hardware acceleration (QuickSync, VAAPI, and Nvidia) for video transcoding.', - release: 'v1.72.0', - }), - withRelease({ - icon: mdiImageAlbum, - iconColor: 'olivedrab', - title: 'View albums via time buckets', - description: 'Upgrade albums to use time buckets, an optimized virtual viewport.', - release: 'v1.72.0', - }), - withRelease({ - icon: mdiImageAlbum, - iconColor: 'olivedrab', - title: 'Album description', - description: 'Save an album description.', - release: 'v1.72.0', - }), - withRelease({ - icon: mdiRotate360, - title: '360° Photos (web)', - description: 'View 360° Photos on the web.', - release: 'v1.71.0', - }), - withRelease({ - icon: mdiMotionPlayOutline, - title: 'Android motion photos', - description: 'Add support for Android Motion Photos.', - release: 'v1.69.0', - }), - withRelease({ - icon: mdiFaceManOutline, - iconColor: 'mistyrose', - title: 'Show/hide faces', - description: 'Add the options to show or hide faces.', - release: 'v1.68.0', - }), - withRelease({ - icon: mdiMerge, - iconColor: 'forestgreen', - title: 'Merge faces', - description: 'Add the ability to merge multiple faces together.', - release: 'v1.67.0', - }), - withRelease({ - icon: mdiImage, - iconColor: 'rebeccapurple', - title: 'Feature photo', - description: 'Add the option to change the feature photo for a person.', - release: 'v1.66.0', - }), - withRelease({ - icon: mdiKeyboardSettingsOutline, - iconColor: 'darkslategray', - title: 'Multi-select via SHIFT', - description: 'Add the option to multi-select while holding SHIFT.', - release: 'v1.66.0', - }), - withRelease({ - icon: mdiImageMultipleOutline, - iconColor: 'rebeccapurple', - title: 'Memories (mobile)', - description: 'View "On this day..." memories in the mobile app.', - release: 'v1.65.0', - }), - withRelease({ - icon: mdiFaceMan, - iconColor: 'mistyrose', - title: 'Facial recognition (mobile)', - description: 'View detected faces in the mobile app.', - release: 'v1.63.0', - }), - withRelease({ - icon: mdiImageMultipleOutline, - iconColor: 'rebeccapurple', - title: 'Memories (web)', - description: 'View pictures taken in past years on this day on the web.', - release: 'v1.61.0', - }), - withRelease({ - icon: mdiCollage, - iconColor: 'deeppink', - title: 'Justified layout (web)', - description: 'Implement justified layout (collage) on the web.', - release: 'v1.61.0', - }), - withRelease({ - icon: mdiRaw, - title: 'RAW file formats', - description: 'Support for RAW file formats.', - release: 'v1.61.0', - }), - withRelease({ - icon: mdiShareAll, - iconColor: 'darkturquoise', - title: 'Partner sharing (mobile)', - description: 'View shared partner photos in the mobile app.', - release: 'v1.58.0', - }), - withRelease({ - icon: mdiFile, - iconColor: 'lightblue', - title: 'XMP sidecar', - description: 'Attach XMP sidecar files to assets.', - release: 'v1.58.0', - }), - withRelease({ - icon: mdiFolder, - iconColor: 'brown', - title: 'Custom storage label', - description: 'Replace the user UUID in the storage template with a custom label.', - release: 'v1.57.0', - }), - withRelease({ - icon: mdiShareCircle, - title: 'Partner sharing', - description: 'Share your entire collection with another user.', - release: 'v1.56.0', - }), - withRelease({ - icon: mdiFaceMan, - iconColor: 'mistyrose', - title: 'Facial recognition', - description: 'Detect faces in pictures and cluster them together as people, which can be named.', - release: 'v1.56.0', - }), - withRelease({ - icon: mdiMap, - iconColor: 'darksalmon', - title: 'Map view (web)', - description: 'View a global map, with clusters of photos based on corresponding GPS data.', - release: 'v1.55.0', - }), - withRelease({ - icon: mdiDevices, - iconColor: 'slategray', - title: 'Manage auth devices', - description: 'Manage logged-in devices and revoke access from User Settings.', - release: 'v1.55.0', - }), - withRelease({ - icon: mdiStar, - iconColor: 'gold', - title: '10,000 Stars', - description: 'Reached 10K stars on GitHub!', - release: 'v1.54.0', - }), - withRelease({ - icon: mdiText, - title: 'Asset descriptions', - description: 'Save an asset description', - release: 'v1.54.0', - }), - withRelease({ - icon: mdiArchiveOutline, - title: 'Archiving', - description: 'Remove assets from the main timeline by archiving them.', - release: 'v1.54.0', - }), - withRelease({ - icon: mdiDevices, - iconColor: 'slategray', - title: 'Responsive web app', - description: 'Optimize the web app for small screen.', - release: 'v1.54.0', - }), - withRelease({ - icon: mdiFileSearch, - iconColor: 'brown', - title: 'Search by metadata', - description: 'Search images by filename, description, tagged people, make, model, and other metadata.', - release: 'v1.52.0', - }), - withRelease({ - icon: mdiImageSearch, - iconColor: 'rebeccapurple', - title: 'CLIP search', - description: 'Search images with free-form text like "Sunset at the beach".', - release: 'v1.51.0', - }), - withRelease({ - icon: mdiMagnify, - iconColor: 'lightblue', - title: 'Explore page', - description: 'View tagged places, object, and people.', - release: 'v1.51.0', - }), - withRelease({ - icon: mdiAppleIos, - title: 'iOS background uploads', - description: 'Automatically backup pictures in the background on iOS.', - release: 'v1.48.0', - }), - withRelease({ - icon: mdiMotionPlayOutline, - title: 'Auto-Link live photos', - description: 'Automatically link live photos, even when uploaded as separate files.', - release: 'v1.48.0', - }), - withRelease({ - icon: mdiMaterialDesign, - iconColor: 'blue', - title: 'Material design 3 (mobile)', - description: 'Upgrade the mobile app to Material Design 3.', - release: 'v1.47.0', - }), - withRelease({ - icon: mdiHeart, - iconColor: 'red', - title: 'Favorites (mobile)', - description: 'Show favorites on the mobile app.', - release: 'v1.46.0', - }), - withRelease({ - icon: mdiCakeVariant, - iconColor: 'deeppink', - title: 'Immich turns 1', - description: 'Immich is officially one year old.', - release: 'v1.43.0', - }), - withRelease({ - icon: mdiHeart, - iconColor: 'red', - title: 'Favorites page (web)', - description: 'Favorite and view favorites on the web.', - release: 'v1.43.0', - }), - withRelease({ - icon: mdiShareCircle, - title: 'Public share links', - description: 'Share photos and albums publicly via a shared link.', - release: 'v1.41.0', - }), - withRelease({ - icon: mdiFolder, - iconColor: 'lightblue', - title: 'User-defined storage structure', - description: 'Support custom storage structures.', - release: 'v1.39.0', - }), - withRelease({ - icon: mdiMotionPlayOutline, - title: 'iOS live photos', - description: 'Backup and display iOS Live Photos.', - release: 'v1.36.0', - }), - withRelease({ - icon: mdiSecurity, - iconColor: 'green', - title: 'OAuth integration', - description: 'Support OAuth2 and OIDC capable identity providers.', - release: 'v1.36.0', - }), - withRelease({ - icon: mdiWeb, - iconColor: 'royalblue', - title: 'Documentation site', - description: 'Release an official documentation website.', - release: 'v1.33.1', - }), - withRelease({ - icon: mdiThemeLightDark, - iconColor: 'slategray', - title: 'Dark mode (web)', - description: 'Dark mode on the web.', - release: 'v1.32.0', - }), - withRelease({ - icon: mdiPanVertical, - title: 'Virtual scrollbar (web)', - description: 'View the main timeline with a virtual scrollbar, allowing to jump to any point in time, instantly.', - release: 'v1.27.0', - }), - withRelease({ - icon: mdiCheckAll, - iconColor: 'green', - title: 'Checksum duplication check', - description: 'Enforce per user sha1 checksum uniqueness.', - release: 'v1.27.0', - }), - withRelease({ - icon: mdiAndroid, - iconColor: 'greenyellow', - title: 'Android background backup', - description: 'Automatic backup in the background on Android.', - release: 'v1.24.0', - }), - withRelease({ - icon: mdiAccountGroup, - iconColor: 'gray', - title: 'Admin portal', - description: 'Manage users and admin settings from the web.', - release: 'v1.10.0', - }), - withRelease({ - icon: mdiShareCircle, - title: 'Album sharing', - description: 'Share albums with other users.', - release: 'v1.7.0', - }), - withRelease({ - icon: mdiTag, - iconColor: 'coral', - title: 'Image tagging', - description: 'Tag images with custom values.', - release: 'v1.7.0', - }), - withRelease({ - icon: mdiImage, - iconColor: 'rebeccapurple', - title: 'View exif', - description: 'View metadata about assets.', - release: 'v1.3.0', - }), - withRelease({ - icon: mdiCheckboxMarked, - iconColor: 'green', - title: 'Multi select', - description: 'Select and execute actions on multiple assets at the same time.', - release: 'v1.2.0', - }), - withRelease({ - icon: mdiVideo, - iconColor: 'slategray', - title: 'Video player', - description: 'Play videos in the web and on mobile.', - release: 'v1.2.0', - }), - { - icon: mdiPartyPopper, - iconColor: 'deeppink', - title: 'First commit', - description: 'First commit on GitHub, Immich is born.', - getDateLabel: withLanguage(new Date(2022, 1, 3)), - }, -]; - -export default function MilestonePage(): JSX.Element { - return ( - -
-

- {title} -

-

{description}

-
- -
-
-
- ); -} diff --git a/docs/static/.well-known/security.txt b/docs/static/.well-known/security.txt deleted file mode 100644 index 5a8414c3e2..0000000000 --- a/docs/static/.well-known/security.txt +++ /dev/null @@ -1,5 +0,0 @@ -Policy: https://github.com/immich-app/immich/blob/main/SECURITY.md -Contact: mailto:security@immich.app -Preferred-Languages: en -Expires: 2026-05-01T23:59:00.000Z -Canonical: https://immich.app/.well-known/security.txt diff --git a/docs/static/_redirects b/docs/static/_redirects index 7b01d1e3bb..ce4b670246 100644 --- a/docs/static/_redirects +++ b/docs/static/_redirects @@ -1,34 +1,36 @@ -/docs /docs/overview/welcome 307 -/docs/ /docs/overview/welcome 307 -/docs/mobile-app-beta-program /docs/features/mobile-app 307 -/docs/contribution-guidelines /docs/overview/support-the-project#contributing 307 -/docs/install /docs/install/docker-compose 307 -/docs/installation/one-step-installation /docs/install/script 307 -/docs/installation/portainer-installation /docs/install/portainer 307 -/docs/installation/recommended-installation /docs/install/docker-compose 307 -/docs/installation/unraid /docs/install/unraid 307 -/docs/installation/requirements /docs/install/requirements 307 -/docs/overview/logo-meaning /docs/overview/logo 307 -/docs/overview/technology-stack /docs/developer/architecture 307 -/docs/usage/automatic-backup /docs/features/automatic-backup 307 -/docs/usage/bulk-upload /docs/features/command-line-interface 307 -/docs/features/bulk-upload /docs/features/command-line-interface 307 -/docs/usage/oauth /docs/administration/oauth 307 -/docs/usage/post-installation /docs/install/post-install 307 -/docs/usage/update /docs/install/docker-compose#step-4---upgrading 307 -/docs/usage/server-commands /docs/administration/server-commands 307 -/docs/features/jobs /docs/administration/jobs 307 -/docs/features/oauth /docs/administration/oauth 307 -/docs/features/password-login /docs/administration/password-login 307 -/docs/features/server-commands /docs/administration/server-commands 307 -/docs/features/storage-template /docs/administration/storage-template 307 -/docs/features/user-management /docs/administration/user-management 307 -/docs/developer/contributing /docs/developer/pr-checklist 307 -/docs/guides/machine-learning /docs/guides/remote-machine-learning 307 -/docs/administration/password-login /docs/administration/system-settings 307 -/docs/features/search /docs/features/searching 307 -/docs/features/smart-search /docs/features/searching 307 -/docs/guides/api-album-sync /docs/community-projects 307 -/docs/guides/remove-offline-files /docs/community-projects 307 -/milestones /roadmap 307 -/docs/overview/introduction /docs/overview/welcome 307 +/ /overview/quick-start 307 +/mobile-app-beta-program /features/mobile-app 307 +/contribution-guidelines /overview/support-the-project#contributing 307 +/install /install/docker-compose 307 +/installation/one-step-installation /install/script 307 +/installation/portainer-installation /install/portainer 307 +/installation/recommended-installation /install/docker-compose 307 +/installation/unraid /install/unraid 307 +/installation/requirements /install/requirements 307 +/overview/logo-meaning /overview/logo 307 +/overview/technology-stack /developer/architecture 307 +/usage/automatic-backup /features/automatic-backup 307 +/usage/bulk-upload /features/command-line-interface 307 +/features/bulk-upload /features/command-line-interface 307 +/usage/oauth /administration/oauth 307 +/usage/post-installation /install/post-install 307 +/usage/update /install/docker-compose#step-4---upgrading 307 +/usage/server-commands /administration/server-commands 307 +/features/jobs /administration/jobs 307 +/features/oauth /administration/oauth 307 +/features/password-login /administration/password-login 307 +/features/server-commands /administration/server-commands 307 +/features/storage-template /administration/storage-template 307 +/features/user-management /administration/user-management 307 +/developer/contributing /developer/pr-checklist 307 +/guides/machine-learning /guides/remote-machine-learning 307 +/administration/password-login /administration/system-settings 307 +/features/search /features/searching 307 +/features/smart-search /features/searching 307 +/guides/api-album-sync https://awesome.immich.app/ 307 +/guides/remove-offline-files https://awesome.immich.app/ 307 +/community-guides https://awesome.immich.app/ 307 +/community-projects https://awesome.immich.app/ 307 +/overview/introduction /overview/quick-start 307 +/overview/welcome /overview/quick-start 307 +/docs/* /:splat 307 diff --git a/docs/static/archived-versions.json b/docs/static/archived-versions.json index 2ff41c5e9e..6affb532c9 100644 --- a/docs/static/archived-versions.json +++ b/docs/static/archived-versions.json @@ -1,230 +1,257 @@ [ { - "label": "v1.143.0", - "url": "https://v1.143.0.archive.immich.app" + "label": "v2.2.3", + "url": "https://docs.v2.2.3.archive.immich.app" + }, + { + "label": "v2.2.2", + "url": "https://docs.v2.2.2.archive.immich.app" + }, + { + "label": "v2.2.1", + "url": "https://docs.v2.2.1.archive.immich.app" + }, + { + "label": "v2.2.0", + "url": "https://docs.v2.2.0.archive.immich.app" + }, + { + "label": "v2.1.0", + "url": "https://docs.v2.1.0.archive.immich.app" + }, + { + "label": "v2.0.1", + "url": "https://docs.v2.0.1.archive.immich.app" + }, + { + "label": "v2.0.0", + "url": "https://docs.v2.0.0.archive.immich.app" + }, + { + "label": "v1.144.1", + "url": "https://docs.v1.144.1.archive.immich.app" + }, + { + "label": "v1.144.0", + "url": "https://docs.v1.144.0.archive.immich.app" + }, + { + "label": "v1.143.1", + "url": "https://docs.v1.143.1.archive.immich.app" }, { "label": "v1.142.1", - "url": "https://v1.142.1.archive.immich.app" - }, - { - "label": "v1.142.0", - "url": "https://v1.142.0.archive.immich.app" + "url": "https://v1.142.1.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.141.1", - "url": "https://v1.141.1.archive.immich.app" - }, - { - "label": "v1.141.0", - "url": "https://v1.141.0.archive.immich.app" + "url": "https://v1.141.1.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.140.1", - "url": "https://v1.140.1.archive.immich.app" - }, - { - "label": "v1.140.0", - "url": "https://v1.140.0.archive.immich.app" + "url": "https://v1.140.1.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.139.4", - "url": "https://v1.139.4.archive.immich.app" - }, - { - "label": "v1.139.3", - "url": "https://v1.139.3.archive.immich.app" - }, - { - "label": "v1.139.2", - "url": "https://v1.139.2.archive.immich.app" + "url": "https://v1.139.4.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.138.1", - "url": "https://v1.138.1.archive.immich.app" - }, - { - "label": "v1.138.0", - "url": "https://v1.138.0.archive.immich.app" + "url": "https://v1.138.1.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.137.3", - "url": "https://v1.137.3.archive.immich.app" - }, - { - "label": "v1.137.2", - "url": "https://v1.137.2.archive.immich.app" - }, - { - "label": "v1.137.1", - "url": "https://v1.137.1.archive.immich.app" - }, - { - "label": "v1.137.0", - "url": "https://v1.137.0.archive.immich.app" + "url": "https://v1.137.3.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.136.0", - "url": "https://v1.136.0.archive.immich.app" + "url": "https://v1.136.0.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.135.3", - "url": "https://v1.135.3.archive.immich.app" - }, - { - "label": "v1.135.2", - "url": "https://v1.135.2.archive.immich.app" - }, - { - "label": "v1.135.1", - "url": "https://v1.135.1.archive.immich.app" - }, - { - "label": "v1.135.0", - "url": "https://v1.135.0.archive.immich.app" + "url": "https://v1.135.3.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.134.0", - "url": "https://v1.134.0.archive.immich.app" + "url": "https://v1.134.0.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.133.1", - "url": "https://v1.133.1.archive.immich.app" - }, - { - "label": "v1.133.0", - "url": "https://v1.133.0.archive.immich.app" + "url": "https://v1.133.1.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.132.3", - "url": "https://v1.132.3.archive.immich.app" + "url": "https://v1.132.3.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.131.3", - "url": "https://v1.131.3.archive.immich.app" + "url": "https://v1.131.3.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.130.3", - "url": "https://v1.130.3.archive.immich.app" + "url": "https://v1.130.3.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.129.0", - "url": "https://v1.129.0.archive.immich.app" + "url": "https://v1.129.0.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.128.0", - "url": "https://v1.128.0.archive.immich.app" + "url": "https://v1.128.0.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.127.0", - "url": "https://v1.127.0.archive.immich.app" + "url": "https://v1.127.0.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.126.1", - "url": "https://v1.126.1.archive.immich.app" + "url": "https://v1.126.1.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.125.7", - "url": "https://v1.125.7.archive.immich.app" + "url": "https://v1.125.7.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.124.2", - "url": "https://v1.124.2.archive.immich.app" + "url": "https://v1.124.2.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.123.0", - "url": "https://v1.123.0.archive.immich.app" + "url": "https://v1.123.0.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.122.3", - "url": "https://v1.122.3.archive.immich.app" + "url": "https://v1.122.3.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.121.0", - "url": "https://v1.121.0.archive.immich.app" + "url": "https://v1.121.0.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.120.2", - "url": "https://v1.120.2.archive.immich.app" + "url": "https://v1.120.2.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.119.1", - "url": "https://v1.119.1.archive.immich.app" + "url": "https://v1.119.1.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.118.2", - "url": "https://v1.118.2.archive.immich.app" + "url": "https://v1.118.2.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.117.0", - "url": "https://v1.117.0.archive.immich.app" + "url": "https://v1.117.0.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.116.2", - "url": "https://v1.116.2.archive.immich.app" + "url": "https://v1.116.2.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.115.0", - "url": "https://v1.115.0.archive.immich.app" + "url": "https://v1.115.0.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.114.0", - "url": "https://v1.114.0.archive.immich.app" + "url": "https://v1.114.0.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.113.1", - "url": "https://v1.113.1.archive.immich.app" + "url": "https://v1.113.1.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.112.1", - "url": "https://v1.112.1.archive.immich.app" + "url": "https://v1.112.1.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.111.0", - "url": "https://v1.111.0.archive.immich.app" + "url": "https://v1.111.0.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.110.0", - "url": "https://v1.110.0.archive.immich.app" + "url": "https://v1.110.0.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.109.2", - "url": "https://v1.109.2.archive.immich.app" + "url": "https://v1.109.2.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.108.0", - "url": "https://v1.108.0.archive.immich.app" + "url": "https://v1.108.0.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.107.2", - "url": "https://v1.107.2.archive.immich.app" + "url": "https://v1.107.2.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.106.4", - "url": "https://v1.106.4.archive.immich.app" + "url": "https://v1.106.4.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.105.1", - "url": "https://v1.105.1.archive.immich.app" + "url": "https://v1.105.1.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.104.0", - "url": "https://v1.104.0.archive.immich.app" + "url": "https://v1.104.0.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.103.1", - "url": "https://v1.103.1.archive.immich.app" + "url": "https://v1.103.1.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.102.3", - "url": "https://v1.102.3.archive.immich.app" + "url": "https://v1.102.3.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.101.0", - "url": "https://v1.101.0.archive.immich.app" + "url": "https://v1.101.0.archive.immich.app", + "rootPath": "/docs" }, { "label": "v1.100.0", - "url": "https://v1.100.0.archive.immich.app" + "url": "https://v1.100.0.archive.immich.app", + "rootPath": "/docs" } ] diff --git a/docs/static/img/synology-action-clean.png b/docs/static/img/synology-action-clean.png new file mode 100644 index 0000000000..4d168b0bd8 Binary files /dev/null and b/docs/static/img/synology-action-clean.png differ diff --git a/docs/static/img/synology-build.png b/docs/static/img/synology-build.png new file mode 100644 index 0000000000..50c4d0fc98 Binary files /dev/null and b/docs/static/img/synology-build.png differ diff --git a/docs/static/img/synology-container-ip.png b/docs/static/img/synology-container-ip.png new file mode 100644 index 0000000000..21617d8c72 Binary files /dev/null and b/docs/static/img/synology-container-ip.png differ diff --git a/docs/static/img/synology-container-manager-customize-docker-compose.png b/docs/static/img/synology-container-manager-customize-docker-compose.png index 558557487a..2c0a40def0 100644 Binary files a/docs/static/img/synology-container-manager-customize-docker-compose.png and b/docs/static/img/synology-container-manager-customize-docker-compose.png differ diff --git a/docs/static/img/synology-custom-port-firewall-rule.png b/docs/static/img/synology-custom-port-firewall-rule.png new file mode 100644 index 0000000000..26ee17785c Binary files /dev/null and b/docs/static/img/synology-custom-port-firewall-rule.png differ diff --git a/docs/static/img/synology-fw-ipedit.png b/docs/static/img/synology-fw-ipedit.png new file mode 100644 index 0000000000..7f4e561395 Binary files /dev/null and b/docs/static/img/synology-fw-ipedit.png differ diff --git a/docs/static/img/synology-fw-rules.png b/docs/static/img/synology-fw-rules.png new file mode 100644 index 0000000000..2ec43a682f Binary files /dev/null and b/docs/static/img/synology-fw-rules.png differ diff --git a/docs/static/img/synology-ipaddress-firewall-rule.png b/docs/static/img/synology-ipaddress-firewall-rule.png new file mode 100644 index 0000000000..d1982b053d Binary files /dev/null and b/docs/static/img/synology-ipaddress-firewall-rule.png differ diff --git a/docs/static/img/synology-project-stop.png b/docs/static/img/synology-project-stop.png new file mode 100644 index 0000000000..8a77446dc2 Binary files /dev/null and b/docs/static/img/synology-project-stop.png differ diff --git a/docs/static/img/synology-remove-unused.png b/docs/static/img/synology-remove-unused.png new file mode 100644 index 0000000000..9b1a217902 Binary files /dev/null and b/docs/static/img/synology-remove-unused.png differ diff --git a/docs/static/img/synology-select-proj.png b/docs/static/img/synology-select-proj.png new file mode 100644 index 0000000000..21642d8713 Binary files /dev/null and b/docs/static/img/synology-select-proj.png differ diff --git a/e2e/.nvmrc b/e2e/.nvmrc index e2228113dd..0a492611a0 100644 --- a/e2e/.nvmrc +++ b/e2e/.nvmrc @@ -1 +1 @@ -22.19.0 +24.11.0 diff --git a/e2e/docker-compose.yml b/e2e/docker-compose.yml index 6aba8ff72a..3d62c8a34a 100644 --- a/e2e/docker-compose.yml +++ b/e2e/docker-compose.yml @@ -35,10 +35,10 @@ services: - 2285:2285 redis: - image: redis:6.2-alpine@sha256:7fe72c486b910f6b1a9769c937dad5d63648ddee82e056f47417542dd40825bb + image: redis:6.2-alpine@sha256:37e002448575b32a599109664107e374c8709546905c372a34d64919043b9ceb database: - image: ghcr.io/immich-app/postgres:14-vectorchord0.3.0@sha256:11ced39d65a92a54d12890ced6a26cc2003f92697d6f0d4d944b98459dba7138 + image: ghcr.io/immich-app/postgres:14-vectorchord0.3.0@sha256:6f3e9d2c2177af16c2988ff71425d79d89ca630ec2f9c8db03209ab716542338 command: -c fsync=off -c shared_preload_libraries=vchord.so -c config_file=/var/lib/postgresql/data/postgresql.conf environment: POSTGRES_PASSWORD: postgres diff --git a/e2e/mise.toml b/e2e/mise.toml new file mode 100644 index 0000000000..c298115e40 --- /dev/null +++ b/e2e/mise.toml @@ -0,0 +1,29 @@ +[tasks.install] +run = "pnpm install --filter immich-e2e --frozen-lockfile" + +[tasks.test] +env._.path = "./node_modules/.bin" +run = "vitest --run" + +[tasks."test-web"] +env._.path = "./node_modules/.bin" +run = "playwright test" + +[tasks.format] +env._.path = "./node_modules/.bin" +run = "prettier --check ." + +[tasks."format-fix"] +env._.path = "./node_modules/.bin" +run = "prettier --write ." + +[tasks.lint] +env._.path = "./node_modules/.bin" +run = "eslint \"src/**/*.ts\" --max-warnings 0" + +[tasks."lint-fix"] +run = { task = "lint --fix" } + +[tasks.check] +env._.path = "./node_modules/.bin" +run = "tsc --noEmit" diff --git a/e2e/package.json b/e2e/package.json index 2078774d01..84e1823e0c 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,6 +1,6 @@ { "name": "immich-e2e", - "version": "1.143.0", + "version": "2.2.3", "description": "", "main": "index.js", "type": "module", @@ -25,7 +25,7 @@ "@playwright/test": "^1.44.1", "@socket.io/component-emitter": "^3.1.2", "@types/luxon": "^3.4.2", - "@types/node": "^22.18.1", + "@types/node": "^22.19.0", "@types/oidc-provider": "^9.0.0", "@types/pg": "^8.15.1", "@types/pngjs": "^6.0.4", @@ -34,7 +34,7 @@ "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-unicorn": "^60.0.0", - "exiftool-vendored": "^28.3.1", + "exiftool-vendored": "^31.1.0", "globals": "^16.0.0", "jose": "^5.6.3", "luxon": "^3.4.4", @@ -43,7 +43,7 @@ "pngjs": "^7.0.0", "prettier": "^3.2.5", "prettier-plugin-organize-imports": "^4.0.0", - "sharp": "^0.34.3", + "sharp": "^0.34.4", "socket.io-client": "^4.7.4", "supertest": "^7.0.0", "typescript": "^5.3.3", @@ -52,6 +52,6 @@ "vitest": "^3.0.0" }, "volta": { - "node": "22.19.0" + "node": "24.11.0" } } diff --git a/e2e/src/api/specs/album.e2e-spec.ts b/e2e/src/api/specs/album.e2e-spec.ts index 5615a312f2..c4f06edd93 100644 --- a/e2e/src/api/specs/album.e2e-spec.ts +++ b/e2e/src/api/specs/album.e2e-spec.ts @@ -136,6 +136,7 @@ describe('/albums', () => { expect(body).toEqual({ ...user1Albums[0], assets: [expect.objectContaining({ isFavorite: false })], + contributorCounts: [{ userId: user1.userId, assetCount: 1 }], lastModifiedAssetTimestamp: expect.any(String), startDate: expect.any(String), endDate: expect.any(String), @@ -310,6 +311,7 @@ describe('/albums', () => { expect(body).toEqual({ ...user1Albums[0], assets: [expect.objectContaining({ id: user1Albums[0].assets[0].id })], + contributorCounts: [{ userId: user1.userId, assetCount: 1 }], lastModifiedAssetTimestamp: expect.any(String), startDate: expect.any(String), endDate: expect.any(String), @@ -345,6 +347,7 @@ describe('/albums', () => { expect(body).toEqual({ ...user1Albums[0], assets: [expect.objectContaining({ id: user1Albums[0].assets[0].id })], + contributorCounts: [{ userId: user1.userId, assetCount: 1 }], lastModifiedAssetTimestamp: expect.any(String), startDate: expect.any(String), endDate: expect.any(String), @@ -362,6 +365,7 @@ describe('/albums', () => { expect(body).toEqual({ ...user1Albums[0], assets: [], + contributorCounts: [{ userId: user1.userId, assetCount: 1 }], assetCount: 1, lastModifiedAssetTimestamp: expect.any(String), endDate: expect.any(String), @@ -382,6 +386,7 @@ describe('/albums', () => { expect(body).toEqual({ ...user2Albums[0], assets: [], + contributorCounts: [{ userId: user1.userId, assetCount: 1 }], assetCount: 1, lastModifiedAssetTimestamp: expect.any(String), endDate: expect.any(String), diff --git a/e2e/src/api/specs/asset.e2e-spec.ts b/e2e/src/api/specs/asset.e2e-spec.ts index 5c30ff5cbe..ab3252c40b 100644 --- a/e2e/src/api/specs/asset.e2e-spec.ts +++ b/e2e/src/api/specs/asset.e2e-spec.ts @@ -15,7 +15,6 @@ import { DateTime } from 'luxon'; import { randomBytes } from 'node:crypto'; import { readFile, writeFile } from 'node:fs/promises'; import { basename, join } from 'node:path'; -import sharp from 'sharp'; import { Socket } from 'socket.io-client'; import { createUserDto, uuidDto } from 'src/fixtures'; import { makeRandomImage } from 'src/generators'; @@ -41,40 +40,6 @@ const today = DateTime.fromObject({ }) as DateTime; const yesterday = today.minus({ days: 1 }); -const createTestImageWithExif = async (filename: string, exifData: Record) => { - // Generate unique color to ensure different checksums for each image - const r = Math.floor(Math.random() * 256); - const g = Math.floor(Math.random() * 256); - const b = Math.floor(Math.random() * 256); - - // Create a 100x100 solid color JPEG using Sharp - const imageBytes = await sharp({ - create: { - width: 100, - height: 100, - channels: 3, - background: { r, g, b }, - }, - }) - .jpeg({ quality: 90 }) - .toBuffer(); - - // Add random suffix to filename to avoid collisions - const uniqueFilename = filename.replace('.jpg', `-${randomBytes(4).toString('hex')}.jpg`); - const filepath = join(tempDir, uniqueFilename); - await writeFile(filepath, imageBytes); - - // Filter out undefined values before writing EXIF - const cleanExifData = Object.fromEntries(Object.entries(exifData).filter(([, value]) => value !== undefined)); - - await exiftool.write(filepath, cleanExifData); - - // Re-read the image bytes after EXIF has been written - const finalImageBytes = await readFile(filepath); - - return { filepath, imageBytes: finalImageBytes, filename: uniqueFilename }; -}; - describe('/asset', () => { let admin: LoginResponseDto; let websocket: Socket; @@ -1249,411 +1214,6 @@ describe('/asset', () => { }); }); - describe('EXIF metadata extraction', () => { - describe('Additional date tag extraction', () => { - describe('Date-time vs time-only tag handling', () => { - it('should fall back to file timestamps when only time-only tags are available', async () => { - const { imageBytes, filename } = await createTestImageWithExif('time-only-fallback.jpg', { - TimeCreated: '2023:11:15 14:30:00', // Time-only tag, should not be used for dateTimeOriginal - // Exclude all date-time tags to force fallback to file timestamps - SubSecDateTimeOriginal: undefined, - DateTimeOriginal: undefined, - SubSecCreateDate: undefined, - SubSecMediaCreateDate: undefined, - CreateDate: undefined, - MediaCreateDate: undefined, - CreationDate: undefined, - DateTimeCreated: undefined, - GPSDateTime: undefined, - DateTimeUTC: undefined, - SonyDateTime2: undefined, - GPSDateStamp: undefined, - }); - - const oldDate = new Date('2020-01-01T00:00:00.000Z'); - const asset = await utils.createAsset(admin.accessToken, { - assetData: { - filename, - bytes: imageBytes, - }, - fileCreatedAt: oldDate.toISOString(), - fileModifiedAt: oldDate.toISOString(), - }); - - await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id }); - - const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) }); - - expect(assetInfo.exifInfo?.dateTimeOriginal).toBeDefined(); - // Should fall back to file timestamps, which we set to 2020-01-01 - expect(new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime()).toBe( - new Date('2020-01-01T00:00:00.000Z').getTime(), - ); - }); - - it('should prefer DateTimeOriginal over time-only tags', async () => { - const { imageBytes, filename } = await createTestImageWithExif('datetime-over-time.jpg', { - DateTimeOriginal: '2023:10:10 10:00:00', // Should be preferred - TimeCreated: '2023:11:15 14:30:00', // Should be ignored (time-only) - }); - - const asset = await utils.createAsset(admin.accessToken, { - assetData: { - filename, - bytes: imageBytes, - }, - }); - - await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id }); - - const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) }); - - expect(assetInfo.exifInfo?.dateTimeOriginal).toBeDefined(); - // Should use DateTimeOriginal, not TimeCreated - expect(new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime()).toBe( - new Date('2023-10-10T10:00:00.000Z').getTime(), - ); - }); - }); - - describe('GPSDateTime tag extraction', () => { - it('should extract GPSDateTime with GPS coordinates', async () => { - const { imageBytes, filename } = await createTestImageWithExif('gps-datetime.jpg', { - GPSDateTime: '2023:11:15 12:30:00Z', - GPSLatitude: 37.7749, - GPSLongitude: -122.4194, - // Exclude other date tags - SubSecDateTimeOriginal: undefined, - DateTimeOriginal: undefined, - SubSecCreateDate: undefined, - SubSecMediaCreateDate: undefined, - CreateDate: undefined, - MediaCreateDate: undefined, - CreationDate: undefined, - DateTimeCreated: undefined, - TimeCreated: undefined, - }); - - const asset = await utils.createAsset(admin.accessToken, { - assetData: { - filename, - bytes: imageBytes, - }, - }); - - await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id }); - - const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) }); - - expect(assetInfo.exifInfo?.dateTimeOriginal).toBeDefined(); - expect(assetInfo.exifInfo?.latitude).toBeCloseTo(37.7749, 4); - expect(assetInfo.exifInfo?.longitude).toBeCloseTo(-122.4194, 4); - expect(new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime()).toBe( - new Date('2023-11-15T12:30:00.000Z').getTime(), - ); - }); - }); - - describe('CreateDate tag extraction', () => { - it('should extract CreateDate when available', async () => { - const { imageBytes, filename } = await createTestImageWithExif('create-date.jpg', { - CreateDate: '2023:11:15 10:30:00', - // Exclude other higher priority date tags - SubSecDateTimeOriginal: undefined, - DateTimeOriginal: undefined, - SubSecCreateDate: undefined, - SubSecMediaCreateDate: undefined, - MediaCreateDate: undefined, - CreationDate: undefined, - DateTimeCreated: undefined, - TimeCreated: undefined, - GPSDateTime: undefined, - }); - - const asset = await utils.createAsset(admin.accessToken, { - assetData: { - filename, - bytes: imageBytes, - }, - }); - - await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id }); - - const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) }); - - expect(assetInfo.exifInfo?.dateTimeOriginal).toBeDefined(); - expect(new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime()).toBe( - new Date('2023-11-15T10:30:00.000Z').getTime(), - ); - }); - }); - - describe('GPSDateStamp tag extraction', () => { - it('should fall back to file timestamps when only date-only tags are available', async () => { - const { imageBytes, filename } = await createTestImageWithExif('gps-datestamp.jpg', { - GPSDateStamp: '2023:11:15', // Date-only tag, should not be used for dateTimeOriginal - // Note: NOT including GPSTimeStamp to avoid automatic GPSDateTime creation - GPSLatitude: 51.5074, - GPSLongitude: -0.1278, - // Explicitly exclude all testable date-time tags to force fallback to file timestamps - DateTimeOriginal: undefined, - CreateDate: undefined, - CreationDate: undefined, - GPSDateTime: undefined, - }); - - const oldDate = new Date('2020-01-01T00:00:00.000Z'); - const asset = await utils.createAsset(admin.accessToken, { - assetData: { - filename, - bytes: imageBytes, - }, - fileCreatedAt: oldDate.toISOString(), - fileModifiedAt: oldDate.toISOString(), - }); - - await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id }); - - const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) }); - - expect(assetInfo.exifInfo?.dateTimeOriginal).toBeDefined(); - expect(assetInfo.exifInfo?.latitude).toBeCloseTo(51.5074, 4); - expect(assetInfo.exifInfo?.longitude).toBeCloseTo(-0.1278, 4); - // Should fall back to file timestamps, which we set to 2020-01-01 - expect(new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime()).toBe( - new Date('2020-01-01T00:00:00.000Z').getTime(), - ); - }); - }); - - /* - * NOTE: The following EXIF date tags are NOT effectively usable with JPEG test files: - * - * NOT WRITABLE to JPEG: - * - MediaCreateDate: Can be read from video files but not written to JPEG - * - DateTimeCreated: Read-only tag in JPEG format - * - DateTimeUTC: Cannot be written to JPEG files - * - SonyDateTime2: Proprietary Sony tag, not writable to JPEG - * - SubSecMediaCreateDate: Tag not defined for JPEG format - * - SourceImageCreateTime: Non-standard insta360 tag, not writable to JPEG - * - * WRITABLE but NOT READABLE from JPEG: - * - SubSecDateTimeOriginal: Can be written but not read back from JPEG - * - SubSecCreateDate: Can be written but not read back from JPEG - * - * EFFECTIVELY TESTABLE TAGS (writable and readable): - * - DateTimeOriginal ✓ - * - CreateDate ✓ - * - CreationDate ✓ - * - GPSDateTime ✓ - * - * The metadata service correctly handles non-readable tags and will fall back to - * file timestamps when only non-readable tags are present. - */ - - describe('Date tag priority order', () => { - it('should respect the complete date tag priority order', async () => { - // Test cases using only EFFECTIVELY TESTABLE tags (writable AND readable from JPEG) - const testCases = [ - { - name: 'DateTimeOriginal has highest priority among testable tags', - exifData: { - DateTimeOriginal: '2023:04:04 04:00:00', // TESTABLE - highest priority among readable tags - CreateDate: '2023:05:05 05:00:00', // TESTABLE - CreationDate: '2023:07:07 07:00:00', // TESTABLE - GPSDateTime: '2023:10:10 10:00:00', // TESTABLE - }, - expectedDate: '2023-04-04T04:00:00.000Z', - }, - { - name: 'CreationDate when DateTimeOriginal missing', - exifData: { - CreationDate: '2023:05:05 05:00:00', // TESTABLE - CreateDate: '2023:07:07 07:00:00', // TESTABLE - GPSDateTime: '2023:10:10 10:00:00', // TESTABLE - }, - expectedDate: '2023-05-05T05:00:00.000Z', - }, - { - name: 'CreationDate when standard EXIF tags missing', - exifData: { - CreationDate: '2023:07:07 07:00:00', // TESTABLE - GPSDateTime: '2023:10:10 10:00:00', // TESTABLE - }, - expectedDate: '2023-07-07T07:00:00.000Z', - }, - { - name: 'GPSDateTime when no other testable date tags present', - exifData: { - GPSDateTime: '2023:10:10 10:00:00', // TESTABLE - Make: 'SONY', - }, - expectedDate: '2023-10-10T10:00:00.000Z', - }, - ]; - - for (const testCase of testCases) { - const { imageBytes, filename } = await createTestImageWithExif( - `${testCase.name.replaceAll(/\s+/g, '-').toLowerCase()}.jpg`, - testCase.exifData, - ); - - const asset = await utils.createAsset(admin.accessToken, { - assetData: { - filename, - bytes: imageBytes, - }, - }); - - await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id }); - - const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) }); - - expect(assetInfo.exifInfo?.dateTimeOriginal, `Failed for: ${testCase.name}`).toBeDefined(); - expect( - new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime(), - `Date mismatch for: ${testCase.name}`, - ).toBe(new Date(testCase.expectedDate).getTime()); - } - }); - }); - - describe('Edge cases for date tag handling', () => { - it('should fall back to file timestamps with GPSDateStamp alone', async () => { - const { imageBytes, filename } = await createTestImageWithExif('gps-datestamp-only.jpg', { - GPSDateStamp: '2023:08:08', // Date-only tag, should not be used for dateTimeOriginal - // Intentionally no GPSTimeStamp - // Exclude all other date tags - SubSecDateTimeOriginal: undefined, - DateTimeOriginal: undefined, - SubSecCreateDate: undefined, - SubSecMediaCreateDate: undefined, - CreateDate: undefined, - MediaCreateDate: undefined, - CreationDate: undefined, - DateTimeCreated: undefined, - TimeCreated: undefined, - GPSDateTime: undefined, - DateTimeUTC: undefined, - }); - - const oldDate = new Date('2020-01-01T00:00:00.000Z'); - const asset = await utils.createAsset(admin.accessToken, { - assetData: { - filename, - bytes: imageBytes, - }, - fileCreatedAt: oldDate.toISOString(), - fileModifiedAt: oldDate.toISOString(), - }); - - await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id }); - - const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) }); - - expect(assetInfo.exifInfo?.dateTimeOriginal).toBeDefined(); - // Should fall back to file timestamps, which we set to 2020-01-01 - expect(new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime()).toBe( - new Date('2020-01-01T00:00:00.000Z').getTime(), - ); - }); - - it('should handle all testable date tags present to verify complete priority order', async () => { - const { imageBytes, filename } = await createTestImageWithExif('all-testable-date-tags.jpg', { - // All TESTABLE date tags to JPEG format (writable AND readable) - DateTimeOriginal: '2023:04:04 04:00:00', // TESTABLE - highest priority among readable tags - CreateDate: '2023:05:05 05:00:00', // TESTABLE - CreationDate: '2023:07:07 07:00:00', // TESTABLE - GPSDateTime: '2023:10:10 10:00:00', // TESTABLE - // Note: Excluded non-testable tags: - // SubSec tags: writable but not readable from JPEG - // Non-writable tags: MediaCreateDate, DateTimeCreated, DateTimeUTC, SonyDateTime2, etc. - // Time-only/date-only tags: already excluded from EXIF_DATE_TAGS - }); - - const asset = await utils.createAsset(admin.accessToken, { - assetData: { - filename, - bytes: imageBytes, - }, - }); - - await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id }); - - const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) }); - - expect(assetInfo.exifInfo?.dateTimeOriginal).toBeDefined(); - // Should use DateTimeOriginal as it has the highest priority among testable tags - expect(new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime()).toBe( - new Date('2023-04-04T04:00:00.000Z').getTime(), - ); - }); - - it('should use CreationDate when SubSec tags are missing', async () => { - const { imageBytes, filename } = await createTestImageWithExif('creation-date-priority.jpg', { - CreationDate: '2023:07:07 07:00:00', // WRITABLE - GPSDateTime: '2023:10:10 10:00:00', // WRITABLE - // Note: DateTimeCreated, DateTimeUTC, SonyDateTime2 are NOT writable to JPEG - // Note: TimeCreated and GPSDateStamp are excluded from EXIF_DATE_TAGS (time-only/date-only) - // Exclude SubSec and standard EXIF tags - SubSecDateTimeOriginal: undefined, - DateTimeOriginal: undefined, - SubSecCreateDate: undefined, - CreateDate: undefined, - }); - - const asset = await utils.createAsset(admin.accessToken, { - assetData: { - filename, - bytes: imageBytes, - }, - }); - - await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id }); - - const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) }); - - expect(assetInfo.exifInfo?.dateTimeOriginal).toBeDefined(); - // Should use CreationDate when available - expect(new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime()).toBe( - new Date('2023-07-07T07:00:00.000Z').getTime(), - ); - }); - - it('should skip invalid date formats and use next valid tag', async () => { - const { imageBytes, filename } = await createTestImageWithExif('invalid-date-handling.jpg', { - // Note: Testing invalid date handling with only WRITABLE tags - GPSDateTime: '2023:10:10 10:00:00', // WRITABLE - Valid date - CreationDate: '2023:13:13 13:00:00', // WRITABLE - Valid date - // Note: TimeCreated excluded (time-only), DateTimeCreated not writable to JPEG - // Exclude other date tags - SubSecDateTimeOriginal: undefined, - DateTimeOriginal: undefined, - SubSecCreateDate: undefined, - CreateDate: undefined, - }); - - const asset = await utils.createAsset(admin.accessToken, { - assetData: { - filename, - bytes: imageBytes, - }, - }); - - await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id }); - - const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) }); - - expect(assetInfo.exifInfo?.dateTimeOriginal).toBeDefined(); - // Should skip invalid dates and use the first valid one (GPSDateTime) - expect(new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime()).toBe( - new Date('2023-10-10T10:00:00.000Z').getTime(), - ); - }); - }); - }); - }); - describe('POST /assets/exist', () => { it('ignores invalid deviceAssetIds', async () => { const response = await utils.checkExistingAssets(user1.accessToken, { diff --git a/e2e/src/api/specs/jobs.e2e-spec.ts b/e2e/src/api/specs/jobs.e2e-spec.ts index a9afd8475f..be7984404b 100644 --- a/e2e/src/api/specs/jobs.e2e-spec.ts +++ b/e2e/src/api/specs/jobs.e2e-spec.ts @@ -1,4 +1,4 @@ -import { JobCommand, JobName, LoginResponseDto, updateConfig } from '@immich/sdk'; +import { LoginResponseDto, QueueCommand, QueueName, updateConfig } from '@immich/sdk'; import { cpSync, rmSync } from 'node:fs'; import { readFile } from 'node:fs/promises'; import { basename } from 'node:path'; @@ -17,28 +17,28 @@ describe('/jobs', () => { describe('PUT /jobs', () => { afterEach(async () => { - await utils.jobCommand(admin.accessToken, JobName.MetadataExtraction, { - command: JobCommand.Resume, + await utils.queueCommand(admin.accessToken, QueueName.MetadataExtraction, { + command: QueueCommand.Resume, force: false, }); - await utils.jobCommand(admin.accessToken, JobName.ThumbnailGeneration, { - command: JobCommand.Resume, + await utils.queueCommand(admin.accessToken, QueueName.ThumbnailGeneration, { + command: QueueCommand.Resume, force: false, }); - await utils.jobCommand(admin.accessToken, JobName.FaceDetection, { - command: JobCommand.Resume, + await utils.queueCommand(admin.accessToken, QueueName.FaceDetection, { + command: QueueCommand.Resume, force: false, }); - await utils.jobCommand(admin.accessToken, JobName.SmartSearch, { - command: JobCommand.Resume, + await utils.queueCommand(admin.accessToken, QueueName.SmartSearch, { + command: QueueCommand.Resume, force: false, }); - await utils.jobCommand(admin.accessToken, JobName.DuplicateDetection, { - command: JobCommand.Resume, + await utils.queueCommand(admin.accessToken, QueueName.DuplicateDetection, { + command: QueueCommand.Resume, force: false, }); @@ -59,8 +59,8 @@ describe('/jobs', () => { it('should queue metadata extraction for missing assets', async () => { const path = `${testAssetDir}/formats/raw/Nikon/D700/philadelphia.nef`; - await utils.jobCommand(admin.accessToken, JobName.MetadataExtraction, { - command: JobCommand.Pause, + await utils.queueCommand(admin.accessToken, QueueName.MetadataExtraction, { + command: QueueCommand.Pause, force: false, }); @@ -77,20 +77,20 @@ describe('/jobs', () => { expect(asset.exifInfo?.make).toBeNull(); } - await utils.jobCommand(admin.accessToken, JobName.MetadataExtraction, { - command: JobCommand.Empty, + await utils.queueCommand(admin.accessToken, QueueName.MetadataExtraction, { + command: QueueCommand.Empty, force: false, }); await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction'); - await utils.jobCommand(admin.accessToken, JobName.MetadataExtraction, { - command: JobCommand.Resume, + await utils.queueCommand(admin.accessToken, QueueName.MetadataExtraction, { + command: QueueCommand.Resume, force: false, }); - await utils.jobCommand(admin.accessToken, JobName.MetadataExtraction, { - command: JobCommand.Start, + await utils.queueCommand(admin.accessToken, QueueName.MetadataExtraction, { + command: QueueCommand.Start, force: false, }); @@ -124,8 +124,8 @@ describe('/jobs', () => { cpSync(`${testAssetDir}/formats/raw/Nikon/D80/glarus.nef`, path); - await utils.jobCommand(admin.accessToken, JobName.MetadataExtraction, { - command: JobCommand.Start, + await utils.queueCommand(admin.accessToken, QueueName.MetadataExtraction, { + command: QueueCommand.Start, force: false, }); @@ -144,8 +144,8 @@ describe('/jobs', () => { it('should queue thumbnail extraction for assets missing thumbs', async () => { const path = `${testAssetDir}/albums/nature/tanners_ridge.jpg`; - await utils.jobCommand(admin.accessToken, JobName.ThumbnailGeneration, { - command: JobCommand.Pause, + await utils.queueCommand(admin.accessToken, QueueName.ThumbnailGeneration, { + command: QueueCommand.Pause, force: false, }); @@ -153,32 +153,32 @@ describe('/jobs', () => { assetData: { bytes: await readFile(path), filename: basename(path) }, }); - await utils.waitForQueueFinish(admin.accessToken, JobName.MetadataExtraction); - await utils.waitForQueueFinish(admin.accessToken, JobName.ThumbnailGeneration); + await utils.waitForQueueFinish(admin.accessToken, QueueName.MetadataExtraction); + await utils.waitForQueueFinish(admin.accessToken, QueueName.ThumbnailGeneration); const assetBefore = await utils.getAssetInfo(admin.accessToken, id); expect(assetBefore.thumbhash).toBeNull(); - await utils.jobCommand(admin.accessToken, JobName.ThumbnailGeneration, { - command: JobCommand.Empty, + await utils.queueCommand(admin.accessToken, QueueName.ThumbnailGeneration, { + command: QueueCommand.Empty, force: false, }); - await utils.waitForQueueFinish(admin.accessToken, JobName.MetadataExtraction); - await utils.waitForQueueFinish(admin.accessToken, JobName.ThumbnailGeneration); + await utils.waitForQueueFinish(admin.accessToken, QueueName.MetadataExtraction); + await utils.waitForQueueFinish(admin.accessToken, QueueName.ThumbnailGeneration); - await utils.jobCommand(admin.accessToken, JobName.ThumbnailGeneration, { - command: JobCommand.Resume, + await utils.queueCommand(admin.accessToken, QueueName.ThumbnailGeneration, { + command: QueueCommand.Resume, force: false, }); - await utils.jobCommand(admin.accessToken, JobName.ThumbnailGeneration, { - command: JobCommand.Start, + await utils.queueCommand(admin.accessToken, QueueName.ThumbnailGeneration, { + command: QueueCommand.Start, force: false, }); - await utils.waitForQueueFinish(admin.accessToken, JobName.MetadataExtraction); - await utils.waitForQueueFinish(admin.accessToken, JobName.ThumbnailGeneration); + await utils.waitForQueueFinish(admin.accessToken, QueueName.MetadataExtraction); + await utils.waitForQueueFinish(admin.accessToken, QueueName.ThumbnailGeneration); const assetAfter = await utils.getAssetInfo(admin.accessToken, id); expect(assetAfter.thumbhash).not.toBeNull(); @@ -193,26 +193,26 @@ describe('/jobs', () => { assetData: { bytes: await readFile(path), filename: basename(path) }, }); - await utils.waitForQueueFinish(admin.accessToken, JobName.MetadataExtraction); - await utils.waitForQueueFinish(admin.accessToken, JobName.ThumbnailGeneration); + await utils.waitForQueueFinish(admin.accessToken, QueueName.MetadataExtraction); + await utils.waitForQueueFinish(admin.accessToken, QueueName.ThumbnailGeneration); const assetBefore = await utils.getAssetInfo(admin.accessToken, id); cpSync(`${testAssetDir}/albums/nature/notocactus_minimus.jpg`, path); - await utils.jobCommand(admin.accessToken, JobName.ThumbnailGeneration, { - command: JobCommand.Resume, + await utils.queueCommand(admin.accessToken, QueueName.ThumbnailGeneration, { + command: QueueCommand.Resume, force: false, }); // This runs the missing thumbnail job - await utils.jobCommand(admin.accessToken, JobName.ThumbnailGeneration, { - command: JobCommand.Start, + await utils.queueCommand(admin.accessToken, QueueName.ThumbnailGeneration, { + command: QueueCommand.Start, force: false, }); - await utils.waitForQueueFinish(admin.accessToken, JobName.MetadataExtraction); - await utils.waitForQueueFinish(admin.accessToken, JobName.ThumbnailGeneration); + await utils.waitForQueueFinish(admin.accessToken, QueueName.MetadataExtraction); + await utils.waitForQueueFinish(admin.accessToken, QueueName.ThumbnailGeneration); const assetAfter = await utils.getAssetInfo(admin.accessToken, id); diff --git a/e2e/src/api/specs/server.e2e-spec.ts b/e2e/src/api/specs/server.e2e-spec.ts index c89280f579..adf2526856 100644 --- a/e2e/src/api/specs/server.e2e-spec.ts +++ b/e2e/src/api/specs/server.e2e-spec.ts @@ -113,6 +113,7 @@ describe('/server', () => { importFaces: false, oauth: false, oauthAutoLaunch: false, + ocr: false, passwordLogin: true, search: true, sidecar: true, diff --git a/e2e/src/api/specs/tag.e2e-spec.ts b/e2e/src/api/specs/tag.e2e-spec.ts index 7b645f8bd4..d69536f3a3 100644 --- a/e2e/src/api/specs/tag.e2e-spec.ts +++ b/e2e/src/api/specs/tag.e2e-spec.ts @@ -582,7 +582,7 @@ describe('/tags', () => { expect(body).toEqual([expect.objectContaining({ id: userAsset.id, success: true })]); }); - it('should remove duplicate assets only once', async () => { + it.skip('should remove duplicate assets only once', async () => { const tagA = await create(user.accessToken, { name: 'TagA' }); await tagAssets( { id: tagA.id, bulkIdsDto: { ids: [userAsset.id] } }, diff --git a/e2e/src/api/specs/user-admin.e2e-spec.ts b/e2e/src/api/specs/user-admin.e2e-spec.ts index b0696dcada..793c508a36 100644 --- a/e2e/src/api/specs/user-admin.e2e-spec.ts +++ b/e2e/src/api/specs/user-admin.e2e-spec.ts @@ -1,5 +1,6 @@ import { LoginResponseDto, + QueueName, createStack, deleteUserAdmin, getMyUser, @@ -327,6 +328,8 @@ describe('/admin/users', () => { { headers: asBearerAuth(user.accessToken) }, ); + await utils.waitForQueueFinish(admin.accessToken, QueueName.BackgroundTask); + const { status, body } = await request(app) .delete(`/admin/users/${user.userId}`) .send({ force: true }) diff --git a/e2e/src/cli/specs/upload.e2e-spec.ts b/e2e/src/cli/specs/upload.e2e-spec.ts index 8249b9b360..b53b4403f8 100644 --- a/e2e/src/cli/specs/upload.e2e-spec.ts +++ b/e2e/src/cli/specs/upload.e2e-spec.ts @@ -442,6 +442,176 @@ describe(`immich upload`, () => { }); }); + describe('immich upload --delete-duplicates', () => { + it('should delete local duplicate files', async () => { + const { + stderr: firstStderr, + stdout: firstStdout, + exitCode: firstExitCode, + } = await immichCli(['upload', `${testAssetDir}/albums/nature/silver_fir.jpg`]); + expect(firstStderr).toContain('{message}'); + expect(firstStdout.split('\n')).toEqual( + expect.arrayContaining([expect.stringContaining('Successfully uploaded 1 new asset')]), + ); + expect(firstExitCode).toBe(0); + + await mkdir(`/tmp/albums/nature`, { recursive: true }); + await symlink(`${testAssetDir}/albums/nature/silver_fir.jpg`, `/tmp/albums/nature/silver_fir.jpg`); + + // Upload with --delete-duplicates flag + const { stderr, stdout, exitCode } = await immichCli([ + 'upload', + `/tmp/albums/nature/silver_fir.jpg`, + '--delete-duplicates', + ]); + + // Check that the duplicate file was deleted + const files = await readdir(`/tmp/albums/nature`); + await rm(`/tmp/albums/nature`, { recursive: true }); + expect(files.length).toBe(0); + + expect(stdout.split('\n')).toEqual( + expect.arrayContaining([ + expect.stringContaining('Found 0 new files and 1 duplicate'), + expect.stringContaining('All assets were already uploaded, nothing to do'), + ]), + ); + expect(stderr).toContain('{message}'); + expect(exitCode).toBe(0); + + // Verify no new assets were uploaded + const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) }); + expect(assets.total).toBe(1); + }); + + it('should have accurate dry run with --delete-duplicates', async () => { + const { + stderr: firstStderr, + stdout: firstStdout, + exitCode: firstExitCode, + } = await immichCli(['upload', `${testAssetDir}/albums/nature/silver_fir.jpg`]); + expect(firstStderr).toContain('{message}'); + expect(firstStdout.split('\n')).toEqual( + expect.arrayContaining([expect.stringContaining('Successfully uploaded 1 new asset')]), + ); + expect(firstExitCode).toBe(0); + + await mkdir(`/tmp/albums/nature`, { recursive: true }); + await symlink(`${testAssetDir}/albums/nature/silver_fir.jpg`, `/tmp/albums/nature/silver_fir.jpg`); + + // Upload with --delete-duplicates and --dry-run flags + const { stderr, stdout, exitCode } = await immichCli([ + 'upload', + `/tmp/albums/nature/silver_fir.jpg`, + '--delete-duplicates', + '--dry-run', + ]); + + // Check that the duplicate file was NOT deleted in dry run mode + const files = await readdir(`/tmp/albums/nature`); + await rm(`/tmp/albums/nature`, { recursive: true }); + expect(files.length).toBe(1); + + expect(stdout.split('\n')).toEqual( + expect.arrayContaining([ + expect.stringContaining('Found 0 new files and 1 duplicate'), + expect.stringContaining('Would have deleted 1 local asset'), + ]), + ); + expect(stderr).toContain('{message}'); + expect(exitCode).toBe(0); + + // Verify no new assets were uploaded + const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) }); + expect(assets.total).toBe(1); + }); + + it('should work with both --delete and --delete-duplicates flags', async () => { + // First, upload a file to create a duplicate on the server + const { + stderr: firstStderr, + stdout: firstStdout, + exitCode: firstExitCode, + } = await immichCli(['upload', `${testAssetDir}/albums/nature/silver_fir.jpg`]); + expect(firstStderr).toContain('{message}'); + expect(firstStdout.split('\n')).toEqual( + expect.arrayContaining([expect.stringContaining('Successfully uploaded 1 new asset')]), + ); + expect(firstExitCode).toBe(0); + + // Both new and duplicate files + await mkdir(`/tmp/albums/nature`, { recursive: true }); + await symlink(`${testAssetDir}/albums/nature/silver_fir.jpg`, `/tmp/albums/nature/silver_fir.jpg`); // duplicate + await symlink(`${testAssetDir}/albums/nature/el_torcal_rocks.jpg`, `/tmp/albums/nature/el_torcal_rocks.jpg`); // new + + // Upload with both --delete and --delete-duplicates flags + const { stderr, stdout, exitCode } = await immichCli([ + 'upload', + `/tmp/albums/nature`, + '--delete', + '--delete-duplicates', + ]); + + // Check that both files were deleted (new file due to --delete, duplicate due to --delete-duplicates) + const files = await readdir(`/tmp/albums/nature`); + await rm(`/tmp/albums/nature`, { recursive: true }); + expect(files.length).toBe(0); + + expect(stdout.split('\n')).toEqual( + expect.arrayContaining([ + expect.stringContaining('Found 1 new files and 1 duplicate'), + expect.stringContaining('Successfully uploaded 1 new asset'), + expect.stringContaining('Deleting assets that have been uploaded'), + ]), + ); + expect(stderr).toContain('{message}'); + expect(exitCode).toBe(0); + + // Verify one new asset was uploaded (total should be 2 now) + const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) }); + expect(assets.total).toBe(2); + }); + + it('should only delete duplicates when --delete-duplicates is used without --delete', async () => { + const { + stderr: firstStderr, + stdout: firstStdout, + exitCode: firstExitCode, + } = await immichCli(['upload', `${testAssetDir}/albums/nature/silver_fir.jpg`]); + expect(firstStderr).toContain('{message}'); + expect(firstStdout.split('\n')).toEqual( + expect.arrayContaining([expect.stringContaining('Successfully uploaded 1 new asset')]), + ); + expect(firstExitCode).toBe(0); + + // Both new and duplicate files + await mkdir(`/tmp/albums/nature`, { recursive: true }); + await symlink(`${testAssetDir}/albums/nature/silver_fir.jpg`, `/tmp/albums/nature/silver_fir.jpg`); // duplicate + await symlink(`${testAssetDir}/albums/nature/el_torcal_rocks.jpg`, `/tmp/albums/nature/el_torcal_rocks.jpg`); // new + + // Upload with only --delete-duplicates flag + const { stderr, stdout, exitCode } = await immichCli(['upload', `/tmp/albums/nature`, '--delete-duplicates']); + + // Check that only the duplicate was deleted, new file should remain + const files = await readdir(`/tmp/albums/nature`); + await rm(`/tmp/albums/nature`, { recursive: true }); + expect(files).toEqual(['el_torcal_rocks.jpg']); + + expect(stdout.split('\n')).toEqual( + expect.arrayContaining([ + expect.stringContaining('Found 1 new files and 1 duplicate'), + expect.stringContaining('Successfully uploaded 1 new asset'), + ]), + ); + expect(stderr).toContain('{message}'); + expect(exitCode).toBe(0); + + // Verify one new asset was uploaded (total should be 2 now) + const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) }); + expect(assets.total).toBe(2); + }); + }); + describe('immich upload --skip-hash', () => { it('should skip hashing', async () => { const filename = `albums/nature/silver_fir.jpg`; diff --git a/e2e/src/generate-date-tag-test-images.ts b/e2e/src/generate-date-tag-test-images.ts deleted file mode 100644 index 34cc956416..0000000000 --- a/e2e/src/generate-date-tag-test-images.ts +++ /dev/null @@ -1,178 +0,0 @@ -#!/usr/bin/env node - -/** - * Script to generate test images with additional EXIF date tags - * This creates actual JPEG images with embedded metadata for testing - * Images are generated into e2e/test-assets/metadata/dates/ - */ - -import { execSync } from 'node:child_process'; -import { writeFileSync } from 'node:fs'; -import { dirname, join } from 'node:path'; -import { fileURLToPath } from 'node:url'; -import sharp from 'sharp'; - -interface TestImage { - filename: string; - description: string; - exifTags: Record; -} - -const testImages: TestImage[] = [ - { - filename: 'time-created.jpg', - description: 'Image with TimeCreated tag', - exifTags: { - TimeCreated: '2023:11:15 14:30:00', - Make: 'Canon', - Model: 'EOS R5', - }, - }, - { - filename: 'gps-datetime.jpg', - description: 'Image with GPSDateTime and coordinates', - exifTags: { - GPSDateTime: '2023:11:15 12:30:00Z', - GPSLatitude: '37.7749', - GPSLongitude: '-122.4194', - GPSLatitudeRef: 'N', - GPSLongitudeRef: 'W', - }, - }, - { - filename: 'datetime-utc.jpg', - description: 'Image with DateTimeUTC tag', - exifTags: { - DateTimeUTC: '2023:11:15 10:30:00', - Make: 'Nikon', - Model: 'D850', - }, - }, - { - filename: 'gps-datestamp.jpg', - description: 'Image with GPSDateStamp and GPSTimeStamp', - exifTags: { - GPSDateStamp: '2023:11:15', - GPSTimeStamp: '08:30:00', - GPSLatitude: '51.5074', - GPSLongitude: '-0.1278', - GPSLatitudeRef: 'N', - GPSLongitudeRef: 'W', - }, - }, - { - filename: 'sony-datetime2.jpg', - description: 'Sony camera image with SonyDateTime2 tag', - exifTags: { - SonyDateTime2: '2023:11:15 06:30:00', - Make: 'SONY', - Model: 'ILCE-7RM5', - }, - }, - { - filename: 'date-priority-test.jpg', - description: 'Image with multiple date tags to test priority', - exifTags: { - SubSecDateTimeOriginal: '2023:01:01 01:00:00', - DateTimeOriginal: '2023:02:02 02:00:00', - SubSecCreateDate: '2023:03:03 03:00:00', - CreateDate: '2023:04:04 04:00:00', - CreationDate: '2023:05:05 05:00:00', - DateTimeCreated: '2023:06:06 06:00:00', - TimeCreated: '2023:07:07 07:00:00', - GPSDateTime: '2023:08:08 08:00:00', - DateTimeUTC: '2023:09:09 09:00:00', - GPSDateStamp: '2023:10:10', - SonyDateTime2: '2023:11:11 11:00:00', - }, - }, - { - filename: 'new-tags-only.jpg', - description: 'Image with only additional date tags (no standard tags)', - exifTags: { - TimeCreated: '2023:12:01 15:45:30', - GPSDateTime: '2023:12:01 13:45:30Z', - DateTimeUTC: '2023:12:01 13:45:30', - GPSDateStamp: '2023:12:01', - SonyDateTime2: '2023:12:01 08:45:30', - GPSLatitude: '40.7128', - GPSLongitude: '-74.0060', - GPSLatitudeRef: 'N', - GPSLongitudeRef: 'W', - }, - }, -]; - -const generateTestImages = async (): Promise => { - // Target directory: e2e/test-assets/metadata/dates/ - // Current file is in: e2e/src/ - const __filename = fileURLToPath(import.meta.url); - const __dirname = dirname(__filename); - const targetDir = join(__dirname, '..', 'test-assets', 'metadata', 'dates'); - - console.log('Generating test images with additional EXIF date tags...'); - console.log(`Target directory: ${targetDir}`); - - for (const image of testImages) { - try { - const imagePath = join(targetDir, image.filename); - - // Create unique JPEG file using Sharp - const r = Math.floor(Math.random() * 256); - const g = Math.floor(Math.random() * 256); - const b = Math.floor(Math.random() * 256); - - const jpegData = await sharp({ - create: { - width: 100, - height: 100, - channels: 3, - background: { r, g, b }, - }, - }) - .jpeg({ quality: 90 }) - .toBuffer(); - - writeFileSync(imagePath, jpegData); - - // Build exiftool command to add EXIF data - const exifArgs = Object.entries(image.exifTags) - .map(([tag, value]) => `-${tag}="${value}"`) - .join(' '); - - const command = `exiftool ${exifArgs} -overwrite_original "${imagePath}"`; - - console.log(`Creating ${image.filename}: ${image.description}`); - execSync(command, { stdio: 'pipe' }); - - // Verify the tags were written - const verifyCommand = `exiftool -json "${imagePath}"`; - const result = execSync(verifyCommand, { encoding: 'utf8' }); - const metadata = JSON.parse(result)[0]; - - console.log(` ✓ Created with ${Object.keys(image.exifTags).length} EXIF tags`); - - // Log first date tag found for verification - const firstDateTag = Object.keys(image.exifTags).find( - (tag) => tag.includes('Date') || tag.includes('Time') || tag.includes('Created'), - ); - if (firstDateTag && metadata[firstDateTag]) { - console.log(` ✓ Verified ${firstDateTag}: ${metadata[firstDateTag]}`); - } - } catch (error) { - console.error(`Failed to create ${image.filename}:`, (error as Error).message); - } - } - - console.log('\nTest image generation complete!'); - console.log('Files created in:', targetDir); - console.log('\nTo test these images:'); - console.log(`cd ${targetDir} && exiftool -time:all -gps:all *.jpg`); -}; - -export { generateTestImages }; - -// Run the generator if this file is executed directly -if (import.meta.url === `file://${process.argv[1]}`) { - generateTestImages().catch(console.error); -} diff --git a/e2e/src/responses.ts b/e2e/src/responses.ts index b14aedf895..27e6091206 100644 --- a/e2e/src/responses.ts +++ b/e2e/src/responses.ts @@ -119,5 +119,6 @@ export const deviceDto = { isPendingSyncReset: false, deviceOS: '', deviceType: '', + appVersion: null, }, }; diff --git a/e2e/src/utils.ts b/e2e/src/utils.ts index b33d6cb190..8f34bbe40a 100644 --- a/e2e/src/utils.ts +++ b/e2e/src/utils.ts @@ -1,5 +1,4 @@ import { - AllJobStatusResponseDto, AssetMediaCreateDto, AssetMediaResponseDto, AssetResponseDto, @@ -7,11 +6,12 @@ import { CheckExistingAssetsDto, CreateAlbumDto, CreateLibraryDto, - JobCommandDto, - JobName, MetadataSearchDto, Permission, PersonCreateDto, + QueueCommandDto, + QueueName, + QueuesResponseDto, SharedLinkCreateDto, UpdateLibraryDto, UserAdminCreateDto, @@ -27,14 +27,14 @@ import { createStack, createUserAdmin, deleteAssets, - getAllJobsStatus, getAssetInfo, getConfig, getConfigDefaults, + getQueuesLegacy, login, + runQueueCommandLegacy, scanLibrary, searchAssets, - sendJobCommand, setBaseUrl, signUpAdmin, tagAssets, @@ -477,8 +477,8 @@ export const utils = { tagAssets: (accessToken: string, tagId: string, assetIds: string[]) => tagAssets({ id: tagId, bulkIdsDto: { ids: assetIds } }, { headers: asBearerAuth(accessToken) }), - jobCommand: async (accessToken: string, jobName: JobName, jobCommandDto: JobCommandDto) => - sendJobCommand({ id: jobName, jobCommandDto }, { headers: asBearerAuth(accessToken) }), + queueCommand: async (accessToken: string, name: QueueName, queueCommandDto: QueueCommandDto) => + runQueueCommandLegacy({ name, queueCommandDto }, { headers: asBearerAuth(accessToken) }), setAuthCookies: async (context: BrowserContext, accessToken: string, domain = '127.0.0.1') => await context.addCookies([ @@ -524,13 +524,13 @@ export const utils = { await updateConfig({ systemConfigDto: defaultConfig }, { headers: asBearerAuth(accessToken) }); }, - isQueueEmpty: async (accessToken: string, queue: keyof AllJobStatusResponseDto) => { - const queues = await getAllJobsStatus({ headers: asBearerAuth(accessToken) }); + isQueueEmpty: async (accessToken: string, queue: keyof QueuesResponseDto) => { + const queues = await getQueuesLegacy({ headers: asBearerAuth(accessToken) }); const jobCounts = queues[queue].jobCounts; return !jobCounts.active && !jobCounts.waiting; }, - waitForQueueFinish: (accessToken: string, queue: keyof AllJobStatusResponseDto, ms?: number) => { + waitForQueueFinish: (accessToken: string, queue: keyof QueuesResponseDto, ms?: number) => { // eslint-disable-next-line no-async-promise-executor return new Promise(async (resolve, reject) => { const timeout = setTimeout(() => reject(new Error('Timed out waiting for queue to empty')), ms || 10_000); diff --git a/e2e/src/web/specs/asset-viewer/navbar.e2e-spec.ts b/e2e/src/web/specs/asset-viewer/navbar.e2e-spec.ts index 4f20e2db19..8fcd1bbdb4 100644 --- a/e2e/src/web/specs/asset-viewer/navbar.e2e-spec.ts +++ b/e2e/src/web/specs/asset-viewer/navbar.e2e-spec.ts @@ -59,7 +59,7 @@ test.describe('Asset Viewer Navbar', () => { await page.goto(`/photos/${asset.id}`); await page.waitForSelector('#immich-asset-viewer'); await page.keyboard.press('f'); - await expect(page.locator('#notification-list').getByTestId('message')).toHaveText('Added to favorites'); + await expect(page.getByText('Added to favorites')).toBeVisible(); }); }); }); diff --git a/e2e/src/web/specs/asset-viewer/slideshow.e2e-spec.ts b/e2e/src/web/specs/asset-viewer/slideshow.e2e-spec.ts index 72bb3c5c59..c8cbc21588 100644 --- a/e2e/src/web/specs/asset-viewer/slideshow.e2e-spec.ts +++ b/e2e/src/web/specs/asset-viewer/slideshow.e2e-spec.ts @@ -51,6 +51,6 @@ test.describe('Slideshow', () => { await expect(page.getByRole('button', { name: 'Exit Slideshow' })).toBeVisible(); await page.keyboard.press('f'); - await expect(page.locator('#notification-list')).not.toBeVisible(); + await expect(page.getByText('Added to favorites')).not.toBeVisible(); }); }); diff --git a/e2e/src/web/specs/auth.e2e-spec.ts b/e2e/src/web/specs/auth.e2e-spec.ts index 173131ec5e..a14a177917 100644 --- a/e2e/src/web/specs/auth.e2e-spec.ts +++ b/e2e/src/web/specs/auth.e2e-spec.ts @@ -38,6 +38,7 @@ test.describe('Registration', () => { await page.getByRole('button', { name: 'User Privacy' }).click(); await page.getByRole('button', { name: 'Storage Template' }).click(); await page.getByRole('button', { name: 'Backups' }).click(); + await page.getByRole('button', { name: 'Mobile App' }).click(); await page.getByRole('button', { name: 'Done' }).click(); // success @@ -85,6 +86,7 @@ test.describe('Registration', () => { await page.getByRole('button', { name: 'Theme' }).click(); await page.getByRole('button', { name: 'Language' }).click(); await page.getByRole('button', { name: 'User Privacy' }).click(); + await page.getByRole('button', { name: 'Mobile App' }).click(); await page.getByRole('button', { name: 'Done' }).click(); // success diff --git a/e2e/src/web/specs/user-admin.e2e-spec.ts b/e2e/src/web/specs/user-admin.e2e-spec.ts index 3d64e47aef..611a1b3dec 100644 --- a/e2e/src/web/specs/user-admin.e2e-spec.ts +++ b/e2e/src/web/specs/user-admin.e2e-spec.ts @@ -52,7 +52,7 @@ test.describe('User Administration', () => { await page.goto(`/admin/users/${user.userId}`); - await page.getByRole('button', { name: 'Edit user' }).click(); + await page.getByRole('button', { name: 'Edit' }).click(); await expect(page.getByLabel('Admin User')).not.toBeChecked(); await page.getByText('Admin User').click(); await expect(page.getByLabel('Admin User')).toBeChecked(); @@ -77,7 +77,7 @@ test.describe('User Administration', () => { await page.goto(`/admin/users/${user.userId}`); - await page.getByRole('button', { name: 'Edit user' }).click(); + await page.getByRole('button', { name: 'Edit' }).click(); await expect(page.getByLabel('Admin User')).toBeChecked(); await page.getByText('Admin User').click(); await expect(page.getByLabel('Admin User')).not.toBeChecked(); diff --git a/e2e/test-assets b/e2e/test-assets index 37f60ea537..163c251744 160000 --- a/e2e/test-assets +++ b/e2e/test-assets @@ -1 +1 @@ -Subproject commit 37f60ea537c0228f5f92e4f42dc42f0bb39a6d7f +Subproject commit 163c251744e0a35d7ecfd02682452043f149fc2b diff --git a/i18n/af.json b/i18n/af.json index fce944504b..68e020b76e 100644 --- a/i18n/af.json +++ b/i18n/af.json @@ -170,7 +170,6 @@ "duplicates": "Duplikate", "duration": "Duur", "edit": "Wysig", - "edited": "Gewysigd", "search_by_description": "Soek by beskrywing", "search_by_description_example": "Stapdag in Sapa", "version": "Weergawe", diff --git a/i18n/ar.json b/i18n/ar.json index ec3745546f..229fa316f7 100644 --- a/i18n/ar.json +++ b/i18n/ar.json @@ -28,10 +28,12 @@ "add_to_album": "إضافة إلى ألبوم", "add_to_album_bottom_sheet_added": "تمت الاضافة الى {album}", "add_to_album_bottom_sheet_already_exists": "موجود مسبقا في {album}", + "add_to_album_bottom_sheet_some_local_assets": "لا يمكن إضافة بعض الأصول المحلية إلى الألبوم", "add_to_album_toggle": "تبديل التحديد لـ{album}", "add_to_albums": "إضافة الى البومات", "add_to_albums_count": "إضافه إلى البومات ({count})", "add_to_shared_album": "إضافة إلى ألبوم مشارك", + "add_upload_to_stack": "اضف رفع الى حزمة", "add_url": "إضافة رابط", "added_to_archive": "أُضيفت للأرشيف", "added_to_favorites": "أُضيفت للمفضلات", @@ -117,12 +119,19 @@ "library_settings": "المكتبة الخارجية", "library_settings_description": "إدارة إعدادات المكتبة الخارجية", "library_tasks_description": "مسح المكتبات الخارجية للعثور على الأصول الجديدة و/أو المتغيرة", - "library_watching_enable_description": "راقب المكتبات الخارجية لتغييرات الملفات", - "library_watching_settings": "مراقبة المكتبات (تجريبي)", + "library_watching_enable_description": "مراقبة المكتبات الخارجية لاكتشاف تغييرات الملفات", + "library_watching_settings": "مراقبة المكتبات [تجريبي]", "library_watching_settings_description": "راقب تلقائيًا التغييرات في الملفات", "logging_enable_description": "تفعيل تسجيل الأحداث", "logging_level_description": "عند التفعيل، أي مستوى تسجيل سيستخدم.", - "logging_settings": "تسجيل الاحداث", + "logging_settings": "السجلات", + "machine_learning_availability_checks": "تحقق من التوفر", + "machine_learning_availability_checks_description": "تحديد خوادم التعلم الآلي المتاحة تلقائيًا وإعطاءها الأولوية", + "machine_learning_availability_checks_enabled": "تفعيل عمليات فحص التوفر", + "machine_learning_availability_checks_interval": "فترة التحقق", + "machine_learning_availability_checks_interval_description": "الفترة الزمنية بالمللي ثانية بين عمليات فحص التوفر", + "machine_learning_availability_checks_timeout": "انتهت مدة انتظار الطلب", + "machine_learning_availability_checks_timeout_description": "مدة انتظار (بالمللي ثانية) لاختبارات توفر الخدمة", "machine_learning_clip_model": "نموذج CLIP", "machine_learning_clip_model_description": "اسم نموذج CLIP مدرجٌ هنا. يرجى ملاحظة أنه يجب إعادة تشغيل وظيفة \"البحث الذكي\" لجميع الصور بعد تغيير النموذج.", "machine_learning_duplicate_detection": "كشف التكرار", @@ -197,7 +206,7 @@ "note_cannot_be_changed_later": "ملاحظة: لا يمكن تغيير هذا لاحقًا!", "notification_email_from_address": "عنوان المرسل", "notification_email_from_address_description": "عنوان البريد الإلكتروني للمرسل، على سبيل المثال: \"Immich Photo Server noreply@example.com\". تاكد من استخدام عنوان بريد الكتروني يسمح لك بارسال البريد الالكتروني منه.", - "notification_email_host_description": "مضيف خادم البريد الإلكتروني (مثلًا: smtp.immich.app)", + "notification_email_host_description": "عنوان خادم البريد الإلكتروني (مثل smtp.immich.app)", "notification_email_ignore_certificate_errors": "تجاهل أخطاء الشهادة", "notification_email_ignore_certificate_errors_description": "تجاهل أخطاء التحقق من صحة شهادة TLS (غير مستحسن)", "notification_email_password_description": "كلمة المرور المستخدمة للمصادقة مع خادم البريد الإلكتروني", @@ -324,7 +333,7 @@ "transcoding_max_b_frames": "أقصى عدد من الإطارات B", "transcoding_max_b_frames_description": "القيم الأعلى تعزز كفاءة الضغط، ولكنها تبطئ عملية الترميز. قد لا تكون متوافقة مع التسريع العتادي على الأجهزة القديمة. قيمة 0 تعطل إطارات B، بينما تضبط القيمة -1 هذا القيمة تلقائيًا.", "transcoding_max_bitrate": "الحد الأقصى لمعدل البت", - "transcoding_max_bitrate_description": "يمكن أن يؤدي تعيين الحد الأقصى لمعدل البت إلى جعل أحجام الملفات أكثر قابلية للتنبؤ بها بتكلفة بسيطة بالنسبة للجودة. عند دقة 720 بكسل، تكون القيم النموذجية 2600 كيلو بت لـ VP9 أو HEVC، أو 4500 كيلو بت لـ H.264. معطل إذا تم ضبطه على 0.", + "transcoding_max_bitrate_description": "يتيح تعيين معدل البت الأقصى التحكم في حجم الملف مع تأثير طفيف على الجودة.عند دقة 720p، القيم المقترحة هي 2600 كيلوبت/ثانية لـ VP9 أو HEVC، و4500 كيلوبت/ثانية لـ H.264.يتم تعطيل الإعداد عند القيمة 0. إذا لم تُحدَّد وحدة، يُفترض k (كيلوبت/ثانية)؛ لذا فإن 5000، 5000k، و5M متكافئة.", "transcoding_max_keyframe_interval": "الحد الأقصى للفاصل الزمني للإطار الرئيسي", "transcoding_max_keyframe_interval_description": "يضبط الحد الأقصى لمسافة الإطار بين الإطارات الرئيسية. تؤدي القيم المنخفضة إلى زيادة سوء كفاءة الضغط، ولكنها تعمل على تحسين أوقات البحث وقد تعمل على تحسين الجودة في المشاهد ذات الحركة السريعة. 0 يضبط هذه القيمة تلقائيًا.", "transcoding_optimal_description": "مقاطع الفيديو ذات الدقة الأعلى من الدقة المستهدفة أو بتنسيق غير مقبول", @@ -342,7 +351,7 @@ "transcoding_target_resolution": "القرار المستهدف", "transcoding_target_resolution_description": "يمكن أن تحافظ الدقة الأعلى على المزيد من التفاصيل ولكنها تستغرق وقتًا أطول للتشفير، ولها أحجام ملفات أكبر، ويمكن أن تقلل من استجابة التطبيق.", "transcoding_temporal_aq": "التكميم التكيفي الزمني", - "transcoding_temporal_aq_description": "ينطبق فقط على NVENC. يزيد من جودة المشاهد عالية التفاصيل ومنخفضة الحركة. قد لا يكون متوافقًا مع الأجهزة القديمة.", + "transcoding_temporal_aq_description": "ينطبق فقط على NVENC. تعمل \"الكمّية التكيفية الزمنية\" على تحسين جودة المشاهد ذات التفاصيل الدقيقة والحركة البطيئة. قد لا يكون هذا الخيار متوافقًا مع الأجهزة القديمة.", "transcoding_threads": "الخيوط", "transcoding_threads_description": "تؤدي القيم الأعلى إلى تشفير أسرع، ولكنها تترك مساحة أقل للخادم لمعالجة المهام الأخرى أثناء النشاط. يجب ألا تزيد هذه القيمة عن عدد مراكز وحدة المعالجة المركزية. يزيد من الإستغلال إذا تم ضبطه على 0.", "transcoding_tone_mapping": "رسم الخرائط النغمية", @@ -394,6 +403,7 @@ "advanced_settings_prefer_remote_title": "تفضل الصور البعيدة", "advanced_settings_proxy_headers_subtitle": "عرف عناوين الوكيل التي يستخدمها Immich لارسال كل طلب شبكي", "advanced_settings_proxy_headers_title": "عناوين الوكيل", + "advanced_settings_readonly_mode_subtitle": "تتيح هذه الميزة وضع العرض فقط، حيث يمكن للمستخدم معاينة الصور فقط، بينما يتم تعطيل جميع الخيارات الأخرى مثل تحديد عدة صور، أو مشاركتها، أو بثها، أو حذفها. يمكن تفعيل/تعطيل وضع العرض فقط من خلال صورة المستخدم في الشاشة الرئيسية", "advanced_settings_readonly_mode_title": "وضع القراءة فقط", "advanced_settings_self_signed_ssl_subtitle": "تخطي التحقق من شهادة SSL لخادم النقطة النهائي. مكلوب للشهادات الموقعة ذاتيا.", "advanced_settings_self_signed_ssl_title": "السماح بشهادات SSL الموقعة ذاتيًا", @@ -422,6 +432,7 @@ "album_remove_user_confirmation": "هل أنت متأكد أنك تريد إزالة {user}؟", "album_search_not_found": "لم يتم ايجاد البوم مطابق لبحثك", "album_share_no_users": "يبدو أنك قمت بمشاركة هذا الألبوم مع جميع المستخدمين أو ليس لديك أي مستخدم للمشاركة معه.", + "album_summary": "ملخص الألبوم", "album_updated": "تم تحديث الألبوم", "album_updated_setting_description": "تلقي إشعارًا عبر البريد الإلكتروني عندما يحتوي الألبوم المشترك على محتويات جديدة", "album_user_left": "تم ترك {album}", @@ -460,6 +471,7 @@ "app_bar_signout_dialog_title": "خروج", "app_settings": "إعدادات التطبيق", "appears_in": "يظهر في", + "apply_count": "تطبيق ({count, number})", "archive": "الأرشيف", "archive_action_prompt": "{count} اضيف إلى الارشيف", "archive_or_unarchive_photo": "أرشفة الصورة أو إلغاء أرشفتها", @@ -492,6 +504,8 @@ "asset_restored_successfully": "تم استعادة الاصل بنجاح", "asset_skipped": "تم تخطيه", "asset_skipped_in_trash": "في سلة المهملات", + "asset_trashed": "اصول محذوفة", + "asset_troubleshoot": "استكشاف مشاكل الأصول", "asset_uploaded": "تم الرفع", "asset_uploading": "جارٍ الرفع…", "asset_viewer_settings_subtitle": "إدارة إعدادات عارض المعرض الخاص بك", @@ -499,7 +513,9 @@ "assets": "المحتويات", "assets_added_count": "تمت إضافة {count, plural, one {# محتوى} other {# محتويات}}", "assets_added_to_album_count": "تمت إضافة {count, plural, one {# الأصل} other {# الأصول}} إلى الألبوم", + "assets_added_to_albums_count": "تمت اضافة {assetTotal, plural, one {# اصل} other {# اصول}} to {albumTotal, plural, one {# البوم} other {# البومات}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} لايمكن اضافته الى الالبوم", + "assets_cannot_be_added_to_albums": "{count, plural, one {اصل} other {اصول}} لا يمكن إضافته إلى أي من الألبومات", "assets_count": "{count, plural, one {# محتوى} other {# محتويات}}", "assets_deleted_permanently": "{count} الاص(و)ل المحذوف(ه) بشكل دائم", "assets_deleted_permanently_from_server": "{count} الاص(و)ل المحذوف(ه) بشكل دائمي من خادم Immich", @@ -516,14 +532,17 @@ "assets_trashed_count": "تم إرسال {count, plural, one {# محتوى} other {# محتويات}} إلى سلة المهملات", "assets_trashed_from_server": "{count} الاص(و)ل المنقولة الى سلة المهملات من خادم Immich", "assets_were_part_of_album_count": "{count, plural, one {هذا المحتوى} other {هذه المحتويات}} في الألبوم بالفعل", + "assets_were_part_of_albums_count": "{count, plural, one {اصل هو} other {اصول هي}}بالفعل جزء من الألبومات", "authorized_devices": "الأجهزه المخولة", "automatic_endpoint_switching_subtitle": "اتصل محليا من خلال شبكه Wi-Fi عند توفرها و استخدم اتصالات بديله في الاماكن الاخرى", "automatic_endpoint_switching_title": "تبديل URL تلقائي", "autoplay_slideshow": "تشغيل تلقائي لعرض الشرائح", "back": "خلف", "back_close_deselect": "الرجوع أو الإغلاق أو إلغاء التحديد", + "background_backup_running_error": "يتم تشغيل النسخ الاحتياطي في الخلفية حاليًا، ولا يمكن بدء النسخ الاحتياطي اليدوي", "background_location_permission": "اذن الوصول للموقع في الخلفية", "background_location_permission_content": "للتمكن من تبديل الشبكه بالخلفية، Immich يحتاج*دائما* للحصول على موقع دقيق ليتمكن التطبيق من قرائة اسم شبكة الWi-Fi", + "background_options": "خيارات الخلفية", "backup": "نسخ احتياطي", "backup_album_selection_page_albums_device": "الالبومات على الجهاز ({count})", "backup_album_selection_page_albums_tap": "انقر للتضمين، وانقر نقرًا مزدوجًا للاستثناء", @@ -531,6 +550,7 @@ "backup_album_selection_page_select_albums": "حدد الألبومات", "backup_album_selection_page_selection_info": "معلومات الاختيار", "backup_album_selection_page_total_assets": "إجمالي الأصول الفريدة", + "backup_albums_sync": "مزامنة ألبومات النسخ الاحتياطي", "backup_all": "الجميع", "backup_background_service_backup_failed_message": "فشل في النسخ الاحتياطي للأصول. جارٍ إعادة المحاولة…", "backup_background_service_connection_failed_message": "فشل في الاتصال بالخادم. جارٍ إعادة المحاولة…", @@ -580,6 +600,7 @@ "backup_controller_page_turn_on": "قم بتشغيل النسخ الاحتياطي المقدمة", "backup_controller_page_uploading_file_info": "تحميل معلومات الملف", "backup_err_only_album": "لا يمكن إزالة الألبوم الوحيد", + "backup_error_sync_failed": "فشل المزامنة. لا يمكن معالجة النسخ الاحتياطي.", "backup_info_card_assets": "أصول", "backup_manual_cancelled": "ملغي", "backup_manual_in_progress": "قيد التحميل حاول مره اخرى", @@ -647,6 +668,8 @@ "change_pin_code": "تغيير رمز PIN", "change_your_password": "غير كلمة المرور الخاصة بك", "changed_visibility_successfully": "تم تغيير الرؤية بنجاح", + "charging": "الشحن", + "charging_requirement_mobile_backup": "يتطلب النسخ الاحتياطي في الخلفية أن يكون الجهاز قيد الشحن", "check_corrupt_asset_backup": "التحقق من وجود نسخ احتياطية فاسدة للاصول", "check_corrupt_asset_backup_button": "اجراء فحص", "check_corrupt_asset_backup_description": "قم بإجراء هذا الفحص فقط عبر شبكة Wi-Fi وبعد نسخ جميع الأصول احتياطيًا. قد يستغرق الإجراء بضع دقائق.", @@ -678,7 +701,6 @@ "comments_and_likes": "التعليقات والإعجابات", "comments_are_disabled": "التعليقات معطلة", "common_create_new_album": "إنشاء ألبوم جديد", - "common_server_error": "يرجى التحقق من اتصال الشبكة الخاص بك ، والتأكد من أن الجهاز قابل للوصول وإصدارات التطبيق/الجهاز متوافقة.", "completed": "اكتمل", "confirm": "تأكيد", "confirm_admin_password": "تأكيد كلمة مرور المسؤول", @@ -733,6 +755,7 @@ "create_user": "إنشاء مستخدم", "created": "تم الإنشاء", "created_at": "مخلوق", + "creating_linked_albums": "جاري إنشاء الألبومات المرتبطة...", "crop": "قص", "curated_object_page_title": "أشياء", "current_device": "الجهاز الحالي", @@ -859,7 +882,6 @@ "edit_tag": "تعديل العلامة", "edit_title": "تعديل العنوان", "edit_user": "تعديل المستخدم", - "edited": "تم التعديل", "editor": "محرر", "editor_close_without_save_prompt": "لن يتم حفظ التغييرات", "editor_close_without_save_title": "إغلاق المحرر؟", @@ -882,7 +904,9 @@ "error": "خطأ", "error_change_sort_album": "فشل في تغيير ترتيب الألبوم", "error_delete_face": "حدث خطأ في حذف الوجه من الأصول", + "error_getting_places": "خطأ أثناء استرجاع بيانات المواقع", "error_loading_image": "حدث خطأ أثناء تحميل الصورة", + "error_loading_partners": "خطأ بتحميل بيانات الشركاء: {error}", "error_saving_image": "خطأ: {error}", "error_tag_face_bounding_box": "خطأ في وضع علامة على الوجه - لا يمكن الحصول على إحداثيات المربع المحيط", "error_title": "خطأ - حدث خللٌ ما", @@ -1047,6 +1071,7 @@ "favorites_page_no_favorites": "لم يتم العثور على الأصول المفضلة", "feature_photo_updated": "تم تحديث الصورة المميزة", "features": "الميزات", + "features_in_development": "الميزات قيد التطوير", "features_setting_description": "إدارة ميزات التطبيق", "file_name": "إسم الملف", "file_name_or_extension": "اسم الملف أو امتداده", @@ -1067,12 +1092,15 @@ "gcast_enabled": "كوكل كاست", "gcast_enabled_description": "تقوم هذه الميزة بتحميل الموارد الخارجية من Google حتى تعمل.", "general": "عام", + "geolocation_instruction_location": "انقر على الاصل الذي يحتوي على إحداثيات نظام تحديد المواقع لاستخدام موقعه، أو اختر الموقع مباشرة من الخريطة", "get_help": "الحصول على المساعدة", "get_wifiname_error": "تعذر الحصول على اسم شبكة Wi-Fi. تأكد من منح الأذونات اللازمة واتصالك بشبكة Wi-Fi", "getting_started": "البدء", "go_back": "الرجوع للخلف", "go_to_folder": "اذهب إلى المجلد", "go_to_search": "اذهب إلى البحث", + "gps": "نظام تحديد المواقع", + "gps_missing": "لا يوجد نظام تحديد المواقع", "grant_permission": "منح الاذن", "group_albums_by": "تجميع الألبومات حسب...", "group_country": "مجموعة البلد", @@ -1090,7 +1118,6 @@ "header_settings_field_validator_msg": "القيمة لا يمكن ان تكون فارغة", "header_settings_header_name_input": "اسم الرأس", "header_settings_header_value_input": "قيمة الرأس", - "headers_settings_tile_subtitle": "قم بتعريف رؤوس الوكيل التي يجب أن يرسلها التطبيق مع كل طلب شبكة", "headers_settings_tile_title": "رؤوس وكيل مخصصة", "hi_user": "مرحبا {name} ({email})", "hide_all_people": "إخفاء جميع الأشخاص", @@ -1208,6 +1235,7 @@ "local": "محلّي", "local_asset_cast_failed": "غير قادر على بث أصل لم يتم تحميله إلى الخادم", "local_assets": "أُصول (ملفات) محلية", + "local_media_summary": "ملخص الملفات المحلية", "local_network": "شبكة محلية", "local_network_sheet_info": "سيتصل التطبيق بالخادم من خلال عنوان URL هذا عند استخدام شبكة Wi-Fi المحددة", "location_permission": "اذن الموقع", @@ -1219,6 +1247,7 @@ "location_picker_longitude_hint": "أدخل خط الطول هنا", "lock": "قفل", "locked_folder": "مجلد مقفول", + "log_detail_title": "تفاصيل السجل", "log_out": "تسجيل خروج", "log_out_all_devices": "تسجيل الخروج من كافة الأجهزة", "logged_in_as": "تم تسجيل الدخول باسم {user}", @@ -1249,6 +1278,7 @@ "login_password_changed_success": "تم تحديث كلمة السر بنجاح", "logout_all_device_confirmation": "هل أنت متأكد أنك تريد تسجيل الخروج من جميع الأجهزة؟", "logout_this_device_confirmation": "هل أنت متأكد أنك تريد تسجيل الخروج من هذا الجهاز؟", + "logs": "السجلات", "longitude": "خط الطول", "look": "الشكل", "loop_videos": "تكرار مقاطع الفيديو", @@ -1256,6 +1286,7 @@ "main_branch_warning": "أنت تستخدم إصداراً قيد التطوير؛ ونحن نوصي بشدة باستخدام إصدار النشر!", "main_menu": "القائمة الرئيسية", "make": "صنع", + "manage_geolocation": "إدارة الموقع", "manage_shared_links": "إدارة الروابط المشتركة", "manage_sharing_with_partners": "إدارة المشاركة مع الشركاء", "manage_the_app_settings": "إدارة إعدادات التطبيق", @@ -1290,6 +1321,7 @@ "mark_as_read": "تحديد كمقروء", "marked_all_as_read": "تم تحديد الكل كمقروء", "matches": "تطابقات", + "matching_assets": "‏الاصول المطابقة", "media_type": "نوع الوسائط", "memories": "الذكريات", "memories_all_caught_up": "كل شيء محدث", @@ -1330,6 +1362,7 @@ "name_or_nickname": "الاسم أو اللقب", "network_requirement_photos_upload": "استخدام بيانات الهاتف المحمول لعمل نسخة احتياطية للصور", "network_requirement_videos_upload": "استخدام بيانات الهاتف المحمول لعمل نسخة احتياطية لمقاطع الفيديو", + "network_requirements": "متطلبات الشبكة", "network_requirements_updated": "تم تغيير متطلبات الشبكة، يتم إعادة تعيين قائمة انتظار النسخ الاحتياطي", "networking_settings": "الشبكات", "networking_subtitle": "إدارة إعدادات نقطة الخادم النهائية", @@ -1340,6 +1373,7 @@ "new_person": "شخص جديد", "new_pin_code": "رمز PIN الجديد", "new_pin_code_subtitle": "هذه أول مرة تدخل فيها إلى المجلد المقفل. أنشئ رمزًا PIN للوصول بامان إلى هذه الصفحة", + "new_timeline": "الخط الزمني الجديد", "new_user_created": "تم إنشاء مستخدم جديد", "new_version_available": "إصدار جديد متاح", "newest_first": "الأحدث أولاً", @@ -1353,20 +1387,25 @@ "no_assets_message": "انقر لتحميل صورتك الأولى", "no_assets_to_show": "لا توجد أصول لعرضها", "no_cast_devices_found": "لم يتم ايجاد جهاز بث", + "no_checksum_local": "لا توجد بيانات تحقق متاحة - يتعذر تحميل الاصول المحلية", + "no_checksum_remote": "لا يوجد رمز تحقق متاح - يتعذر تحميل الاصل من الموقع البعيد", "no_duplicates_found": "لم يتم العثور على أي تكرارات.", "no_exif_info_available": "لا تتوفر معلومات exif", "no_explore_results_message": "قم برفع المزيد من الصور لاستكشاف مجموعتك.", "no_favorites_message": "أضف المفضلة للعثور بسرعة على أفضل الصور ومقاطع الفيديو", "no_libraries_message": "إنشاء مكتبة خارجية لعرض الصور ومقاطع الفيديو الخاصة بك", + "no_local_assets_found": "لم يتم العثور على أي اصول محلية تتطابق مع قيمة التحقق هذه", "no_locked_photos_message": "الصور والفديوهات في المجلد المقفل مخفية ولن تصهر في التصفح او البحث في مكتبتك.", "no_name": "لا اسم", "no_notifications": "لا توجد تنبيهات", "no_people_found": "لم يتم العثور على اشخاص مطابقين", "no_places": "لا أماكن", + "no_remote_assets_found": "لم يتم العثور على أي اصول بعيدة تتطابق مع رمز التحقق هذل", "no_results": "لا يوجد نتائج", "no_results_description": "جرب كلمة رئيسية مرادفة أو أكثر عمومية", "no_shared_albums_message": "قم بإنشاء ألبوم لمشاركة الصور ومقاطع الفيديو مع الأشخاص في شبكتك", "no_uploads_in_progress": "لا يوجد اي ملفات قيد الرفع", + "not_available": "غير متاح", "not_in_any_album": "ليست في أي ألبوم", "not_selected": "لم يختار", "note_apply_storage_label_to_previously_uploaded assets": "ملاحظة: لتطبيق سمة التخزين على المحتويات التي تم رفعها مسبقًا، قم بتشغيل", @@ -1401,6 +1440,8 @@ "open_the_search_filters": "افتح مرشحات البحث", "options": "خيارات", "or": "أو", + "organize_into_albums": "ترتيب في ألبومات", + "organize_into_albums_description": "أضف الصور الموجودة إلى الألبومات باستخدام إعدادات النسخ المتزامن الحالية", "organize_your_library": "تنظيم مكتبتك", "original": "أصلي", "other": "أخرى", @@ -1486,6 +1527,7 @@ "port": "المنفذ", "preferences_settings_subtitle": "ادارة تفضيلات التطبيق", "preferences_settings_title": "التفضيلات", + "preparing": "قيد التحضير", "preset": "الإعداد المسبق", "preview": "معاينة", "previous": "السابق", @@ -1498,12 +1540,9 @@ "privacy": "الخصوصية", "profile": "حساب تعريفي", "profile_drawer_app_logs": "السجلات", - "profile_drawer_client_out_of_date_major": "تطبيق الهاتف المحمول قديم.يرجى التحديث إلى أحدث إصدار رئيسي.", - "profile_drawer_client_out_of_date_minor": "تطبيق الهاتف المحمول قديم.يرجى التحديث إلى أحدث إصدار صغير.", "profile_drawer_client_server_up_to_date": "العميل والخادم محدثان", "profile_drawer_github": "Github", - "profile_drawer_server_out_of_date_major": "الخادم قديم.يرجى التحديث إلى أحدث إصدار رئيسي.", - "profile_drawer_server_out_of_date_minor": "الخادم قديم.يرجى التحديث إلى أحدث إصدار صغير.", + "profile_drawer_readonly_mode": "تم تفعيل وضع القراءة فقط. اضغط مطولا على رمز صورة المستخدم للخروج.", "profile_image_of_user": "صورة الملف الشخصي لـ {user}", "profile_picture_set": "مجموعة الصور الشخصية.", "public_album": "الألبوم العام", @@ -1540,6 +1579,7 @@ "purchase_server_description_2": "حالة الداعم", "purchase_server_title": "الخادم", "purchase_settings_server_activated": "يتم إدارة مفتاح منتج الخادم من قبل مدير النظام", + "query_asset_id": "استعلام عن معرف الأصل", "queue_status": "يتم الاضافة الى قائمة انتظار النسخ الاحتياطي {count}/{total}", "rating": "تقييم نجمي", "rating_clear": "مسح التقييم", @@ -1547,6 +1587,9 @@ "rating_description": "‫‌اعرض تقييم EXIF في لوحة المعلومات", "reaction_options": "خيارات رد الفعل", "read_changelog": "قراءة سجل التغيير", + "readonly_mode_disabled": "تم تعطيل وضع القراءة فقط", + "readonly_mode_enabled": "تم تفعيل وضع القراءة فقط", + "ready_for_upload": "جاهز للرفع", "reassign": "إعادة التعيين", "reassigned_assets_to_existing_person": "تمت إعادة تعيين {count, plural, one {# الأصل} other {# الاصول}} إلى {name, select, null {شخص موجود } other {{name}}}", "reassigned_assets_to_new_person": "تمت إعادة تعيين {count, plural, one {# المحتوى} other {# المحتويات}} إلى شخص جديد", @@ -1571,6 +1614,7 @@ "regenerating_thumbnails": "جارٍ تجديد الصور المصغرة", "remote": "بعيد", "remote_assets": "الأُصول البعيدة", + "remote_media_summary": "ملخص الملفات البعيدة", "remove": "إزالة", "remove_assets_album_confirmation": "هل أنت متأكد أنك تريد إزالة {count, plural, one {# المحتوى} other {# المحتويات}} من الألبوم ؟", "remove_assets_shared_link_confirmation": "هل أنت متأكد أنك تريد إزالة {count, plural, one {# المحتوى} other {# المحتويات}} من رابط المشاركة هذا؟", @@ -1623,6 +1667,7 @@ "restore_user": "استعادة المستخدم", "restored_asset": "المحتويات المستعادة", "resume": "استئناف", + "resume_paused_jobs": "استكمال {count, plural, one {# وظيفة معلقة} other {# وظائف معلقة}}", "retry_upload": "أعد محاولة الرفع", "review_duplicates": "مراجعة التكرارات", "review_large_files": "مراجعة الملفات الكبيرة", @@ -1716,6 +1761,7 @@ "select_user_for_sharing_page_err_album": "فشل في إنشاء ألبوم", "selected": "التحديد", "selected_count": "{count, plural, other {# محددة }}", + "selected_gps_coordinates": "إحداثيات نظام تحديد المواقع المختارة", "send_message": "‏إرسال رسالة", "send_welcome_email": "إرسال بريدًا إلكترونيًا ترحيبيًا", "server_endpoint": "نقطة نهاية الخادم", @@ -1844,6 +1890,7 @@ "show_slideshow_transition": "إظهار انتقال عرض الشرائح", "show_supporter_badge": "شارة المؤيد", "show_supporter_badge_description": "إظهار شارة المؤيد", + "show_text_search_menu": "عرض قائمة خيارات البحث في النص", "shuffle": "خلط", "sidebar": "الشريط الجانبي", "sidebar_display_description": "عرض رابط للعرض في الشريط الجانبي", @@ -1874,6 +1921,7 @@ "stacktrace": "تتّبُع التكديس", "start": "ابدأ", "start_date": "تاريخ البدء", + "start_date_before_end_date": "يجب أن يكون تاريخ بدء الفترة قبل تاريخ نهايتها", "state": "الولاية", "status": "الحالة", "stop_casting": "ايقاف البث", @@ -1898,6 +1946,8 @@ "sync_albums_manual_subtitle": "مزامنة جميع الفديوهات والصور المرفوعة الى البومات الخزن الاحتياطي المختارة", "sync_local": "مزامنة الملفات المحلية", "sync_remote": "مزامنة الملفات البعيدة", + "sync_status": "حالة النسخ المتزامن", + "sync_status_subtitle": "عرض وإدارة نظام النسخ المتزامن", "sync_upload_album_setting_subtitle": "انشئ و ارفع صورك و فديوهاتك الالبومات المختارة في Immich", "tag": "العلامة", "tag_assets": "أصول العلامة", @@ -1935,7 +1985,9 @@ "to_change_password": "تغيير كلمة المرور", "to_favorite": "تفضيل", "to_login": "تسجيل الدخول", + "to_multi_select": "للتحديد المتعدد", "to_parent": "انتقل إلى الوالد", + "to_select": "للتحديد", "to_trash": "حذف", "toggle_settings": "الإعدادات", "total": "الإجمالي", @@ -1955,6 +2007,7 @@ "trash_page_select_assets_btn": "اختر الأصول", "trash_page_title": "سلة المهملات ({count})", "trashed_items_will_be_permanently_deleted_after": "سيتم حذفُ العناصر المحذوفة نِهائيًا بعد {days, plural, one {# يوم} other {# أيام }}.", + "troubleshoot": "استكشاف المشاكل", "type": "النوع", "unable_to_change_pin_code": "تفيير رمز PIN غير ممكن", "unable_to_setup_pin_code": "انشاء رمز PIN غير ممكن", @@ -1985,6 +2038,7 @@ "unstacked_assets_count": "تم إخراج {count, plural, one {# الأصل} other {# الأصول}} من التكديس", "untagged": "غير مُعَلَّم", "up_next": "التالي", + "update_location_action_prompt": "تحديث موقع {count} عناصر محددة على النحو التالي:", "updated_at": "تم التحديث", "updated_password": "تم تحديث كلمة المرور", "upload": "رفع", @@ -2051,6 +2105,7 @@ "view_next_asset": "عرض المحتوى التالي", "view_previous_asset": "عرض المحتوى السابق", "view_qr_code": "­عرض رمز الاستجابة السريعة", + "view_similar_photos": "عرض صور مشابهة", "view_stack": "عرض التكديس", "view_user": "عرض المستخدم", "viewer_remove_from_stack": "حذف من الكومه أو المجموعة", @@ -2069,5 +2124,6 @@ "yes": "نعم", "you_dont_have_any_shared_links": "ليس لديك أي روابط مشتركة", "your_wifi_name": "اسم شبكة Wi-Fi الخاص بك", - "zoom_image": "تكبير الصورة" + "zoom_image": "تكبير الصورة", + "zoom_to_bounds": "تكبير حتى حدود المنطقة" } diff --git a/i18n/az.json b/i18n/az.json index 0449289735..53e7f55db6 100644 --- a/i18n/az.json +++ b/i18n/az.json @@ -1,7 +1,7 @@ { "about": "Haqqında", "account": "Hesab", - "account_settings": "Hesab parametrləri", + "account_settings": "Hesab Parametrləri", "acknowledge": "Təsdiq et", "action": "Əməliyyat", "action_common_update": "Yenilə", @@ -16,8 +16,8 @@ "add_a_title": "Başlıq əlavə et", "add_birthday": "Doğum günü əlavə et", "add_endpoint": "Son nöqtə əlavə et", - "add_exclusion_pattern": "İstisna nümunəsi əlavə et", - "add_import_path": "Import yolunu əlavə et", + "add_exclusion_pattern": "Çıxarma nümunəsi əlavə et", + "add_import_path": "İdxal yolu əlavə et", "add_location": "Məkan əlavə et", "add_more_users": "Daha çox istifadəçi əlavə et", "add_partner": "Partnyor əlavə et", @@ -25,20 +25,21 @@ "add_photos": "Şəkillər əlavə et", "add_tag": "Etiket əlavə et", "add_to": "Bura əlavə et…", - "add_to_album": "Albom əlavə et", + "add_to_album": "Alboma əlavə et", "add_to_album_bottom_sheet_added": "{album} albomuna əlavə edildi", "add_to_album_bottom_sheet_already_exists": "Artıq {album} albomunda var", + "add_to_album_bottom_sheet_some_local_assets": "Bəzi lokal resurslar alboma əlavə edilə bilmədi", "add_to_album_toggle": "{album} üçün seçimi dəyişin", "add_to_albums": "Albomlara əlavə et", - "add_to_albums_count": "Albomlara əlavə et ({count})", + "add_to_albums_count": "({count}) albomlarına əlavə et", "add_to_shared_album": "Paylaşılan alboma əlavə et", "add_url": "URL əlavə et", "added_to_archive": "Arxivə əlavə edildi", "added_to_favorites": "Sevimlilələrə əlavə edildi", "added_to_favorites_count": "{count, number} şəkil sevimlilələrə əlavə edildi", "admin": { - "add_exclusion_pattern_description": "İstisna şablonlarını əlavə edin. *, ** və ? ilə Globbing dəstəklənir. Məs.: \"Raw\" adlanan hər hansısa bir qovluqda bütün faylları saymamaq üçün \"**/Raw/**\"-dan istifadə edin. \".tif\" ilə bitən bütün faylları saymamaq üçün \"**/*.tif\"-dən istifadə edin. Faylı mütləq yoldan istifadə etməklə saymamaq istəyirsinizsə \"/path/to/ignore/**\"-dan istifadə edin.", - "admin_user": "Admin İstifadəçi", + "add_exclusion_pattern_description": "Çıxarma nümunələri əlavə et. *, ** və ? istifadə edilərək globbing dəstəklənir. Hər hansı bir \"Raw\" adlı qovluqdakı bütün faylları görməməzlikdən gəlmək üçün **/Raw/** istifadə et. “.tif” ilə bitən bütün faylları görməməzlikdən gəlmək üçün **/*.tif istifadə et. Tam yolu görməməzlikdən gəlmək üçün /path/to/ignore/** istifadə et.", + "admin_user": "İdarəçi İstifadəçi", "asset_offline_description": "Bu xarici kitabxana varlığı diskdə artıq tapılmadı və zibil qutusuna köçürüldü. Əgər fayl kitabxana içərisində köçürülübsə, zaman şkalanızı yeni uyğun gələn varlıq üçün yoxlayın. Varlığı yenidən qaytarmaq üçün aşağıda verilmiş fayl yolunun Immich tərəfindən əlçatan olduğundan əmin olduqdan sonra kitabxananı skan edin.", "authentication_settings": "Səlahiyyətləndirmə parametrləri", "authentication_settings_description": "Şifrə, OAuth və digər səlahiyyətləndirmə parametrləri", @@ -48,8 +49,15 @@ "backup_database": "Verilənlər bazasının dump-ını yaradın", "backup_database_enable_description": "Verilənlər bazasının artıq nüsxələrini aktiv et", "backup_keep_last_amount": "Tutulması gərəkən nüsxələrin sayı", - "backup_settings": "Ehtiyat Nüsxə Parametrləri", + "backup_onboarding_1_description": "buludda və ya başqa fiziki yerdə saytdan kənar surət.", + "backup_onboarding_2_description": "müxtəlif cihazlarda yerli nüsxələr. Bura əsas fayllar və həmin faylların ehtiyat lokal nüsxəsi daxildir.", + "backup_onboarding_3_description": "orijinal fayllar da daxil olmaqla məlumatlarınızın ümumi surətləri. Buraya 1 kənar nüsxə və 2 lokal nüsxə daxildir.", + "backup_onboarding_footer": "Immich-in ehtiyat nüsxəsini çıxarmaq haqqında ətraflı məlumat üçün sənədlərə müraciət edin.", + "backup_onboarding_parts_title": "3-2-1 ehtiyat nüsxəsinə aşağıdakılar daxildir:", + "backup_onboarding_title": "Ehtiyat surətlər", + "backup_settings": "Bazanın Dump Parametrləri", "backup_settings_description": "Verilənlər bazasının ehtiyat nüsxə parametrlərini idarə et", + "cleared_jobs": "{job} üçün tapşırıqlar silindi", "config_set_by_file": "Konfiqurasiya hal-hazırda konfiqurasiya faylı ilə təyin olunub", "confirm_delete_library": "{library} kitabxanasını silmək istədiyinizdən əminmisiniz?", "confirm_email_below": "Təsdiqləmək üçün aşağıya {email} yazın", @@ -69,7 +77,7 @@ "image_thumbnail_title": "Önizləmə parametrləri", "job_concurrency": "{job}paralellik", "job_created": "Tapşırıq yaradıldı", - "job_not_concurrency_safe": "Bu tapşırıq parallel fəaliyyət üçün uyğun deyil", + "job_not_concurrency_safe": "Bu iş eyni vaxtda icra üçün təhlükəsiz deyil.", "job_settings": "Tapşırıq parametrləri", "job_settings_description": "Parallel şəkildə fəaliyyət göstərən tapşırıqları idarə et", "job_status": "Tapşırıq statusu", diff --git a/i18n/be.json b/i18n/be.json index f98609b84b..a8bd54b400 100644 --- a/i18n/be.json +++ b/i18n/be.json @@ -28,6 +28,8 @@ "add_to_album": "Дадаць у альбом", "add_to_album_bottom_sheet_added": "Дададзена да {album}", "add_to_album_bottom_sheet_already_exists": "Ужо знаходзіцца ў {album}", + "add_to_album_bottom_sheet_some_local_assets": "Некаторыя лакальныя актывы не могуць быць дададзены ў альбом", + "add_to_album_toggle": "Пераключыць выбар для {album}", "add_to_albums": "Дадаць у альбомы", "add_to_albums_count": "Дадаць у альбомы ({count})", "add_to_shared_album": "Дадаць у агульны альбом", @@ -49,6 +51,9 @@ "backup_keep_last_amount": "Колькасць папярэдніх рэзервовых копій для захавання", "backup_onboarding_1_description": "зняшняя копія ў воблаку або ў іншым фізічным месцы.", "backup_onboarding_2_description": "лакальныя копіі на іншых прыладах. Гэта ўключае ў сябе асноўныя файлы і лакальную рэзервовую копію гэтых файлаў.", + "backup_onboarding_3_description": "поўная колькасць копій вашых данных, у тым ліку зыходных файлаў. Гэта ўключае 1 пазаштатную копію і 2 лакальныя копіі.", + "backup_onboarding_description": " стратэгія рэзервавання 3-2-1 рэкамендавана для аховы вашых данных. Вы павінны захоўваць копіі вашых загружаных фота / відэа гэтак жа добра, як базу данных Immich для вычарпальна поўнага рэзервовага капіявання.", + "backup_onboarding_footer": "Каб атрымаць дадатковую інфармацыю пра рэзервовае капіраванне Immich, звярніцеся да дакументацыі.", "backup_onboarding_parts_title": "Рэзервовая копія «3-2-1» уключае ў сябе:", "backup_onboarding_title": "Рэзервовыя копіі", "backup_settings": "Налады рэзервовага капіявання", @@ -91,6 +96,8 @@ "image_resolution": "Раздзяляльнасць", "image_settings": "Налады відарыса", "image_settings_description": "Кіруйце якасцю і раздзяляльнасцю сгенерыраваных відарысаў", + "image_thumbnail_description": "Маленькая мініяцюра з выдаленымі метададзенымі, якая выкарыстоўваецца пры праглядзе груп фатаграфій, такіх як асноўная хроніка", + "image_thumbnail_quality_description": "Якасць мініяцюр ад 1 да 100. Чым вышэй якасць, тым лепш, але пры гэтым ствараюцца файлы большага памеру і можа знізіцца хуткасць водгуку прыкладання.", "image_thumbnail_title": "Налады мініяцюр", "job_concurrency": "{job} канкурэнтнасць", "job_created": "Заданне створана", @@ -98,6 +105,8 @@ "job_settings": "Налады заданняў", "job_settings_description": "Кіраваць наладамі адначасовага (паралельнага) выканання задання", "job_status": "Становішча задання", + "jobs_delayed": "{jobCount, plural, other {# адкладзена}}", + "jobs_failed": "{jobCount, plural, other {# не выканалася}}", "library_created": "Створана бібліятэка: {library}", "library_deleted": "Бібліятэка выдалена", "library_scanning": "Сканаванне па раскладзе", @@ -154,6 +163,9 @@ "trash_settings": "Налады сметніцы", "trash_settings_description": "Кіраванне наладамі сметніцы", "user_cleanup_job": "Ачыстка карыстальніка", + "user_delete_delay": "Уліковы запіс {user} і актывы будуць запланаваны для канчатковага выдалення праз {delay, plural, one {# дзень} few {# дні} many {# дзён} other {# дзён}}.", + "user_delete_delay_settings_description": "Колькасць дзён пасля выдалення, па заканчэнні якіх уліковы запіс карыстальніка і яго актывы будуць выдаленыя незваротна. Заданне на выдаленне карыстальніка запускаецца апоўначы для праверкі гатоўнасці карыстальнікаў да выдалення. Змены ў гэтым параметры будуць улічаныя пры наступным выкананні.", + "user_delete_immediately": "Уліковы запіс {user} і актывы будуць неадкладна змешчаны ў чаргу на канчатковае выдаленне.", "user_management": "Кіраванне карыстальнікамі", "user_password_has_been_reset": "Пароль карыстальніка быў скінуты:", "user_password_reset_description": "Задайце карыстальніку часовы пароль і паведаміце яму, што пры наступным уваходзе ў сістэму яму трэба будзе змяніць пароль.", @@ -317,7 +329,6 @@ "edit_tag": "Рэдагаваць тэг", "edit_title": "Рэдагаваць загаловак", "edit_user": "Рэдагаваць карыстальніка", - "edited": "Адрэдагавана", "editor": "Рэдактар", "editor_close_without_save_prompt": "Змены не будуць захаваны", "editor_close_without_save_title": "Закрыць рэдактар?", diff --git a/i18n/bg.json b/i18n/bg.json index 9debd3b10f..a59cc4d67d 100644 --- a/i18n/bg.json +++ b/i18n/bg.json @@ -28,10 +28,12 @@ "add_to_album": "Добави към албум", "add_to_album_bottom_sheet_added": "Добавено в {album}", "add_to_album_bottom_sheet_already_exists": "Вече е в {album}", + "add_to_album_bottom_sheet_some_local_assets": "Някои локални файлове не успяха да се добавят към албума", "add_to_album_toggle": "Сменете избора за {album}", "add_to_albums": "Добавяне в албуми", "add_to_albums_count": "Добавяне в албуми ({count})", "add_to_shared_album": "Добави към споделен албум", + "add_upload_to_stack": "Добави качените в група", "add_url": "Добави URL", "added_to_archive": "Добавено към архива", "added_to_favorites": "Добавени към любимите ви", @@ -89,7 +91,7 @@ "image_prefer_embedded_preview_setting_description": "Използване на вградените миниатюри в RAW снимките като входни за обработка на изображенията, когато има такива. Това може да доведе до по-точни цветове за някои изображения, но качеството на прегледите зависи от камерата и изображението може да има повече компресионни артефакти.", "image_prefer_wide_gamut": "Предпочитане на широка гама", "image_prefer_wide_gamut_setting_description": "Използване на Display P3 за миниатюри. Това запазва по-добре жизнеността на изображенията с широки цветови пространства, но изображенията може да изглеждат по различен начин на стари устройства със стара версия на браузъра. sRGB изображенията се запазват като sRGB, за да се избегнат цветови промени.", - "image_preview_description": "Среден размер на изображението с премахнати метаданни, използвано при преглед на един актив и за машинно обучение", + "image_preview_description": "Среден размер на изображението с премахнати метаданни, използвано при преглед на един елемент и за машинно обучение", "image_preview_quality_description": "Качество на предварителния преглед от 1 до 100. По-високата стойност е по-добра, но води до по-големи файлове и може да намали бързодействието на приложението. Задаването на ниска стойност може да повлияе на качеството на машинното обучение.", "image_preview_title": "Настройки на прегледа", "image_quality": "Качество", @@ -116,13 +118,20 @@ "library_scanning_enable_description": "Включване на периодичното сканиране на библиотеката", "library_settings": "Външна библиотека", "library_settings_description": "Управление на настройките за външна библиотека", - "library_tasks_description": "Сканирайте външни библиотеки за нови и/или променени активи", + "library_tasks_description": "Сканирайте външни библиотеки за нови и/или променени елементи", "library_watching_enable_description": "Наблюдаване за промяна на файловете във външната библиотека", - "library_watching_settings": "Наблюдаване на библиотеката (ЕКСПЕРИМЕНТАЛНО)", + "library_watching_settings": "Наблюдаване на библиотеката [ЕКСПЕРИМЕНТАЛНО]", "library_watching_settings_description": "Автоматично наблюдавай за променени файлове", "logging_enable_description": "Включване на запис (логове)", "logging_level_description": "Когато е включено, какво ниво на записване да се използва.", "logging_settings": "Записване", + "machine_learning_availability_checks": "Проверки за наличност", + "machine_learning_availability_checks_description": "Автоматично откриване и предпочитане на налични сървъри за машинно обучение", + "machine_learning_availability_checks_enabled": "Активиране на проверки за наличност", + "machine_learning_availability_checks_interval": "Интервал на проверяване", + "machine_learning_availability_checks_interval_description": "Време в милисекунди между проверките за наличност", + "machine_learning_availability_checks_timeout": "Време за изчакване на отговор", + "machine_learning_availability_checks_timeout_description": "Време за изчакване на отговор в милисекунди при проверка на наличност", "machine_learning_clip_model": "CLIP модел", "machine_learning_clip_model_description": "Името на CLIP модела, посочен тук. Имайте предвид, че при промяна на модела трябва да стартирате отново задачата \"Интелигентно Търсене\" за всички изображения.", "machine_learning_duplicate_detection": "Откриване на дубликати", @@ -170,7 +179,7 @@ "memory_cleanup_job": "Почистване на паметта", "memory_generate_job": "Генериране на паметта", "metadata_extraction_job": "Извличане на метаданни", - "metadata_extraction_job_description": "Извличане на метаданни от всеки от елемент, като GPS локация, лица и резолюция на файловете", + "metadata_extraction_job_description": "Извличане на метаданни от всеки елемент, като GPS локация, лица и резолюция на файловете", "metadata_faces_import_setting": "Включи импорт на лице", "metadata_faces_import_setting_description": "Импортирай лица от EXIF данни и помощни файлове", "metadata_settings": "Опции за метаданни", @@ -202,6 +211,8 @@ "notification_email_ignore_certificate_errors_description": "Игнорирай грешки свързани с валидация на TLS сертификат (не се препоръчва)", "notification_email_password_description": "Парола използвана за удостоверяване пред сървъра за електронна поща", "notification_email_port_description": "Порт на сървъра за електронна поща (например 25, 465 или 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "Използвай SMTPS (SMTP по TLS)", "notification_email_sent_test_email_button": "Изпрати тестов имейл и запази", "notification_email_setting_description": "Настройки за изпращане на имейл известия", "notification_email_test_email": "Изпрати тестов имейл", @@ -324,7 +335,7 @@ "transcoding_max_b_frames": "Максимални B-фрейма", "transcoding_max_b_frames_description": "По-високите стойности подобряват ефективността на компресията, но забавят разкодирането. Може да не е съвместим с хардуерното ускорение на по-стари устройства. 0 деактивира B-фрейма, докато -1 задава тази стойност автоматично.", "transcoding_max_bitrate": "Максимален битрейт", - "transcoding_max_bitrate_description": "Задаването на максимален битрейт може да направи размерите на файловете по-предвидими при незначителни разлики за качеството. При 720p типичните стойности са 2600 kbit/s за VP9 или HEVC или 4500 kbit/s за H.264. Деактивирано, ако е зададено на 0.", + "transcoding_max_bitrate_description": "Задаването на максимален битрейт може да направи размерите на файловете по-предвидими при незначителни разлики за качеството. При 720p типичните стойности са 2600 kbit/s за VP9 или HEVC или 4500 kbit/s за H.264. Деактивирано, ако е зададено на 0. Когато не е зададена мерна единица, подразбира се k (kbit/s); така 5000, 5000k и 5M (Mbit/s) са еквивалентни.", "transcoding_max_keyframe_interval": "Максимален интервал между ключовите кадри", "transcoding_max_keyframe_interval_description": "Задава максималното разстояние между ключовите кадри. По-ниските стойности влошават ефективността на компресията, но подобряват времето за търсене и могат да подобрят качеството в сцени с бързо движение. 0 задава тази стойност автоматично.", "transcoding_optimal_description": "Видеоклипове с по-висока от целевата разделителна способност или не в приетия формат", @@ -342,7 +353,7 @@ "transcoding_target_resolution": "Целева резолюция", "transcoding_target_resolution_description": "По-високите разделителни способности могат да представят повече детайли, но отнемат повече време за разкодиране, имат по-големи размери на файловете и могат да намалят отзивчивостта на приложението.", "transcoding_temporal_aq": "Темпорален AQ", - "transcoding_temporal_aq_description": "Само за NVENC. Повишава качеството на сцени с висока детайлност и ниско ниво на движение. Може да не е съвместимо с по-стари устройства.", + "transcoding_temporal_aq_description": "Само за NVENC. Повишава качеството на сцени с висока детайлност и малко движение. Може да не е съвместимо с по-стари устройства.", "transcoding_threads": "Нишки", "transcoding_threads_description": "По-високите стойности водят до по-бързо разкодиране, но оставят по-малко място за сървъра да обработва други задачи, докато е активен. Тази стойност не трябва да надвишава броя на процесорните ядра. Увеличава максимално използването, ако е зададено на 0.", "transcoding_tone_mapping": "Тонално картографиране", @@ -393,11 +404,11 @@ "advanced_settings_prefer_remote_subtitle": "Някои устройства са твърде бавни за да генерират миниатюри. Активирай тази опция за да се зареждат винаги от сървъра.", "advanced_settings_prefer_remote_title": "Предпочитай изображенията на сървъра", "advanced_settings_proxy_headers_subtitle": "Дефиниране на прокси хедъри, които Immich трябва да изпраща с всяка мрежова заявка", - "advanced_settings_proxy_headers_title": "Прокси хедъри", + "advanced_settings_proxy_headers_title": "Прокси хедъри [ЕКСПЕРИМЕНТАЛНО]", "advanced_settings_readonly_mode_subtitle": "Активира режима \"само за четене\", при който снимките могат да бъдат разглеждани, но неща като избор на няколко изображения, споделяне, изтриване са забранени. Активиране/деактивиране на режима само за четене става от картинката-аватар на потребителя от основния екран", "advanced_settings_readonly_mode_title": "Режим само за четене", "advanced_settings_self_signed_ssl_subtitle": "Пропуска проверката на SSL-сертификата на сървъра. Изисква се при самоподписани сертификати.", - "advanced_settings_self_signed_ssl_title": "Разреши самоподписани SSL сертификати", + "advanced_settings_self_signed_ssl_title": "Разреши самоподписани SSL сертификати [ЕКСПЕРИМЕНТАЛНО]", "advanced_settings_sync_remote_deletions_subtitle": "Автоматично изтрии или възстанови обект на това устройство, когато действието е извършено през уеб-интерфейса", "advanced_settings_sync_remote_deletions_title": "Синхронизация на дистанционни изтривания [ЕКСПЕРИМЕНТАЛНО]", "advanced_settings_tile_subtitle": "Разширени потребителски настройки", @@ -457,10 +468,14 @@ "api_key_description": "Тази стойност ще бъде показана само веднъж. Моля, не забравяйте да го копирате, преди да затворите прозореца.", "api_key_empty": "Името на вашия API ключ не трябва да е празно", "api_keys": "API ключове", + "app_architecture_variant": "Вариант (Ахитектура)", "app_bar_signout_dialog_content": "Наистина ли искате да излезете?", "app_bar_signout_dialog_ok": "Да", "app_bar_signout_dialog_title": "Излез от профила", + "app_download_links": "Линкове за сваляне на приложението", "app_settings": "Настройки ма приложението", + "app_stores": "Магазини за приложения", + "app_update_available": "Налична е нова версия", "appears_in": "Излиза в", "apply_count": "Приложи ({count, number})", "archive": "Архив", @@ -544,6 +559,7 @@ "backup_albums_sync": "Синхронизиране на архивите", "backup_all": "Всичко", "backup_background_service_backup_failed_message": "Неуспешно архивиране. Нов опит…", + "backup_background_service_complete_notification": "Завърши архивирането на обектите", "backup_background_service_connection_failed_message": "Неуспешно свързване към сървъра. Нов опит…", "backup_background_service_current_upload_notification": "Зареждам {filename}", "backup_background_service_default_notification": "Търсене на нови обекти…", @@ -591,6 +607,7 @@ "backup_controller_page_turn_on": "Включи архивиране в активен режим", "backup_controller_page_uploading_file_info": "Инфо за архивирания файл", "backup_err_only_album": "Не може да се премахне единствения албум", + "backup_error_sync_failed": "Синхронизацията е неуспешна. Резервното копие не може да се обработи.", "backup_info_card_assets": "обекта", "backup_manual_cancelled": "Отменено", "backup_manual_in_progress": "Върви архивиране. Опитай след малко", @@ -678,8 +695,8 @@ "client_cert_import_success_msg": "Клиентския сертификат е импортиран", "client_cert_invalid_msg": "Невалиден сертификат или грешна парола", "client_cert_remove_msg": "Клиентския сертификат е премахнат", - "client_cert_subtitle": "Поддържа се само формат PKCS12 (.p12, .pfx). Импорт и премахване на сертификат може само преди вписване в системата", - "client_cert_title": "Клиентски SSL сертификат", + "client_cert_subtitle": "Поддържа се само формат PKCS12 (.p12, .pfx). Импорт/премахване на сертификат може само преди вписване в системата", + "client_cert_title": "Клиентски SSL сертификат [ЕКСПЕРИМЕНТАЛНО]", "clockwise": "По часовниковата стрелка", "close": "Затвори", "collapse": "Свиване", @@ -691,7 +708,6 @@ "comments_and_likes": "Коментари и харесвания", "comments_are_disabled": "Коментарите са деактивирани", "common_create_new_album": "Създай нов албум", - "common_server_error": "Моля, проверете мрежовата връзка, убедете се, че сървъра е достъпен и версиите на сървъра и приложението са съвместими.", "completed": "Завършено", "confirm": "Потвърди", "confirm_admin_password": "Потвърждаване на паролата на администратора", @@ -730,6 +746,7 @@ "create": "Създай", "create_album": "Създай албум", "create_album_page_untitled": "Без заглавие", + "create_api_key": "Създайте API ключ", "create_library": "Създай библиотека", "create_link": "Създай линк", "create_link_to_share": "Създаване на линк за споделяне", @@ -873,7 +890,6 @@ "edit_tag": "Редактирай таг", "edit_title": "Редактиране на заглавието", "edit_user": "Редактиране на потребител", - "edited": "Редактирано", "editor": "Редактор", "editor_close_without_save_prompt": "Промените няма да бъдат запазени", "editor_close_without_save_title": "Затваряне на редактора?", @@ -1029,6 +1045,7 @@ "exif_bottom_sheet_description_error": "Неуспешно обновяване на описание", "exif_bottom_sheet_details": "ПОДРОБНОСТИ", "exif_bottom_sheet_location": "МЯСТО", + "exif_bottom_sheet_no_description": "Няма описание", "exif_bottom_sheet_people": "ХОРА", "exif_bottom_sheet_person_add_person": "Добави име", "exit_slideshow": "Изход от слайдшоуто", @@ -1110,7 +1127,6 @@ "header_settings_field_validator_msg": "Недопустимо е да няма стойност", "header_settings_header_name_input": "Име на заглавието", "header_settings_header_value_input": "Стойност на заглавието", - "headers_settings_tile_subtitle": "Дефиниране на прокси заглавия, които приложението трябва да изпраща с всяка мрежова заявка", "headers_settings_tile_title": "Потребителски прокси заглавия", "hi_user": "Здравей, {name} {email}", "hide_all_people": "Скрий всички хора", @@ -1228,7 +1244,7 @@ "local": "Локално", "local_asset_cast_failed": "Не може да се предава обект, който още не е качен на сървъра", "local_assets": "Локални обекти", - "local_media_summary": "Обобщение на локалните медийни файлове", + "local_media_summary": "Обобщение на локалните файлове", "local_network": "Локална мрежа", "local_network_sheet_info": "Приложението ще се свърже със сървъра на този URL, когато устройството е свързано към зададената Wi-Fi мрежа", "location_permission": "Разрешение за местоположение", @@ -1335,9 +1351,11 @@ "minute": "Минута", "minutes": "Минути", "missing": "Липсващи", + "mobile_app": "Мобилно приложение", + "mobile_app_download_onboarding_note": "Свалете мобилното приложение Immich с някоя от следните опции", "model": "Модел", "month": "Месец", - "monthly_title_text_date_format": "MMMM y", + "monthly_title_text_date_format": "MMMM г", "more": "Още", "move": "Премести", "move_off_locked_folder": "Извади от заключената папка", @@ -1353,6 +1371,8 @@ "my_albums": "Мои албуми", "name": "Име", "name_or_nickname": "Име или прякор", + "navigate": "Придвижване", + "navigate_to_time": "Придвижване до момент във времето", "network_requirement_photos_upload": "Използвай мобилни данни за архивиране на снимки", "network_requirement_videos_upload": "Използвай мобилни данни за архивиране на видео", "network_requirements": "Изисквания към мрежата", @@ -1362,6 +1382,7 @@ "never": "Никога", "new_album": "Нов Албум", "new_api_key": "Нов API ключ", + "new_date_range": "Нов период от време", "new_password": "Нова парола", "new_person": "Нов човек", "new_pin_code": "Нов PIN код", @@ -1412,6 +1433,8 @@ "notifications": "Известия", "notifications_setting_description": "Управление на известията", "oauth": "OAuth", + "obtainium_configurator": "Конфигуратор за получаване", + "obtainium_configurator_instructions": "Използвайте Obtainium за инсталация и обновяване на приложението за Android директно от GitHub на Immich. Създайте API ключ и изберете вариант за да създадете Obtainium конфигурационен линк", "official_immich_resources": "Официална информация за Immich", "offline": "Офлайн", "offset": "Отместване", @@ -1520,6 +1543,7 @@ "port": "Порт", "preferences_settings_subtitle": "Управление на предпочитанията на приложението", "preferences_settings_title": "Предпочитания", + "preparing": "Подготовка", "preset": "Шаблон", "preview": "Прегледи", "previous": "Предишно", @@ -1532,13 +1556,9 @@ "privacy": "Поверителност", "profile": "Профил", "profile_drawer_app_logs": "Дневник", - "profile_drawer_client_out_of_date_major": "Мобилното приложение е остаряло. Моля, актуализирай до най-новата основна версия.", - "profile_drawer_client_out_of_date_minor": "Мобилното приложение е остаряло. Моля, актуализирай до най-новата версия.", "profile_drawer_client_server_up_to_date": "Клиента и сървъра са обновени", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Режима само за четене е активиран. С дълго натискане върху картиката-аватар на потребителя ще деактивирате само за четене.", - "profile_drawer_server_out_of_date_major": "Версията на сървъра е остаряла. Моля, актуализирай поне до последната главна версия.", - "profile_drawer_server_out_of_date_minor": "Версията на сървъра е остаряла. Моля, актуализирай до последната версия.", "profile_image_of_user": "Профилна снимка на {user}", "profile_picture_set": "Профилната снимка е сложена.", "public_album": "Публичен албум", @@ -1585,6 +1605,7 @@ "read_changelog": "Прочети промените", "readonly_mode_disabled": "Режима само за четене е деактивиран", "readonly_mode_enabled": "Режима само за четене е активиран", + "ready_for_upload": "Готово за качване", "reassign": "Преназначаване", "reassigned_assets_to_existing_person": "Преназначени {count, plural, one {# елемент} other {# елемента}} на {name, select, null {съществуващ човек} other {{name}}}", "reassigned_assets_to_new_person": "Преназначени {count, plural, one {# елемент} other {# елемента}} на нов човек", @@ -1766,6 +1787,7 @@ "server_online": "Сървър онлайн", "server_privacy": "Поверителност на сървъра", "server_stats": "Статус на сървъра", + "server_update_available": "Налична е нова версия за сървъра", "server_version": "Версия на сървъра", "set": "Задай", "set_as_album_cover": "Задаване като обложка на албум", @@ -1794,6 +1816,8 @@ "setting_notifications_subtitle": "Настройка на известията", "setting_notifications_total_progress_subtitle": "Общ напредък на зареждане (готово/всички обекти)", "setting_notifications_total_progress_title": "Показване на общия напредък на архивиране във фонов режим", + "setting_video_viewer_auto_play_subtitle": "Автоматично започни възпроизвеждане на видео при отваряне", + "setting_video_viewer_auto_play_title": "Автоматично възпроизвеждане на видео", "setting_video_viewer_looping_title": "Циклично", "setting_video_viewer_original_video_subtitle": "При показване на видео от сървъра показвай оригиналния файл, дори и да има транскодирана версия. Може да използва буфериране. Локално наличните видеа се показват винаги в оригинал, независимо от тази настройка.", "setting_video_viewer_original_video_title": "Само оригинално видео", @@ -1916,6 +1940,7 @@ "stacktrace": "Следа на събраните", "start": "Старт", "start_date": "Начална дата", + "start_date_before_end_date": "Началната дата трябва да бъде преди крайната дата", "state": "Щат", "status": "Статус", "stop_casting": "Спри предаването", @@ -2004,6 +2029,7 @@ "troubleshoot": "Отстраняване на проблеми", "type": "Тип", "unable_to_change_pin_code": "Невъзможна промяна на PIN кода", + "unable_to_check_version": "Невъзможна проверка на версията на приложението или сървъра", "unable_to_setup_pin_code": "Неуспешно задаване на PIN кода", "unarchive": "Разархивирай", "unarchive_action_prompt": "{count} са премахнати от Архива", diff --git a/i18n/bn.json b/i18n/bn.json index 004b584d3c..813aadea7e 100644 --- a/i18n/bn.json +++ b/i18n/bn.json @@ -28,6 +28,7 @@ "add_to_album": "এলবাম এ যোগ করুন", "add_to_album_bottom_sheet_added": "{album} এ যোগ করা হয়েছে", "add_to_album_bottom_sheet_already_exists": "{album} এ আগে থেকেই আছে", + "add_to_album_bottom_sheet_some_local_assets": "কিছু স্থানীয় ছবি বা ভিডিও অ্যালবামে যোগ করা যায়নি", "add_to_album_toggle": "{album} - এর নির্বাচন পরিবর্তন করুন", "add_to_albums": "অ্যালবামে যোগ করুন", "add_to_albums_count": "অ্যালবামে যোগ করুন ({count})", @@ -123,6 +124,11 @@ "logging_enable_description": "লগিং এনাবল/সক্ষম করুন", "logging_level_description": "সক্রিয় থাকাকালীন, কোন লগ স্তর ব্যবহার করতে হবে।", "logging_settings": "লগিং", + "machine_learning_availability_checks": "প্রাপ্যতা পরীক্ষা", + "machine_learning_availability_checks_description": "স্বয়ংক্রিয়ভাবে উপলব্ধ মেশিন লার্নিং সার্ভারগুলি সনাক্ত করুন এবং পছন্দ করুন", + "machine_learning_availability_checks_enabled": "প্রাপ্যতা পরীক্ষা সক্ষম করুন", + "machine_learning_availability_checks_interval": "চেক ব্যবধান", + "machine_learning_availability_checks_interval_description": "প্রাপ্যতা পরীক্ষাগুলির মধ্যে ব্যবধান মিলিসেকেন্ডে", "machine_learning_clip_model": "CLIP মডেল", "machine_learning_clip_model_description": "এখানে তালিকাভুক্ত একটি CLIP মডেলের নাম। মনে রাখবেন, মডেল পরিবর্তনের পর সব ছবির জন্য অবশ্যই ‘Smart Search’ কাজটি আবার চালাতে হবে।", "machine_learning_duplicate_detection": "পুনরাবৃত্তি সনাক্তকরণ", diff --git a/i18n/ca.json b/i18n/ca.json index cdedff97ea..d35132d7c4 100644 --- a/i18n/ca.json +++ b/i18n/ca.json @@ -28,12 +28,14 @@ "add_to_album": "Afegir a un l'àlbum", "add_to_album_bottom_sheet_added": "Afegit a {album}", "add_to_album_bottom_sheet_already_exists": "Ja està a {album}", + "add_to_album_bottom_sheet_some_local_assets": "Alguns recursos locals no s'han pogut afegir a l'àlbum", "add_to_album_toggle": "Commutar selecció de {album}", "add_to_albums": "Afegir als àlbums", "add_to_albums_count": "Afegir als àlbums ({count})", "add_to_shared_album": "Afegir a un àlbum compartit", + "add_upload_to_stack": "Afegeix la càrrega a la pila", "add_url": "Afegir URL", - "added_to_archive": "Afegit als arxivats", + "added_to_archive": "Afegir a l'arxiu", "added_to_favorites": "Afegit als preferits", "added_to_favorites_count": "{count, number} afegits als preferits", "admin": { @@ -62,7 +64,7 @@ "confirm_delete_library": "Esteu segurs que voleu eliminar la llibreria {library}?", "confirm_delete_library_assets": "Esteu segurs que voleu esborrar aquesta llibreria? Això esborrarà {count, plural, one {# contained asset} other {all # contained assets}} d'Immich i no es podrà desfer. Els fitxers romandran al disc.", "confirm_email_below": "Per a confirmar, escriviu \"{email}\" a sota", - "confirm_reprocess_all_faces": "Esteu segur que voleu reprocessar totes les cares? Això també esborrarà la gent que heu anomenat.", + "confirm_reprocess_all_faces": "Esteu segurs que voleu reprocessar totes les cares? Això també esborrarà la gent que heu anomenat.", "confirm_user_password_reset": "Esteu segur que voleu reinicialitzar la contrasenya de l'usuari {user}?", "confirm_user_pin_code_reset": "Esteu segur que voleu restablir el codi PIN de {user}?", "create_job": "Crear tasca", @@ -84,10 +86,10 @@ "image_fullsize_enabled": "Activa la generació d'imatges a tamany complet", "image_fullsize_enabled_description": "Genera imatges a tamany complet per formats no compatibles amb la web. Quan \"Prefereix vista prèvia incrustada\" està activat, les vistes prèvies incrustades s'utilitzen directament sense conversió. No afecta els formats compatibles amb la web com JPEG.", "image_fullsize_quality_description": "De 1 a 100, qualitat de l'imatge a tamany complet. Un valor més alt és millor, però resulta en fitxers de major tamany.", - "image_fullsize_title": "Configuració d'imatges a tamany complet", + "image_fullsize_title": "Configuració de les imatges a tamany complet", "image_prefer_embedded_preview": "Prefereix vista prèvia incrustada", "image_prefer_embedded_preview_setting_description": "Empra vista prèvia incrustada en les fotografies RAW com a entrada per al processament d'imatge, quan sigui possible. Aquesta acció pot produir colors més acurats en algunes imatges, però la qualitat de la vista prèvia depèn de la càmera i la imatge pot tenir més artefactes de compressió.", - "image_prefer_wide_gamut": "Prefereix àmplia gamma", + "image_prefer_wide_gamut": "Prefereix la gamma àmplia", "image_prefer_wide_gamut_setting_description": "Uitlitza Display P3 per a les miniatures. Això preserva més bé la vitalitat de les imatges amb espais de color àmplis, però les imatges es poden veure diferent en aparells antics amb una versió antiga del navegador. Les imatges sRGB romandran com a sRGB per a evitar canvis de color.", "image_preview_description": "Imatge de mida mitjana amb metadades eliminades, que s'utilitza quan es visualitza un sol recurs i per a l'aprenentatge automàtic", "image_preview_quality_description": "Vista prèvia de la qualitat de l'1 al 100. Més alt és millor, però produeix fitxers més grans i pot reduir la capacitat de resposta de l'aplicació. Establir un valor baix pot afectar la qualitat de l'aprenentatge automàtic.", @@ -95,11 +97,11 @@ "image_quality": "Qualitat", "image_resolution": "Resolució", "image_resolution_description": "Les resolucions més altes poden conservar més detalls però triguen més a codificar-se, tenen mides de fitxer més grans i poden reduir la capacitat de resposta de l'aplicació.", - "image_settings": "Configuració d'imatges", + "image_settings": "Configuració de les imatges", "image_settings_description": "Gestiona la qualitat i resolució de les imatges generades", "image_thumbnail_description": "Miniatura petita amb metadades eliminades, que s'utilitza quan es visualitzen grups de fotos com la línia de temps principal", "image_thumbnail_quality_description": "Qualitat de miniatura d'1 a 100. Més alt és millor, però produeix fitxers més grans i pot reduir la capacitat de resposta de l'aplicació.", - "image_thumbnail_title": "Configuració de miniatures", + "image_thumbnail_title": "Configuració de les miniatures", "job_concurrency": "{job} simultàniament", "job_created": "Tasca creada", "job_not_concurrency_safe": "Aquesta tasca no és segura per a la conconcurrència.", @@ -123,6 +125,13 @@ "logging_enable_description": "Habilitar el registrament", "logging_level_description": "Quan està habilitat, quin nivell de registre es vol emprar.", "logging_settings": "Registre", + "machine_learning_availability_checks": "Comprovacions de disponibilitat", + "machine_learning_availability_checks_description": "Detectar i preferir automàticament els servidors d'aprenentatge automàtic disponibles", + "machine_learning_availability_checks_enabled": "Habilita les comprovacions de disponibilitat", + "machine_learning_availability_checks_interval": "Interval de comprovació", + "machine_learning_availability_checks_interval_description": "Interval en mil·lisegons entre comprovacions de disponibilitat", + "machine_learning_availability_checks_timeout": "Temps d'espera de la sol·licitud", + "machine_learning_availability_checks_timeout_description": "Temps d'espera en mil·lisegons per a les comprovacions de disponibilitat", "machine_learning_clip_model": "Model CLIP", "machine_learning_clip_model_description": "El nom d'un model CLIP que apareix a aquí. Tingues en compte que has de tornar a executar la cerca intel·ligent per a totes les imatges quan es canvia de model.", "machine_learning_duplicate_detection": "Detecció de duplicats", @@ -324,7 +333,7 @@ "transcoding_max_b_frames": "Nombre màxim de B-frames", "transcoding_max_b_frames_description": "Els valors més alts milloren l'eficiència de la compressió, però alenteixen la codificació. És possible que no sigui compatible amb l'acceleració de maquinari en dispositius antics. 0 desactiva els B-frames, mentre que -1 estableix aquest valor automàticament.", "transcoding_max_bitrate": "Taxa de bits màxima", - "transcoding_max_bitrate_description": "Establir una taxa de bits màxima pot fer que les mides dels fitxers siguin més previsibles amb un cost menor per a la qualitat. A 720p, els valors típics són 2600 kbit/s per a VP9 o HEVC, o 4500 kbit/s per a H.264. Desactivat si s'estableix a 0.", + "transcoding_max_bitrate_description": "Establir una taxa de bits màxima pot fer que les mides dels fitxers siguin més previsibles amb un cost menor per a la qualitat. A 720p, els valors típics són 2600 kbit/s per a VP9 o HEVC, o 4500 kbit/s per a H.264. Desactivat si s'estableix a 0. Quan no s'especifica, s'assumeix kbit/s; per tant 5000 i 5000k i 5M son equivalents.", "transcoding_max_keyframe_interval": "Interval màxim de fotogrames clau", "transcoding_max_keyframe_interval_description": "Estableix la distància màxima entre fotogrames clau. Els valors més baixos empitjoren l'eficiència de la compressió, però milloren els temps de cerca i poden millorar la qualitat en escenes amb moviment ràpid. 0 estableix aquest valor automàticament.", "transcoding_optimal_description": "Vídeos superiors a la resolució objectiu o que no tenen un format acceptat", @@ -342,7 +351,7 @@ "transcoding_target_resolution": "Resolució objectiu", "transcoding_target_resolution_description": "Les resolucions més altes poden conservar més detalls, però triguen més temps a codificar-se, tenen mides de fitxer més grans i poden reduir la capacitat de resposta de l'aplicació.", "transcoding_temporal_aq": "AQ temporal", - "transcoding_temporal_aq_description": "S'aplica només a NVENC. Augmenta la qualitat de les escenes de baix moviment i alt detall. És possible que no sigui compatible amb dispositius antics.", + "transcoding_temporal_aq_description": "S'aplica només a NVENC. Quantització adaptativa temporal augmenta la qualitat de les escenes de baix moviment i alt detall. És possible que no sigui compatible amb dispositius antics.", "transcoding_threads": "Fils", "transcoding_threads_description": "Els valors més alts condueixen a una codificació més ràpida, però deixen menys espai perquè el servidor processi altres tasques mentre està actiu. Aquest valor no hauria de ser superior al nombre de nuclis de CPU. Maximitza la utilització si s'estableix a 0.", "transcoding_tone_mapping": "Mapeig de to", @@ -496,6 +505,7 @@ "asset_skipped": "Saltat", "asset_skipped_in_trash": "A la paperera", "asset_trashed": "Recurs a la paperera", + "asset_troubleshoot": "Diagnòstic de l'element", "asset_uploaded": "Carregat", "asset_uploading": "S'està carregant…", "asset_viewer_settings_subtitle": "Gestiona la configuració del visualitzador de la galeria", @@ -532,6 +542,7 @@ "background_backup_running_error": "La còpia de seguretat en segon pla s'està executant actualment, no es pot iniciar la còpia de seguretat manual", "background_location_permission": "Permís d'ubicació en segon pla", "background_location_permission_content": "Per canviar de xarxa quan s'executa en segon pla, Immich ha de *sempre* tenir accés a la ubicació precisa perquè l'aplicació pugui llegir el nom de la xarxa Wi-Fi", + "background_options": "Opcions en segon pla", "backup": "Còpia", "backup_album_selection_page_albums_device": "Àlbums al dispositiu ({count})", "backup_album_selection_page_albums_tap": "Un toc per incloure, doble toc per excloure", @@ -539,6 +550,7 @@ "backup_album_selection_page_select_albums": "Selecciona àlbums", "backup_album_selection_page_selection_info": "Informació de la selecció", "backup_album_selection_page_total_assets": "Total d'elements únics", + "backup_albums_sync": "Sincronització d'àlbums de còpia de seguretat", "backup_all": "Tots", "backup_background_service_backup_failed_message": "No s'ha pogut copiar els elements. Tornant a intentar…", "backup_background_service_connection_failed_message": "No s'ha pogut connectar al servidor. Tornant a intentar…", @@ -588,6 +600,7 @@ "backup_controller_page_turn_on": "Activa la còpia de seguretat", "backup_controller_page_uploading_file_info": "S'està pujant la informació del fitxer", "backup_err_only_album": "No es pot eliminar l'únic àlbum", + "backup_error_sync_failed": "Sincronització malament, No es pot fer backup.", "backup_info_card_assets": "elements", "backup_manual_cancelled": "Cancel·lat", "backup_manual_in_progress": "La pujada ja està en curs. Torneu-ho a provar més tard", @@ -608,7 +621,7 @@ "bugs_and_feature_requests": "Errors i sol·licituds de funcions", "build": "Construeix", "build_image": "Construeix la imatge", - "bulk_delete_duplicates_confirmation": "Esteu segur que voleu suprimir de manera massiva {count, plural, one {# recurs duplicat} other {# recursos duplicats}}? Això mantindrà el recurs més gran de cada grup i esborrarà permanentment tots els altres duplicats. No podeu desfer aquesta acció!", + "bulk_delete_duplicates_confirmation": "Esteu segurs que voleu suprimir de manera massiva {count, plural, one {# recurs duplicat} other {# recursos duplicats}}? Això mantindrà el recurs més gran de cada grup i esborrarà permanentment tots els altres duplicats. No podeu desfer aquesta acció!", "bulk_keep_duplicates_confirmation": "Esteu segur que voleu mantenir {count, plural, one {# recurs duplicat} other {# recursos duplicats}}? Això resoldrà tots els grups duplicats sense eliminar res.", "bulk_trash_duplicates_confirmation": "Esteu segur que voleu enviar a les escombraries {count, plural, one {# recurs duplicat} other {# recursos duplicats}}? Això mantindrà el recurs més gran de cada grup i eliminarà la resta de duplicats.", "buy": "Comprar Immich", @@ -655,6 +668,8 @@ "change_pin_code": "Canviar el codi PIN", "change_your_password": "Canvia la teva contrasenya", "changed_visibility_successfully": "Visibilitat canviada amb èxit", + "charging": "Carregant", + "charging_requirement_mobile_backup": "La còpia de seguretat en segon pla requereix que el dispositiu estigui carregant", "check_corrupt_asset_backup": "Comprovar les còpies de seguretat corruptes", "check_corrupt_asset_backup_button": "Realitzar comprovació", "check_corrupt_asset_backup_description": "Executeu aquesta comprovació només mitjançant Wi-Fi i un cop s'hagi fet una còpia de seguretat de tots els actius. El procediment pot trigar uns minuts.", @@ -686,7 +701,6 @@ "comments_and_likes": "Comentaris i agradaments", "comments_are_disabled": "Els comentaris estan desactivats", "common_create_new_album": "Crea un àlbum nou", - "common_server_error": "Si us plau, comproveu la vostra connexió de xarxa, assegureu-vos que el servidor és accessible i que les versions de l'aplicació i del servidor són compatibles.", "completed": "Completat", "confirm": "Confirmar", "confirm_admin_password": "Confirmeu la contrasenya d'administrador", @@ -769,7 +783,7 @@ "deduplication_info_description": "Per preseleccionar recursos automàticament i eliminar els duplicats de manera massiva, ens fixem en:", "default_locale": "Localització predeterminada", "default_locale_description": "Format de dates i números segons la configuració del navegador", - "delete": "Esborra", + "delete": "Esborrar", "delete_action_confirmation_message": "Segur que vols eliminar aquest recurs? Aquesta acció el mourà a la paperera del servidor, i et preguntarà si el vols eliminar localment", "delete_action_prompt": "{count} eliminats", "delete_album": "Esborra l'àlbum", @@ -868,7 +882,6 @@ "edit_tag": "Editar etiqueta", "edit_title": "Edita títol", "edit_user": "Edita l'usuari", - "edited": "Editat", "editor": "Editor", "editor_close_without_save_prompt": "No es desaran els canvis", "editor_close_without_save_title": "Tancar l'editor?", @@ -880,7 +893,7 @@ "empty_trash": "Buidar la paperera", "empty_trash_confirmation": "Esteu segur que voleu buidar la paperera? Això eliminarà tots els recursos a la paperera permanentment d'Immich.\nNo podeu desfer aquesta acció!", "enable": "Activar", - "enable_backup": "Habilitar Còpia de Seguretat", + "enable_backup": "Còpia de Seguretat", "enable_biometric_auth_description": "Introduïu el codi PIN per a habilitar l'autenticació biomètrica", "enabled": "Activat", "end_date": "Data final", @@ -893,6 +906,7 @@ "error_delete_face": "Error esborrant cara de les cares reconegudes", "error_getting_places": "S'ha produït un error en obtenir els llocs", "error_loading_image": "Error carregant la imatge", + "error_loading_partners": "No s'han pogut carregar les parelles: {error}", "error_saving_image": "Error: {error}", "error_tag_face_bounding_box": "Error a l'etiquetar la cara - no s'han pogut obtenir les coordenades de l'àrea", "error_title": "Error - Quelcom ha anat malament", @@ -1023,6 +1037,7 @@ "exif_bottom_sheet_description_error": "No s'ha pogut actualitzar la descripció", "exif_bottom_sheet_details": "DETALLS", "exif_bottom_sheet_location": "UBICACIÓ", + "exif_bottom_sheet_no_description": "Sense descrioció", "exif_bottom_sheet_people": "PERSONES", "exif_bottom_sheet_person_add_person": "Afegir nom", "exit_slideshow": "Surt de la presentació de diapositives", @@ -1099,12 +1114,11 @@ "has_quota": "Quota", "hash_asset": "Hash del recurs", "hashed_assets": "Recursos amb hash", - "hashing": "Hashing", + "hashing": "Generant el hash", "header_settings_add_header_tip": "Afegeix Capçalera", "header_settings_field_validator_msg": "El valor no pot estar buit", "header_settings_header_name_input": "Nom de la capçalera", "header_settings_header_value_input": "Valor de la capçalera", - "headers_settings_tile_subtitle": "Definiu les capçaleres de proxy que l'aplicació hauria d'enviar amb cada sol·licitud de xarxa", "headers_settings_tile_title": "Capçaleres proxy personalitzades", "hi_user": "Hola {name} ({email})", "hide_all_people": "Amaga totes les persones", @@ -1222,6 +1236,7 @@ "local": "Local", "local_asset_cast_failed": "No es pot convertir un actiu que no s'ha penjat al servidor", "local_assets": "Recursos Locals", + "local_media_summary": "Resum de Mitjans Locals", "local_network": "Xarxa local", "local_network_sheet_info": "L'aplicació es connectarà al servidor mitjançant aquest URL quan utilitzeu la xarxa Wi-Fi especificada", "location_permission": "Permís d'ubicació", @@ -1233,6 +1248,7 @@ "location_picker_longitude_hint": "Introdueix aquí la longitud", "lock": "Bloqueja", "locked_folder": "Carpeta bloquejada", + "log_detail_title": "Detall de Registres", "log_out": "Tanca la sessió", "log_out_all_devices": "Tanqueu la sessió de tots els dispositius", "logged_in_as": "Sessió iniciada com a {user}", @@ -1263,6 +1279,7 @@ "login_password_changed_success": "La contrasenya s'ha canviat correctament", "logout_all_device_confirmation": "Esteu segur que voleu tancar la sessió de tots els dispositius?", "logout_this_device_confirmation": "Esteu segur que voleu tancar la sessió d'aquest dispositiu?", + "logs": "Registres", "longitude": "Longitud", "look": "Aspecte", "loop_videos": "Vídeos en bucle", @@ -1305,6 +1322,7 @@ "mark_as_read": "Marcar com ha llegit", "marked_all_as_read": "Marcat tot com a llegit", "matches": "Coincidències", + "matching_assets": "Recursos Coincidents", "media_type": "Tipus de mitjà", "memories": "Records", "memories_all_caught_up": "Posat al dia", @@ -1325,6 +1343,7 @@ "minute": "Minut", "minutes": "Minuts", "missing": "Restants", + "mobile_app": "Aplicació mòbil", "model": "Model", "month": "Mes", "monthly_title_text_date_format": "MMMM y", @@ -1343,8 +1362,10 @@ "my_albums": "Els meus àlbums", "name": "Nom", "name_or_nickname": "Nom o sobrenom", + "navigate": "Navegar", "network_requirement_photos_upload": "Fes servir dades mòbils per a còpies de seguretat de fotos", "network_requirement_videos_upload": "Fes servir dades mòbils per a còpies de seguretat de videos", + "network_requirements": "Requeriments de Xarxa", "network_requirements_updated": "Han canviat els requeriments de xarxa, reiniciant la cua", "networking_settings": "Xarxes", "networking_subtitle": "Gestiona la configuració del endpoint del servidor", @@ -1355,6 +1376,7 @@ "new_person": "Persona nova", "new_pin_code": "Nou codi PIN", "new_pin_code_subtitle": "Aquesta és la primera vegada que accedeixes a la carpeta bloquejada. Crea una codi PIN i accedeix de manera segura a aquesta pàgina", + "new_timeline": "Nova Línia de Temps", "new_user_created": "Nou usuari creat", "new_version_available": "NOVA VERSIÓ DISPONIBLE", "newest_first": "El més nou primer", @@ -1368,20 +1390,25 @@ "no_assets_message": "FEU CLIC PER PUJAR LA VOSTRA PRIMERA FOTO", "no_assets_to_show": "No hi ha elements per mostrar", "no_cast_devices_found": "No s'han trobat dispositius per transmetre", + "no_checksum_local": "Cap checksum disponible - no s'han pogut carregar els recursos locals", + "no_checksum_remote": "Cap checksum disponible - no s'ha pogut obtenir el recurs remot", "no_duplicates_found": "No s'han trobat duplicats.", "no_exif_info_available": "No hi ha informació d'exif disponible", "no_explore_results_message": "Penja més fotos per explorar la teva col·lecció.", "no_favorites_message": "Afegiu preferits per trobar les millors fotos i vídeos a l'instant", "no_libraries_message": "Creeu una llibreria externa per veure les vostres fotos i vídeos", + "no_local_assets_found": "No s'ha trobat cap recurs local amb aquest checksum", "no_locked_photos_message": "Les fotos i vídeos d'aquesta carpeta estan ocultes, i no es mostraran a mesura que navegues o cerques a la teva biblioteca.", "no_name": "Sense nom", "no_notifications": "No hi ha notificacions", "no_people_found": "No s'han trobat coincidències de persones", "no_places": "No hi ha llocs", + "no_remote_assets_found": "No s'ha trobat cap recurs remot amb aquest checksum", "no_results": "Sense resultats", "no_results_description": "Proveu un sinònim o una paraula clau més general", "no_shared_albums_message": "Creeu un àlbum per compartir fotos i vídeos amb persones a la vostra xarxa", "no_uploads_in_progress": "Cap pujada en progrés", + "not_available": "N/A", "not_in_any_album": "En cap àlbum", "not_selected": "No seleccionat", "note_apply_storage_label_to_previously_uploaded assets": "Nota: per aplicar l'etiqueta d'emmagatzematge als actius penjats anteriorment, executeu el", @@ -1503,6 +1530,7 @@ "port": "Port", "preferences_settings_subtitle": "Gestiona les preferències de l'aplicació", "preferences_settings_title": "Preferències", + "preparing": "Preparant", "preset": "Preestablert", "preview": "Previsualització", "previous": "Anterior", @@ -1515,13 +1543,9 @@ "privacy": "Privacitat", "profile": "Perfil", "profile_drawer_app_logs": "Registres", - "profile_drawer_client_out_of_date_major": "L'aplicació mòbil està desactualitzada. Si us plau, actualitzeu a l'última versió major.", - "profile_drawer_client_out_of_date_minor": "L'aplicació mòbil està desactualitzada. Si us plau, actualitzeu a l'última versió menor.", - "profile_drawer_client_server_up_to_date": "El Client i el Servidor estan actualitzats", + "profile_drawer_client_server_up_to_date": "El client i el servidor estan actualitzats", "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Manera de només lectura activada. Feu doble click a la icona de l'avatar de l'usuari per sortir.", - "profile_drawer_server_out_of_date_major": "El servidor està desactualitzat. Si us plau, actualitzeu a l'última versió major.", - "profile_drawer_server_out_of_date_minor": "El servidor està desactualitzat. Si us plau, actualitzeu a l'última versió menor.", + "profile_drawer_readonly_mode": "Mode només lectura. Feu pulsació llarga a la icona de l'avatar d'usuari per sortir.", "profile_image_of_user": "Imatge de perfil de {user}", "profile_picture_set": "Imatge de perfil configurada.", "public_album": "Àlbum públic", @@ -1568,6 +1592,7 @@ "read_changelog": "Llegeix el registre de canvis", "readonly_mode_disabled": "Mode de només lectura desactivat", "readonly_mode_enabled": "Mode de només lectura activat", + "ready_for_upload": "Llest per a pujar", "reassign": "Reassignar", "reassigned_assets_to_existing_person": "{count, plural, one {S'ha reassignat # recurs} other {S'han reassignat # recursos}} a {name, select, null {una persona existent} other {{name}}}", "reassigned_assets_to_new_person": "{count, plural, one {S'ha reassignat # recurs} other {S'han reassignat # recursos}} a una persona nova", @@ -1592,6 +1617,7 @@ "regenerating_thumbnails": "Regenerant les miniatures", "remote": "Remot", "remote_assets": "Recursos Remots", + "remote_media_summary": "Resum de Mitjans Remots", "remove": "Eliminar", "remove_assets_album_confirmation": "Confirmes que vols eliminar {count, plural, one {# recurs} other {# recursos}} de l'àlbum?", "remove_assets_shared_link_confirmation": "Esteu segur que voleu eliminar {count, plural, one {# recurs} other {# recursos}} d'aquest enllaç compartit?", @@ -1644,6 +1670,7 @@ "restore_user": "Restaurar l'usuari", "restored_asset": "Element restaurat", "resume": "Reprendre", + "resume_paused_jobs": "Reprèn {count, plural, one {# treball pausat} other {# treballs pausats}}", "retry_upload": "Torna a provar de pujar", "review_duplicates": "Revisar duplicats", "review_large_files": "Revisar fitxers grans", @@ -1747,6 +1774,7 @@ "server_online": "Servidor en línia", "server_privacy": "Privadesa del servidor", "server_stats": "Estadístiques del servidor", + "server_update_available": "Actualització del servidor disponible", "server_version": "Versió del servidor", "set": "Establir", "set_as_album_cover": "Establir com a portada de l'àlbum", @@ -1775,6 +1803,8 @@ "setting_notifications_subtitle": "Ajusta les preferències de notificació", "setting_notifications_total_progress_subtitle": "Progrés general de la pujada (elements completats/total)", "setting_notifications_total_progress_title": "Mostra el progrés total de la còpia de seguretat en segon pla", + "setting_video_viewer_auto_play_subtitle": "Comença a veure videos quan s'obrin", + "setting_video_viewer_auto_play_title": "Veure videos automàticament", "setting_video_viewer_looping_title": "Bucle", "setting_video_viewer_original_video_subtitle": "Quan reproduïu un vídeo des del servidor, reproduïu l'original encara que hi hagi una transcodificació disponible. Pot conduir a l'amortització. Els vídeos disponibles localment es reprodueixen en qualitat original independentment d'aquesta configuració.", "setting_video_viewer_original_video_title": "Força el vídeo original", @@ -1782,7 +1812,7 @@ "settings_require_restart": "Si us plau, reinicieu Immich per a aplicar aquest canvi", "settings_saved": "Configuració desada", "setup_pin_code": "Configurar un codi PIN", - "share": "Comparteix", + "share": "Compartir", "share_action_prompt": "Compartits {count} recursos", "share_add_photos": "Afegeix fotografies", "share_assets_selected": "{count} seleccionats", @@ -1866,6 +1896,7 @@ "show_slideshow_transition": "Mostra la transició de la presentació de diapositives", "show_supporter_badge": "Insígnia de contribuent", "show_supporter_badge_description": "Mostra una insígnia de contributor", + "show_text_search_menu": "Mostra el menú de cerca amb text", "shuffle": "Mescla", "sidebar": "Barra lateral", "sidebar_display_description": "Mostra un enllaç a la vista a la barra lateral", @@ -1896,6 +1927,7 @@ "stacktrace": "Traça de pila", "start": "Inicia", "start_date": "Data inicial", + "start_date_before_end_date": "La data d'inici ha de ser abans de la data de fi", "state": "Regió", "status": "Estat", "stop_casting": "Atura la transmisió", @@ -1920,6 +1952,8 @@ "sync_albums_manual_subtitle": "Sincronitza tots els vídeos i fotos penjats amb els àlbums de còpia de seguretat seleccionats", "sync_local": "Sincronitza Local", "sync_remote": "Sincronitza Remot", + "sync_status": "Estat de sincronització", + "sync_status_subtitle": "Observa i administra el sistema de sincronització", "sync_upload_album_setting_subtitle": "Creeu i pugeu les seves fotos i vídeos als àlbums seleccionats a Immich", "tag": "Etiqueta", "tag_assets": "Etiquetar actius", @@ -1979,8 +2013,10 @@ "trash_page_select_assets_btn": "Selecciona elements", "trash_page_title": "Paperera ({count})", "trashed_items_will_be_permanently_deleted_after": "Els elements que s'enviïn a la paperera s'eliminaran permanentment després de {days, plural, one {# dia} other {# dies}}.", + "troubleshoot": "Solució de problemes", "type": "Tipus", "unable_to_change_pin_code": "No es pot canviar el codi PIN", + "unable_to_check_version": "No es pot comprovar la versió de l'aplicació ni del servidor", "unable_to_setup_pin_code": "No s'ha pogut configurar el codi PIN", "unarchive": "Desarxivar", "unarchive_action_prompt": "{count} eliminades de l'arxiu", @@ -2095,5 +2131,6 @@ "yes": "Sí", "you_dont_have_any_shared_links": "No tens cap enllaç compartit", "your_wifi_name": "Nom del teu Wi-Fi", - "zoom_image": "Ampliar Imatge" + "zoom_image": "Ampliar Imatge", + "zoom_to_bounds": "Amplia als límits" } diff --git a/i18n/cs.json b/i18n/cs.json index 4c42d2d3f1..0f649dac9b 100644 --- a/i18n/cs.json +++ b/i18n/cs.json @@ -28,10 +28,12 @@ "add_to_album": "Přidat do alba", "add_to_album_bottom_sheet_added": "Přidáno do {album}", "add_to_album_bottom_sheet_already_exists": "Je již v {album}", + "add_to_album_bottom_sheet_some_local_assets": "Některé místní položky nebylo možné přidat do alba", "add_to_album_toggle": "Přepnout výběr pro {album}", "add_to_albums": "Přidat do alb", "add_to_albums_count": "Přidat do alb ({count})", "add_to_shared_album": "Přidat do sdíleného alba", + "add_upload_to_stack": "Přidat nahrané do zásobníku", "add_url": "Přidat URL", "added_to_archive": "Přidáno do archivu", "added_to_favorites": "Přidáno do oblíbených", @@ -118,7 +120,7 @@ "library_settings_description": "Správa nastavení externí knihovny", "library_tasks_description": "Vyhledávání nových nebo změněných položek v externích knihovnách", "library_watching_enable_description": "Sledovat změny souborů v externích knihovnách", - "library_watching_settings": "Sledování knihovny (EXPERIMENTÁLNÍ)", + "library_watching_settings": "Sledování knihovny [EXPERIMENTÁLNÍ]", "library_watching_settings_description": "Automatické sledování změněných souborů", "logging_enable_description": "Povolit protokolování", "logging_level_description": "Když je povoleno, jakou úroveň protokolu použít.", @@ -128,8 +130,8 @@ "machine_learning_availability_checks_enabled": "Povolit kontroly dostupnosti", "machine_learning_availability_checks_interval": "Interval kontrol", "machine_learning_availability_checks_interval_description": "Interval v milisekundách mezi kontrolami dostupnosti", - "machine_learning_availability_checks_timeout": "Vypršení požadavku", - "machine_learning_availability_checks_timeout_description": "Časové vypršení požadavku v milisekundách u kontrol dostupnosti", + "machine_learning_availability_checks_timeout": "Časový limit požadavku", + "machine_learning_availability_checks_timeout_description": "Časový limit v milisekundách pro kontrolu dostupnosti", "machine_learning_clip_model": "Model CLIP", "machine_learning_clip_model_description": "Název CLIP modelu je uvedený zde. Pamatujte, že při změně modelu je nutné znovu spustit úlohu 'Chytré vyhledávání' pro všechny obrázky.", "machine_learning_duplicate_detection": "Kontrola duplicit", @@ -152,6 +154,18 @@ "machine_learning_min_detection_score_description": "Minimální skóre důvěryhodnosti pro detekci obličeje od 0 do 1. Nižší hodnoty odhalí více tváří, ale mohou vést k falešně pozitivním výsledkům.", "machine_learning_min_recognized_faces": "Mininum rozpoznaných obličejů", "machine_learning_min_recognized_faces_description": "Minimální počet rozpoznaných obličejů pro vytvoření osoby. Zvýšení tohoto počtu zpřesňuje rozpoznávání obličejů za cenu zvýšení pravděpodobnosti, že obličej nebude přiřazen k osobě.", + "machine_learning_ocr": "OCR", + "machine_learning_ocr_description": "Použijte strojové učení k rozpoznávání textu v obrázcích", + "machine_learning_ocr_enabled": "Povolit OCR", + "machine_learning_ocr_enabled_description": "Pokud je tato funkce vypnuta, obrázky nebudou podrobeny rozpoznávání textu.", + "machine_learning_ocr_max_resolution": "Maximální rozlišení", + "machine_learning_ocr_max_resolution_description": "Náhledy nad tímto rozlišením budou změněny tak, aby byl zachován poměr stran. Vyšší hodnoty jsou přesnější, ale jejich zpracování trvá déle a zabírají více paměti.", + "machine_learning_ocr_min_detection_score": "Minimální detekční skóre", + "machine_learning_ocr_min_detection_score_description": "Minimální skóre spolehlivosti pro detekci textu v rozmezí 0–1. Nižší hodnoty detekují více textu, ale mohou vést k falešným pozitivním výsledkům.", + "machine_learning_ocr_min_recognition_score": "Minimální počet bodů pro rozpoznání", + "machine_learning_ocr_min_score_recognition_description": "Minimální skóre spolehlivosti pro rozpoznání detekovaného textu v rozmezí 0–1. Nižší hodnoty rozpoznají více textu, ale mohou vést k falešným pozitivům.", + "machine_learning_ocr_model": "OCR model", + "machine_learning_ocr_model_description": "Serverové modely jsou přesnější než mobilní modely, ale jejich zpracování trvá déle a zabírají více paměti.", "machine_learning_settings": "Strojové učení", "machine_learning_settings_description": "Správa funkcí a nastavení strojového učení", "machine_learning_smart_search": "Chytré vyhledávání", @@ -209,6 +223,8 @@ "notification_email_ignore_certificate_errors_description": "Ignorovat chyby ověření certifikátu TLS (nedoporučuje se)", "notification_email_password_description": "Heslo pro ověření na e-mailovém serveru", "notification_email_port_description": "Port e-mailového serveru (např. 25, 465 nebo 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "Používat SMTPS (SMTP přes TLS)", "notification_email_sent_test_email_button": "Odeslat testovací e-mail a uložit", "notification_email_setting_description": "Nastavení pro zasílání e-mailových oznámení", "notification_email_test_email": "Odeslat testovací e-mail", @@ -241,6 +257,7 @@ "oauth_storage_quota_default_description": "Kvóta v GiB, která se použije, pokud není poskytnuta žádná deklarace.", "oauth_timeout": "Časový limit požadavku", "oauth_timeout_description": "Časový limit pro požadavky v milisekundách", + "ocr_job_description": "Použijte strojové učení k rozpoznávání textu v obrázcích", "password_enable_description": "Přihlášení pomocí e-mailu a hesla", "password_settings": "Přihlášení heslem", "password_settings_description": "Správa nastavení přihlašování pomocí hesla", @@ -331,7 +348,7 @@ "transcoding_max_b_frames": "Maximální počet B-snímků", "transcoding_max_b_frames_description": "Vyšší hodnoty zvyšují účinnost komprese, ale zpomalují kódování. Nemusí být kompatibilní s hardwarovou akcelerací na starších zařízeních. Hodnota 0 zakáže B-snímky, zatímco -1 tuto hodnotu nastaví automaticky.", "transcoding_max_bitrate": "Maximální datový tok", - "transcoding_max_bitrate_description": "Nastavení maximálního datového toku může zvýšit předvídatelnost velikosti souborů za cenu menší újmy na kvalitě. Při rozlišení 720p jsou typické hodnoty 2600 kbit/s pro VP9 nebo HEVC nebo 4500 kbit/s pro H.264. Je zakázáno, pokud je nastavena hodnota 0.", + "transcoding_max_bitrate_description": "Nastavení maximálního datového toku může zvýšit předvídatelnost velikosti souborů za cenu menší újmy na kvalitě. Při rozlišení 720p jsou typické hodnoty 2600 kbit/s pro VP9 nebo HEVC nebo 4500 kbit/s pro H.264. Pokud je nastaveno na 0, je zakázáno. Pokud není zadána žádná jednotka, předpokládá se k (pro kbit/s); proto jsou 5000, 5000k a 5M (pro Mbit/s) ekvivalentní.", "transcoding_max_keyframe_interval": "Maximální interval klíčových snímků", "transcoding_max_keyframe_interval_description": "Nastavuje maximální vzdálenost mezi klíčovými snímky. Nižší hodnoty zhoršují účinnost komprese, ale zlepšují rychlost při přeskakování a mohou zlepšit kvalitu ve scénách s rychlým pohybem. Hodnota 0 nastavuje tuto hodnotu automaticky.", "transcoding_optimal_description": "Videa s vyšším než cílovým rozlišením nebo videa, která nejsou v akceptovaném formátu", @@ -349,7 +366,7 @@ "transcoding_target_resolution": "Cílové rozlišení", "transcoding_target_resolution_description": "Vyšší rozlišení mohou zachovat více detailů, ale jejich kódování trvá déle, mají větší velikost souboru a mohou snížit odezvu aplikace.", "transcoding_temporal_aq": "Časové AQ", - "transcoding_temporal_aq_description": "Platí pouze pro NVENC. Zvyšuje kvalitu scén s vysokým počtem detailů a malým počtem pohybů. Nemusí být kompatibilní se staršími zařízeními.", + "transcoding_temporal_aq_description": "Platí pouze pro NVENC. Časová adaptivní kvantizace zvyšuje kvalitu scén s vysokým rozlišením a malým pohybem. Nemusí být kompatibilní se staršími zařízeními.", "transcoding_threads": "Vlákna", "transcoding_threads_description": "Vyšší hodnoty vedou k rychlejšímu kódování, ale ponechávají serveru méně prostoru pro zpracování jiných úloh. Tato hodnota by neměla být vyšší než počet jader procesoru. Maximalizuje využití, pokud je nastavena na 0.", "transcoding_tone_mapping": "Mapování tónů", @@ -400,11 +417,11 @@ "advanced_settings_prefer_remote_subtitle": "U některých zařízení je načítání miniatur z lokálních prostředků velmi pomalé. Aktivujte toto nastavení, aby se místo toho načítaly vzdálené obrázky.", "advanced_settings_prefer_remote_title": "Preferovat vzdálené obrázky", "advanced_settings_proxy_headers_subtitle": "Definice hlaviček proxy serveru, které by měl Immich odesílat s každým síťovým požadavkem", - "advanced_settings_proxy_headers_title": "Proxy hlavičky", + "advanced_settings_proxy_headers_title": "Vlastní proxy hlavičky [EXPERIMENTÁLNÍ]", "advanced_settings_readonly_mode_subtitle": "Povoluje režim pouze pro čtení, ve kterém lze fotografie pouze prohlížet, ale funkce jako výběr více obrázků, sdílení, přenos, mazání jsou zakázány. Povolení/zakázání režimu pouze pro čtení pomocí avatara uživatele na hlavní obrazovce", "advanced_settings_readonly_mode_title": "Režim pouze pro čtení", "advanced_settings_self_signed_ssl_subtitle": "Vynechá ověření SSL certifikátu serveru. Vyžadováno pro self-signed certifikáty.", - "advanced_settings_self_signed_ssl_title": "Povolit self-signed SSL certifikáty", + "advanced_settings_self_signed_ssl_title": "Povolit self-signed SSL certifikáty [EXPERIMENTÁLNÍ]", "advanced_settings_sync_remote_deletions_subtitle": "Automaticky odstranit nebo obnovit položku v tomto zařízení, když je tato akce provedena na webu", "advanced_settings_sync_remote_deletions_title": "Synchronizace vzdáleného mazání [EXPERIMENTÁLNÍ]", "advanced_settings_tile_subtitle": "Pokročilé uživatelské nastavení", @@ -464,10 +481,14 @@ "api_key_description": "Tato hodnota se zobrazí pouze jednou. Před zavřením okna ji nezapomeňte zkopírovat.", "api_key_empty": "Název klíče API by neměl být prázdný", "api_keys": "API klíče", + "app_architecture_variant": "Varianta (architektura)", "app_bar_signout_dialog_content": "Určitě se chcete odhlásit?", "app_bar_signout_dialog_ok": "Ano", "app_bar_signout_dialog_title": "Odhlásit se", + "app_download_links": "Odkazy ke stažení aplikace", "app_settings": "Aplikace", + "app_stores": "Obchody s aplikacemi", + "app_update_available": "K dispozici je aktualizace aplikace", "appears_in": "Vyskytuje se v", "apply_count": "Použít ({count, number})", "archive": "Archiv", @@ -551,6 +572,7 @@ "backup_albums_sync": "Synchronizace zálohovaných alb", "backup_all": "Vše", "backup_background_service_backup_failed_message": "Zálohování médií selhalo. Zkouším to znovu…", + "backup_background_service_complete_notification": "Zálohování položek dokončeno", "backup_background_service_connection_failed_message": "Nepodařilo se připojit k serveru. Zkouším to znovu…", "backup_background_service_current_upload_notification": "Nahrávání {filename}", "backup_background_service_default_notification": "Kontrola nových médií…", @@ -598,6 +620,7 @@ "backup_controller_page_turn_on": "Povolit zálohování na popředí", "backup_controller_page_uploading_file_info": "Informace o nahraném souboru", "backup_err_only_album": "Nelze odstranit jediné vybrané album", + "backup_error_sync_failed": "Synchronizace selhala. Nelze zpracovat zálohu.", "backup_info_card_assets": "položek", "backup_manual_cancelled": "Zrušeno", "backup_manual_in_progress": "Nahrávání již probíhá. Zkuste to znovu později", @@ -659,6 +682,8 @@ "change_password_description": "Buď se do systému přihlašujete poprvé, nebo jste byli požádáni o změnu hesla. Zadejte prosím nové heslo níže.", "change_password_form_confirm_password": "Potvrďte heslo", "change_password_form_description": "Dobrý den, {name}\n\nje to buď poprvé, co se přihlašujete do systému, nebo byl vytvořen požadavek na změnu hesla. Níže zadejte nové heslo.", + "change_password_form_log_out": "Odhlásit všechna ostatní zařízení", + "change_password_form_log_out_description": "Doporučujeme se odhlásit ze všech ostatních zařízení", "change_password_form_new_password": "Nové heslo", "change_password_form_password_mismatch": "Hesla se neshodují", "change_password_form_reenter_new_password": "Znovu zadejte nové heslo", @@ -685,8 +710,8 @@ "client_cert_import_success_msg": "Klientský certifikát je importován", "client_cert_invalid_msg": "Neplatný soubor certifikátu nebo špatné heslo", "client_cert_remove_msg": "Klientský certifikát je odstraněn", - "client_cert_subtitle": "Podpora pouze formátu PKCS12 (.p12, .pfx). Import/odstranění certifikátu je možné pouze před přihlášením", - "client_cert_title": "Klientský SSL certifikát", + "client_cert_subtitle": "Podporuje pouze formát PKCS12 (.p12, .pfx). Import/odstranění certifikátu je možné pouze před přihlášením", + "client_cert_title": "Klientský SSL certifikát [EXPERIMENTÁLNÍ]", "clockwise": "Po směru hodinových ručiček", "close": "Zavřít", "collapse": "Sbalit", @@ -698,7 +723,6 @@ "comments_and_likes": "Komentáře a lajky", "comments_are_disabled": "Komentáře jsou vypnuty", "common_create_new_album": "Vytvořit nové album", - "common_server_error": "Zkontrolujte připojení k internetu. Ujistěte se, že server je dostupný a aplikace/server jsou v kompatibilní verzi.", "completed": "Dokončeno", "confirm": "Potvrdit", "confirm_admin_password": "Potvrzení hesla správce", @@ -737,6 +761,7 @@ "create": "Vytvořit", "create_album": "Vytvořit album", "create_album_page_untitled": "Bez názvu", + "create_api_key": "Vytvořit API klíč", "create_library": "Vytvořit knihovnu", "create_link": "Vytvořit odkaz", "create_link_to_share": "Vytvořit odkaz pro sdílení", @@ -766,6 +791,7 @@ "daily_title_text_date_year": "EEEE, d. MMMM y", "dark": "Tmavý", "dark_theme": "Přepnout tmavý motiv", + "date": "Datum", "date_after": "Datum po", "date_and_time": "Datum a čas", "date_before": "Datum před", @@ -863,7 +889,7 @@ "edit_date_and_time": "Upravit datum a čas", "edit_date_and_time_action_prompt": "{count} časových údajů upraveno", "edit_date_and_time_by_offset": "Posunout datum", - "edit_date_and_time_by_offset_interval": "Nový rozsah dat: {from} – {to}", + "edit_date_and_time_by_offset_interval": "Nový rozsah dat: {from} - {to}", "edit_description": "Upravit popis", "edit_description_prompt": "Vyberte nový popis:", "edit_exclusion_pattern": "Upravit vzor vyloučení", @@ -880,7 +906,6 @@ "edit_tag": "Upravit značku", "edit_title": "Upravit název", "edit_user": "Upravit uživatele", - "edited": "Upraveno", "editor": "Editor", "editor_close_without_save_prompt": "Změny nebudou uloženy", "editor_close_without_save_title": "Zavřít editor?", @@ -1036,6 +1061,7 @@ "exif_bottom_sheet_description_error": "Chyba při aktualizaci popisu", "exif_bottom_sheet_details": "PODROBNOSTI", "exif_bottom_sheet_location": "POLOHA", + "exif_bottom_sheet_no_description": "Žádný popisek", "exif_bottom_sheet_people": "LIDÉ", "exif_bottom_sheet_person_add_person": "Přidat jméno", "exit_slideshow": "Ukončit prezentaci", @@ -1074,6 +1100,7 @@ "features_setting_description": "Správa funkcí aplikace", "file_name": "Název souboru", "file_name_or_extension": "Název nebo přípona souboru", + "file_size": "Velikost souboru", "filename": "Název souboru", "filetype": "Typ souboru", "filter": "Filtr", @@ -1117,7 +1144,6 @@ "header_settings_field_validator_msg": "Hodnota nemůže být prázdná", "header_settings_header_name_input": "Název hlavičky", "header_settings_header_value_input": "Hodnota hlavičky", - "headers_settings_tile_subtitle": "Definice hlaviček proxy serveru, které má aplikace odesílat s každým síťovým požadavkem", "headers_settings_tile_title": "Vlastní proxy hlavičky", "hi_user": "Ahoj {name} ({email})", "hide_all_people": "Skrýt všechny lidi", @@ -1238,6 +1264,7 @@ "local_media_summary": "Souhrn místních médií", "local_network": "Místní síť", "local_network_sheet_info": "Aplikace se při použití zadané sítě Wi-Fi připojí k serveru prostřednictvím tohoto URL", + "location": "Poloha", "location_permission": "Oprávnění polohy", "location_permission_content": "Aby bylo možné používat funkci automatického přepínání, potřebuje Immich oprávnění k přesné poloze, aby mohl přečíst název aktuální sítě Wi-Fi", "location_picker_choose_on_map": "Vybrat na mapě", @@ -1342,6 +1369,8 @@ "minute": "Minuta", "minutes": "Minut", "missing": "Chybějící", + "mobile_app": "Mobilní aplikace", + "mobile_app_download_onboarding_note": "Stáhněte si doprovodnou mobilní aplikaci pomocí následujících možností", "model": "Model", "month": "Měsíc", "monthly_title_text_date_format": "LLLL y", @@ -1360,6 +1389,8 @@ "my_albums": "Moje alba", "name": "Jméno", "name_or_nickname": "Jméno nebo přezdívka", + "navigate": "Navigovat", + "navigate_to_time": "Navigovat na čas", "network_requirement_photos_upload": "Pro zálohování fotografií používat mobilní data", "network_requirement_videos_upload": "Pro zálohování videí používat mobilní data", "network_requirements": "Požadavky na síť", @@ -1369,6 +1400,7 @@ "never": "Nikdy", "new_album": "Nové album", "new_api_key": "Nový API klíč", + "new_date_range": "Nový rozsah dat", "new_password": "Nové heslo", "new_person": "Nová osoba", "new_pin_code": "Nový PIN kód", @@ -1419,6 +1451,9 @@ "notifications": "Oznámení", "notifications_setting_description": "Správa oznámení", "oauth": "OAuth", + "obtainium_configurator": "Obtainium konfigurátor", + "obtainium_configurator_instructions": "Pomocí Obtainia nainstalujte a aktualizujte aplikaci pro Android přímo z vydání na Immich GitHubu. Vytvořte API klíč a vyberte variantu pro vytvoření konfiguračního odkazu Obtainia", + "ocr": "OCR", "official_immich_resources": "Oficiální zdroje Immich", "offline": "Offline", "offset": "Posun", @@ -1523,6 +1558,9 @@ "play_memories": "Přehrát vzpomníky", "play_motion_photo": "Přehrát pohybovou fotografii", "play_or_pause_video": "Přehrát nebo pozastavit video", + "play_original_video": "Přehrát původní video", + "play_original_video_setting_description": "Upřednostňujte přehrávání originálních videí před překódovanými videi. Pokud originální soubor není kompatibilní, nemusí se přehrávat správně.", + "play_transcoded_video": "Přehrát překódované video", "please_auth_to_access": "Pro přístup se prosím ověřte", "port": "Port", "preferences_settings_subtitle": "Správa předvoleb aplikace", @@ -1540,13 +1578,9 @@ "privacy": "Soukromí", "profile": "Profil", "profile_drawer_app_logs": "Logy", - "profile_drawer_client_out_of_date_major": "Mobilní aplikace je zastaralá. Aktualizujte ji na nejnovější hlavní verzi.", - "profile_drawer_client_out_of_date_minor": "Mobilní aplikace je zastaralá. Aktualizujte ji na nejnovější verzi.", "profile_drawer_client_server_up_to_date": "Klient a server jsou aktuální", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Režim jen pro čtení. Ukončíte ho dlouhým podržením ikony avataru.", - "profile_drawer_server_out_of_date_major": "Server je zastaralý. Aktualizujte na nejnovější hlavní verzi.", - "profile_drawer_server_out_of_date_minor": "Server je zastaralý. Aktualizujte je na nejnovější verzi.", "profile_image_of_user": "Profilový obrázek uživatele {user}", "profile_picture_set": "Profilový obrázek nastaven.", "public_album": "Veřejné album", @@ -1623,7 +1657,7 @@ "remove_assets_album_confirmation": "Opravdu chcete z alba odstranit {count, plural, one {# položku} few {# položky} other {# položek}}?", "remove_assets_shared_link_confirmation": "Opravdu chcete ze sdíleného odkazu odstranit {count, plural, one {# položku} few {# položky} other {# položek}}?", "remove_assets_title": "Odstranit položky?", - "remove_custom_date_range": "Odstranit vlastní rozsah datumů", + "remove_custom_date_range": "Odstranit vlastní rozsah dat", "remove_deleted_assets": "Odstranit offline soubory", "remove_from_album": "Odstranit z alba", "remove_from_album_action_prompt": "{count} odstraněných z alba", @@ -1663,6 +1697,7 @@ "reset_sqlite_confirmation": "Jste si jisti, že chcete obnovit databázi SQLite? Pro opětovnou synchronizaci dat se budete muset odhlásit a znovu přihlásit", "reset_sqlite_success": "Obnovení SQLite databáze proběhlo úspěšně", "reset_to_default": "Obnovit výchozí nastavení", + "resolution": "Rozlišení", "resolve_duplicates": "Vyřešit duplicity", "resolved_all_duplicates": "Vyřešeny všechny duplicity", "restore": "Obnovit", @@ -1681,6 +1716,7 @@ "running": "Probíhá", "save": "Uložit", "save_to_gallery": "Uložit do galerie", + "saved": "Uloženo", "saved_api_key": "API klíč uložen", "saved_profile": "Profil uložen", "saved_settings": "Nastavení uloženo", @@ -1697,6 +1733,9 @@ "search_by_description_example": "Pěší turistika v Sapě", "search_by_filename": "Vyhledávání podle názvu nebo přípony souboru", "search_by_filename_example": "např. IMG_1234.JPG nebo PNG", + "search_by_ocr": "Hledat pomocí OCR", + "search_by_ocr_example": "Latte", + "search_camera_lens_model": "Vyhledat model objektivu...", "search_camera_make": "Vyhledat výrobce fotoaparátu...", "search_camera_model": "Vyhledat model fotoaparátu...", "search_city": "Vyhledat město...", @@ -1705,7 +1744,7 @@ "search_filter_camera_title": "Výběr typu fotoaparátu", "search_filter_date": "Datum", "search_filter_date_interval": "{start} až {end}", - "search_filter_date_title": "Výběr rozmezí dat", + "search_filter_date_title": "Výběr rozsahu dat", "search_filter_display_option_not_in_album": "Není v albu", "search_filter_display_options": "Možnost zobrazení", "search_filter_filename": "Vyhledávat podle názvu souboru", @@ -1713,6 +1752,7 @@ "search_filter_location_title": "Výběr polohy", "search_filter_media_type": "Typ média", "search_filter_media_type_title": "Výběr typu média", + "search_filter_ocr": "Hledat pomocí OCR", "search_filter_people_title": "Výběr lidí", "search_for": "Vyhledat", "search_for_existing_person": "Vyhledat existující osobu", @@ -1775,6 +1815,7 @@ "server_online": "Server online", "server_privacy": "Ochrana soukromí serveru", "server_stats": "Statistiky serveru", + "server_update_available": "K dispozici je aktualizace serveru", "server_version": "Verze serveru", "set": "Nastavit", "set_as_album_cover": "Nastavit jako obal alba", @@ -1803,6 +1844,8 @@ "setting_notifications_subtitle": "Přizpůsobení předvoleb oznámení", "setting_notifications_total_progress_subtitle": "Celkový průběh nahrání (hotovo/celkově)", "setting_notifications_total_progress_title": "Zobrazit celkový průběh zálohování na pozadí", + "setting_video_viewer_auto_play_subtitle": "Automaticky spustit přehrávání videí při jejich otevření", + "setting_video_viewer_auto_play_title": "Automatické přehrávání videí", "setting_video_viewer_looping_title": "Smyčka", "setting_video_viewer_original_video_subtitle": "Při streamování videa ze serveru přehrávat originál, i když je k dispozici překódovaná verze. Může vést k bufferování. Videa dostupná lokálně se přehrávají v původní kvalitě bez ohledu na toto nastavení.", "setting_video_viewer_original_video_title": "Vynutit původní video", @@ -1962,7 +2005,7 @@ "tag_updated": "Aktualizována značka: {tag}", "tagged_assets": "Přiřazena značka {count, plural, one {# položce} other {# položkám}}", "tags": "Značky", - "tap_to_run_job": "Klepnutím na spustíte úlohu", + "tap_to_run_job": "Klepnutím spustíte úlohu", "template": "Šablona", "theme": "Motiv", "theme_selection": "Výběr motivu", @@ -1982,6 +2025,7 @@ "theme_setting_three_stage_loading_title": "Povolení třístupňového načítání", "they_will_be_merged_together": "Budou sloučeny dohromady", "third_party_resources": "Zdroje třetích stran", + "time": "Čas", "time_based_memories": "Časové vzpomínky", "timeline": "Časová osa", "timezone": "Časové pásmo", @@ -2014,6 +2058,7 @@ "troubleshoot": "Diagnostika", "type": "Typ", "unable_to_change_pin_code": "Nelze změnit PIN kód", + "unable_to_check_version": "Nepodařilo se zjistit verzi aplikace nebo serveru", "unable_to_setup_pin_code": "Nelze nastavit PIN kód", "unarchive": "Odebrat z archivu", "unarchive_action_prompt": "{count} odstraněných z archivu", diff --git a/i18n/da.json b/i18n/da.json index 9c25a293bc..5bb67dd83d 100644 --- a/i18n/da.json +++ b/i18n/da.json @@ -6,7 +6,7 @@ "action": "Handling", "action_common_update": "Opdater", "actions": "Handlinger", - "active": "Aktive", + "active": "Aktiv", "activity": "Aktivitet", "activity_changed": "Aktivitet er {enabled, select, true {aktiveret} other {deaktiveret}}", "add": "Tilføj", @@ -28,10 +28,12 @@ "add_to_album": "Tilføj til album", "add_to_album_bottom_sheet_added": "Tilføjet til {album}", "add_to_album_bottom_sheet_already_exists": "Allerede i {album}", + "add_to_album_bottom_sheet_some_local_assets": "Nogle lokale elementer kunne ikke føjes til albummet", "add_to_album_toggle": "Skift selektion for {album}", "add_to_albums": "Tilføj til albummer", "add_to_albums_count": "Tilføj til albummer({count})", "add_to_shared_album": "Tilføj til delt album", + "add_upload_to_stack": "Tilføj upload til stack", "add_url": "Tilføj URL", "added_to_archive": "Tilføjet til arkiv", "added_to_favorites": "Tilføjet til favoritter", @@ -118,11 +120,18 @@ "library_settings_description": "Administrer eksterne biblioteksindstillinger", "library_tasks_description": "Scan eksterne biblioteker for nye og/eller ændrede mediefiler", "library_watching_enable_description": "Overvåg eksterne biblioteker for filændringer", - "library_watching_settings": "Biblioteks overvågning (EKSPERIMENTEL)", + "library_watching_settings": "Biblioteks overvågning [EKSPERIMENTEL]", "library_watching_settings_description": "Tjek automatisk for ændrede filer", "logging_enable_description": "Aktiver logning", "logging_level_description": "Når slået til, hvilket logniveau, der skal bruges.", "logging_settings": "Logning", + "machine_learning_availability_checks": "Tilgængelighedstjek", + "machine_learning_availability_checks_description": "Opdag og foretræk automatisk tilgængelige maskinlæringsservere", + "machine_learning_availability_checks_enabled": "Aktivér tilgængelighedstjek", + "machine_learning_availability_checks_interval": "Kontroller interval", + "machine_learning_availability_checks_interval_description": "Interval i millisekunder mellem tilgængelighedstjeks", + "machine_learning_availability_checks_timeout": "Timeout på anmodning", + "machine_learning_availability_checks_timeout_description": "Timeout i millisekunder på tilgængelighedstjeks", "machine_learning_clip_model": "CLIP-model", "machine_learning_clip_model_description": "Navnet på CLIP-modellen på listen her. Bemærk at du skal genkøre \"Smart Søgning\"-jobbet for alle billeder, hvis du skifter model.", "machine_learning_duplicate_detection": "Dubletdetektion", @@ -145,6 +154,18 @@ "machine_learning_min_detection_score_description": "Minimum tillidsscore for et ansigt, der kan registreres fra 0-1. Lavere værdier vil registrere flere ansigter, men kan resultere i falske positiver.", "machine_learning_min_recognized_faces": "Minimum genkendte ansigter", "machine_learning_min_recognized_faces_description": "Minimumsantallet af genkendte ansigter for en person, før denne person bliver oprettet. At øge dette gør ansigtsgenkendelse mere præcis på bekostning af at øge chancen for, at et ansigt ikke er tildelt en person.", + "machine_learning_ocr": "OCR", + "machine_learning_ocr_description": "Brug maskinlæring til at genkende tekst i billeder", + "machine_learning_ocr_enabled": "Aktiver OCR", + "machine_learning_ocr_enabled_description": "Hvis deaktiveret, vil tekstgenkendelse ikke blive udført på billederne.", + "machine_learning_ocr_max_resolution": "Maksimum opløsning", + "machine_learning_ocr_max_resolution_description": "Forhåndsvisninger over denne opløsning ændres i størrelse, mens billedformatet bevares. Højere værdier er mere nøjagtige, men tager længere tid at behandle og bruger mere hukommelse.", + "machine_learning_ocr_min_detection_score": "Minimum detektionsscore", + "machine_learning_ocr_min_detection_score_description": "Minimums konfidensscore for tekst, der skal detekteres, fra 0-1. Lavere værdier vil detektere mere tekst, men kan resultere i falsk positiver.", + "machine_learning_ocr_min_recognition_score": "Minimum genkendelsesscore", + "machine_learning_ocr_min_score_recognition_description": "Minimum konfidensscore for genkendelse af registreret tekst er fra 0-1. Lavere værdier vil genkende mere tekst, men kan resultere i falsk positiver.", + "machine_learning_ocr_model": "OCR model", + "machine_learning_ocr_model_description": "Server modeller er mere præcise end mobil modeller, men tager længer tid at processere og bruger mere hukommelse.", "machine_learning_settings": "Maskinlæringsindstillinger", "machine_learning_settings_description": "Administrer maskinlæringsfunktioner og indstillinger", "machine_learning_smart_search": "Smart søgning", @@ -202,6 +223,8 @@ "notification_email_ignore_certificate_errors_description": "Ignorér TLS-certifikatgodkendelsesfejl (ikke anbefalet)", "notification_email_password_description": "Adgangskode til brug ved autentificering med e-mailserveren", "notification_email_port_description": "Emailserverens port (fx 25, 465 eller 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "Brug SMTPS (SMTP over TLS)", "notification_email_sent_test_email_button": "Send test-email og gem", "notification_email_setting_description": "Indstillinger for sending af emailnotifikationer", "notification_email_test_email": "Send test-email", @@ -234,6 +257,7 @@ "oauth_storage_quota_default_description": "Kvote i GiB som bruges, når der ikke bliver oplyst en fordring.", "oauth_timeout": "Forespørgslen udløb", "oauth_timeout_description": "Udløbstid for forespørgsel i milisekunder", + "ocr_job_description": "Brug maskinlæring til at genkende tekst i billeder", "password_enable_description": "Log ind med email og adgangskode", "password_settings": "Adgangskodelogin", "password_settings_description": "Administrer indstillinger for adgangskodelogin", @@ -324,7 +348,7 @@ "transcoding_max_b_frames": "Maksimum B-frames", "transcoding_max_b_frames_description": "Højere værdier forbedrer kompressionseffektivitet, men kan gøre indkodning langsommere. Er måske ikke kompatibelt med hardware-acceleration på ældre enheder. 0 slår B-frames fra, mens -1 sætter denne værdi automatisk.", "transcoding_max_bitrate": "Maksimal bitrate", - "transcoding_max_bitrate_description": "At sætte en maksmimal bitrate kan gøre filstørrelserne mere forudsigelige med et lille tab i kvalitet. Ved 720p er almindelige værdier 2600 kbit/s for VP9 eller HEVC, eller 4500 kbit/s for H.264. Slået fra hvis sat til 0.", + "transcoding_max_bitrate_description": "Indstilling af en maksimal bitrate kan gøre filstørrelser mere forudsigelige, men med et mindre fald i kvaliteten. Ved 720p er typiske værdier 2600 kbit/s for VP9 eller HEVC eller 4500 kbit/s for H.264. Deaktiveret, hvis den er indstillet til 0. Når der ikke er angivet nogen enhed, antages k (for kbit/s); derfor er 5000, 5000k og 5M (for Mbit/s) ækvivalente.", "transcoding_max_keyframe_interval": "Maksimal keyframe-interval", "transcoding_max_keyframe_interval_description": "Sætter den maksimale frameafstand mellem keyframes. Lavere værdier forringer kompressionseffektiviteten, men forbedrer søgetider og kan forbedre kvaliteten i scener med hurtig bevægelse. 0 sætter denne værdi automatisk.", "transcoding_optimal_description": "Videoer højere end målopløsningen eller ikke i et godkendt format", @@ -387,17 +411,17 @@ "admin_password": "Administratoradgangskode", "administration": "Administration", "advanced": "Avanceret", - "advanced_settings_enable_alternate_media_filter_subtitle": "Brug denne valgmulighed for at filtrere media under synkronisering baseret på alternative kriterier. Prøv kun denne hvis du har problemer med at appen ikke opdager alle albums.", + "advanced_settings_enable_alternate_media_filter_subtitle": "Brug denne valgmulighed for at filtrere media under synkronisering baseret på alternative kriterier. Prøv kun denne, hvis du har problemer med, at appen ikke opdager alle albums.", "advanced_settings_enable_alternate_media_filter_title": "[EKSPERIMENTEL] Brug alternativ enheds album synkroniserings filter", "advanced_settings_log_level_title": "Logniveau: {level}", "advanced_settings_prefer_remote_subtitle": "Nogle enheder er meget lang tid om at indlæse miniaturebilleder af lokale elementer. Aktiver denne indstilling for at indlæse elementer fra serveren i stedet.", "advanced_settings_prefer_remote_title": "Foretræk elementer på serveren", "advanced_settings_proxy_headers_subtitle": "Definer proxy headers Immich skal sende med hver netværks forespørgsel", - "advanced_settings_proxy_headers_title": "Proxy headere", + "advanced_settings_proxy_headers_title": "Tilpasset proxy headere [EKSPERIMENTALT]", "advanced_settings_readonly_mode_subtitle": "Aktiverer skrivebeskyttet tilstand, hvor billederne alene kan vises. Ting som at vælge flere billeder, dele, caste og slette er alle deaktiveret. Aktiver skrivebeskyttet tilstand via en bruger avatar fra hovedskærmen", "advanced_settings_readonly_mode_title": "Skrivebeskyttet tilstand", "advanced_settings_self_signed_ssl_subtitle": "Spring verificering af SSL-certifikat over for serverens endelokation. Kræves for selvsignerede certifikater.", - "advanced_settings_self_signed_ssl_title": "Tillad selvsignerede certifikater", + "advanced_settings_self_signed_ssl_title": "Tillad selvsignerede SSL certifikater [EKSPERIMENTALT]", "advanced_settings_sync_remote_deletions_subtitle": "Slet eller gendan automatisk en mediefil på denne enhed, når denne handling foretages på Immich webinterface", "advanced_settings_sync_remote_deletions_title": "Synkroniser fjernsletninger [EKSPERIMENTELT]", "advanced_settings_tile_subtitle": "Avancerede brugerindstillinger", @@ -423,6 +447,7 @@ "album_remove_user_confirmation": "Er du sikker på at du vil fjerne {user}?", "album_search_not_found": "Ingen album fundet som matcher din søgning", "album_share_no_users": "Det ser ud til at du har delt denne album med alle brugere, eller du har ikke nogen brugere til at dele med.", + "album_summary": "Albumoversigt", "album_updated": "Album opdateret", "album_updated_setting_description": "Modtag en emailnotifikation når et delt album får nye mediefiler", "album_user_left": "Forlod {album}", @@ -456,10 +481,14 @@ "api_key_description": "Denne værdi vises kun én gang. Venligst kopiér den før du lukker vinduet.", "api_key_empty": "Din API-nøgle-navn burde ikke være tom", "api_keys": "API-nøgler", + "app_architecture_variant": "Variant (Arkitektur)", "app_bar_signout_dialog_content": "Er du sikker på, du vil logge ud?", "app_bar_signout_dialog_ok": "Ja", "app_bar_signout_dialog_title": "Log ud", + "app_download_links": "App Download Links", "app_settings": "Appindstillinger", + "app_stores": "App Butikker", + "app_update_available": "App opdatering er tilgængelig", "appears_in": "Optræder i", "apply_count": "Brug ({count, number})", "archive": "Arkiv", @@ -494,6 +523,8 @@ "asset_restored_successfully": "Elementet blev gendannet succesfuldt", "asset_skipped": "Sprunget over", "asset_skipped_in_trash": "I skraldespand", + "asset_trashed": "Objekt kasseret", + "asset_troubleshoot": "Fejlsøg på objekt", "asset_uploaded": "Uploadet", "asset_uploading": "Uploader…", "asset_viewer_settings_subtitle": "Administrer indstillinger for gallerifremviser", @@ -527,8 +558,10 @@ "autoplay_slideshow": "Afspil slideshow automatisk", "back": "Tilbage", "back_close_deselect": "Tilbage, luk eller fravælg", + "background_backup_running_error": "Backup kører lige nu i baggrund; kan ikke starte manuel backup", "background_location_permission": "Tilladelse til baggrundsplacering", "background_location_permission_content": "For at skifte netværk, når appen kører i baggrunden, skal Immich *altid* have præcis placeringsadgang, så appen kan læse WiFi-netværkets navn", + "background_options": "Baggrundsmuligheder", "backup": "Sikkerhedskopier", "backup_album_selection_page_albums_device": "Albummer på enheden ({count})", "backup_album_selection_page_albums_tap": "Tryk en gang for at inkludere, tryk to gange for at ekskludere", @@ -536,8 +569,10 @@ "backup_album_selection_page_select_albums": "Vælg albummer", "backup_album_selection_page_selection_info": "Oplysninger om valgte", "backup_album_selection_page_total_assets": "Samlede unikke elementer", + "backup_albums_sync": "Synkronisering af backupalbums", "backup_all": "Alt", "backup_background_service_backup_failed_message": "Sikkerhedskopiering af elementer fejlede. Forsøger igen…", + "backup_background_service_complete_notification": "Sikkerhedskopiering af aktiver fuldført", "backup_background_service_connection_failed_message": "Forbindelsen til serveren blev tabt. Forsøger igen…", "backup_background_service_current_upload_notification": "Uploader {filename}", "backup_background_service_default_notification": "Søger efter nye elementer…", @@ -585,6 +620,7 @@ "backup_controller_page_turn_on": "Slå sikkerhedskopiering til", "backup_controller_page_uploading_file_info": "Uploader filinformation", "backup_err_only_album": "Kan ikke slette det eneste album", + "backup_error_sync_failed": "Synkroniseringen mislykkedes. Sikkerhedskopieringen kunne ikke behandles.", "backup_info_card_assets": "objekter", "backup_manual_cancelled": "Annulleret", "backup_manual_in_progress": "Upload er allerede undervejs. Prøv igen efter noget tid", @@ -633,10 +669,10 @@ "cannot_merge_people": "Kan ikke sammenflette personer", "cannot_undo_this_action": "Du kan ikke fortryde denne handling!", "cannot_update_the_description": "Kan ikke opdatere beskrivelsen", - "cast": "Cast", + "cast": "Caste", "cast_description": "Konfigurer tilgængelige cast destinationer", "change_date": "Ændr dato", - "change_description": "Beskrivelse af ændringer", + "change_description": "Ændr beskrivelse", "change_display_order": "Ændrer visningsrækkefølge", "change_expiration_time": "Ændr udløbstidspunkt", "change_location": "Ændr sted", @@ -646,12 +682,16 @@ "change_password_description": "Dette er enten første gang du tilmelder dig, eller en ændring af kodeordet blev bestilt. Indtast dit nye kodeord herunder.", "change_password_form_confirm_password": "Bekræft kodeord", "change_password_form_description": "Hej {name},\n\nDette er enten første gang du logger ind eller også er der lavet en anmodning om at ændre dit kodeord. Indtast venligst et nyt kodeord nedenfor.", + "change_password_form_log_out": "Log ud af alle andre enheder", + "change_password_form_log_out_description": "Det er anbefalet at logge ud af alle andre enheder", "change_password_form_new_password": "Nyt kodeord", "change_password_form_password_mismatch": "Kodeord er ikke ens", "change_password_form_reenter_new_password": "Gentag nyt kodeord", "change_pin_code": "Skift PIN kode", "change_your_password": "Skift dit kodeord", "changed_visibility_successfully": "Synlighed blev ændret", + "charging": "Lader", + "charging_requirement_mobile_backup": "Baggrundsbackup kræver, at enheden er tilsluttet oplader", "check_corrupt_asset_backup": "Tjek for korrupte sikkerhedskopier af elementer", "check_corrupt_asset_backup_button": "Foretag kontrol", "check_corrupt_asset_backup_description": "Kør kun denne kontrol via Wi-Fi, og når alle elementer er blevet sikkerhedskopieret. Proceduren kan tage et par minutter.", @@ -670,8 +710,8 @@ "client_cert_import_success_msg": "Klient certifikat er importeret", "client_cert_invalid_msg": "Invalid certifikat fil eller forkert adgangskode", "client_cert_remove_msg": "Klient certifikat er fjernet", - "client_cert_subtitle": "Supportere kin PKCS12 (.p12, .pfx) Certifikat importering/fjernelse er kun tilgængeligt før login", - "client_cert_title": "SSL Klient Certifikat", + "client_cert_subtitle": "Supportere kun PKCS12 (.p12, .pfx) format. Certifikat importering/fjernelse er kun tilgængeligt før login", + "client_cert_title": "SSL Klient Certifikat [EKSPERIMENTAL]", "clockwise": "Med uret", "close": "Luk", "collapse": "Klap sammen", @@ -683,7 +723,6 @@ "comments_and_likes": "Kommentarer og likes", "comments_are_disabled": "Kommentarer er slået fra", "common_create_new_album": "Opret et nyt album", - "common_server_error": "Tjek din internetforbindelse, sørg for at serveren er tilgængelig og at app- og serversioner er kompatible.", "completed": "Fuldført", "confirm": "Bekræft", "confirm_admin_password": "Bekræft administratoradgangskode", @@ -722,6 +761,7 @@ "create": "Opret", "create_album": "Opret album", "create_album_page_untitled": "Uden titel", + "create_api_key": "Opret API nøgle", "create_library": "Opret bibliotek", "create_link": "Opret link", "create_link_to_share": "Opret link for at dele", @@ -738,6 +778,7 @@ "create_user": "Opret bruger", "created": "Oprettet", "created_at": "Oprettet", + "creating_linked_albums": "Opretter sammenkædede albums...", "crop": "Beskær", "curated_object_page_title": "Ting", "current_device": "Nuværende enhed", @@ -750,6 +791,7 @@ "daily_title_text_date_year": "E, dd MMM, yyyy", "dark": "Mørk", "dark_theme": "Skift til mørkt tema", + "date": "Dato", "date_after": "Dato efter", "date_and_time": "Dato og klokkeslæt", "date_before": "Dato før", @@ -864,7 +906,6 @@ "edit_tag": "Rediger tag", "edit_title": "Redigér titel", "edit_user": "Redigér bruger", - "edited": "Redigeret", "editor": "Redaktør", "editor_close_without_save_prompt": "Ændringerne vil ikke blive gemt", "editor_close_without_save_title": "Luk editor?", @@ -887,7 +928,9 @@ "error": "Fejl", "error_change_sort_album": "Ændring af sorteringsrækkefølgen mislykkedes", "error_delete_face": "Fejl ved sletning af ansigt fra mediefil", + "error_getting_places": "Fejl ved hentning af steder", "error_loading_image": "Fejl ved indlæsning af billede", + "error_loading_partners": "Fejl ved indlæsning af partnere: {error}", "error_saving_image": "Fejl: {error}", "error_tag_face_bounding_box": "Fejl ved tagging af ansigt - kan ikke finde koordinator for afgrænsningskasse", "error_title": "Fejl - Noget gik galt", @@ -1018,6 +1061,7 @@ "exif_bottom_sheet_description_error": "Fejl ved opdatering af beskrivelsen", "exif_bottom_sheet_details": "DETALJER", "exif_bottom_sheet_location": "LOKATION", + "exif_bottom_sheet_no_description": "Ingen beskrivelse", "exif_bottom_sheet_people": "PERSONER", "exif_bottom_sheet_person_add_person": "Tilføj navn", "exit_slideshow": "Afslut slideshow", @@ -1052,9 +1096,11 @@ "favorites_page_no_favorites": "Ingen favoritter blev fundet", "feature_photo_updated": "Forsidebillede uploadet", "features": "Funktioner", + "features_in_development": "Funktioner under udvikling", "features_setting_description": "Administrer app-funktioner", "file_name": "Filnavn", "file_name_or_extension": "Filnavn eller filtype", + "file_size": "Fil størrelse", "filename": "Filnavn", "filetype": "Filtype", "filter": "Filter", @@ -1094,11 +1140,10 @@ "hash_asset": "Hash objekter", "hashed_assets": "Hashede objekter", "hashing": "Hasher", - "header_settings_add_header_tip": "Tilføj Header", + "header_settings_add_header_tip": "Tilføj header", "header_settings_field_validator_msg": "Værdi kan ikke være tom", "header_settings_header_name_input": "Header navn", "header_settings_header_value_input": "Header værdi", - "headers_settings_tile_subtitle": "Definer proxy headers appen skal sende med hver netværks forespørgsel", "headers_settings_tile_title": "Brugerdefineret proxy headers", "hi_user": "Hej {name} ({email})", "hide_all_people": "Skjul alle personer", @@ -1216,8 +1261,10 @@ "local": "Lokal", "local_asset_cast_failed": "Kan ikke caste et aktiv, der ikke er uploadet til serveren", "local_assets": "Lokale objekter", + "local_media_summary": "Opsummering af lokale media", "local_network": "Lokalt netværk", "local_network_sheet_info": "Appen vil oprette forbindelse til serveren via denne URL, når du bruger det angivne WiFi-netværk", + "location": "Lokation", "location_permission": "Tilladelse til placering", "location_permission_content": "For automatisk at skifte netværk, skal Immich *altid* have præcis placeringsadgang, så appen kan læse Wi-Fi netværkets navn", "location_picker_choose_on_map": "Vælg på kort", @@ -1227,6 +1274,7 @@ "location_picker_longitude_hint": "Indtast din længdegrad her", "lock": "Lås", "locked_folder": "Låst mappe", + "log_detail_title": "Logdetaljer", "log_out": "Log ud", "log_out_all_devices": "Log ud af alle enheder", "logged_in_as": "Logget ind som {user}", @@ -1257,6 +1305,7 @@ "login_password_changed_success": "Kodeordet blev opdateret", "logout_all_device_confirmation": "Er du sikker på, at du vil logge ud af alle enheder?", "logout_this_device_confirmation": "Er du sikker på, at du vil logge denne enhed ud?", + "logs": "Logs", "longitude": "Længdegrad", "look": "Kig", "loop_videos": "Gentag videoer", @@ -1299,6 +1348,7 @@ "mark_as_read": "Marker som læst", "marked_all_as_read": "Markerede alle som læst", "matches": "Parringer", + "matching_assets": "Matchende objekter", "media_type": "Medietype", "memories": "Minder", "memories_all_caught_up": "Ajour", @@ -1319,36 +1369,43 @@ "minute": "Minut", "minutes": "Minutter", "missing": "Mangler", + "mobile_app": "Mobil App", + "mobile_app_download_onboarding_note": "Hent den tilhørende mobilapp via en af følgende muligheder", "model": "Model", "month": "Måned", - "monthly_title_text_date_format": "MMMM y", + "monthly_title_text_date_format": "MMMM å", "more": "Mere", "move": "Flyt", "move_off_locked_folder": "Flyt ud af låst mappe", - "move_to_lock_folder_action_prompt": "{count} føjet til i den låste mappe", + "move_to_lock_folder_action_prompt": "{count} føjet til den låste mappe", "move_to_locked_folder": "Flyt til låst mappe", "move_to_locked_folder_confirmation": "Disse billeder og videoer vil blive fjernet fra alle albums, og vil kun være synlig fra den låste mappe", "moved_to_archive": "Flyttede {count, plural, one {# mediefil} other {# mediefiler}} til arkivet", "moved_to_library": "Flyttede {count, plural, one {# mediefil} other {# mediefiler}} til biblioteket", "moved_to_trash": "Flyttet til skraldespand", - "multiselect_grid_edit_date_time_err_read_only": "Kan ikke redigere datoen på kun læselige elementer. Springer over", - "multiselect_grid_edit_gps_err_read_only": "Kan ikke redigere lokation af kun læselige elementer. Springer over", + "multiselect_grid_edit_date_time_err_read_only": "Kan ikke redigere datoen på skrivebeskyttet elementer. Springer over", + "multiselect_grid_edit_gps_err_read_only": "Kan ikke redigere lokation af skrivebeskyttet elementer. Springer over", "mute_memories": "Dæmp minder", "my_albums": "Mine albummer", "name": "Navn", - "name_or_nickname": "Navn eller kælenavn", + "name_or_nickname": "Navn eller kaldenavn", + "navigate": "Naviger", + "navigate_to_time": "Naviger til tid", "network_requirement_photos_upload": "Benyt mobildatanettet for at sikkerhedskopiere dine fotos", "network_requirement_videos_upload": "Benyt mobildatanettet for at sikkerhedskopiere dine videoer", + "network_requirements": "Netværkskrav", "network_requirements_updated": "Netværkskravene er ændret, backup-køen nulstilles", "networking_settings": "Netværk", "networking_subtitle": "Administrer serverens endepunktindstillinger", - "never": "aldrig", + "never": "Aldrig", "new_album": "Nyt album", "new_api_key": "Ny API-nøgle", + "new_date_range": "Nyt datointerval", "new_password": "Ny adgangskode", "new_person": "Ny person", "new_pin_code": "Ny PIN kode", "new_pin_code_subtitle": "Dette er første gang du tilgår den låste mappe. Lav en PIN kode for sikkert at tilgå denne side", + "new_timeline": "Ny tidslinje", "new_user_created": "Ny bruger oprettet", "new_version_available": "NY VERSION TILGÆNGELIG", "newest_first": "Nyeste først", @@ -1358,24 +1415,29 @@ "no_albums_message": "Opret et album for at organisere dine billeder og videoer", "no_albums_with_name_yet": "Det ser ud til, at du ikke har noget album med dette navn endnu.", "no_albums_yet": "Det ser ud til, at du ikke har nogen album endnu.", - "no_archived_assets_message": "Arkivér billeder og videoer for at gemme dem væk fra din Billede oversigt", + "no_archived_assets_message": "Arkivér billeder og videoer for at gemme dem væk fra din billedoversigt", "no_assets_message": "KLIK FOR AT UPLOADE DIT FØRSTE BILLEDE", "no_assets_to_show": "Ingen elementer at vise", "no_cast_devices_found": "Ingen Cast-enheder fundet", + "no_checksum_local": "Ingen checksum tilgængelig – kan ikke hente lokale objekter", + "no_checksum_remote": "Ingen checksum tilgængelig – kan ikke hente eksterne objekter", "no_duplicates_found": "Ingen duplikater fundet.", "no_exif_info_available": "Ingen tilgængelig exif information", "no_explore_results_message": "Upload flere billeder for at udforske din samling.", "no_favorites_message": "Tilføj favoritter for hurtigt at finde dine bedst billeder og videoer", "no_libraries_message": "Opret et eksternt bibliotek for at se dine billeder og videoer", + "no_local_assets_found": "Ingen lokale objekter fundet med denne checksum", "no_locked_photos_message": "Billeder og videoer i den låste mappe er skjulte og vil ikke blive vist i dit bibliotek.", "no_name": "Intet navn", "no_notifications": "Ingen notifikationer", "no_people_found": "Ingen tilsvarende personer fundet", "no_places": "Ingen steder", + "no_remote_assets_found": "Ingen eksterne objekter fundet med denne checksum", "no_results": "Ingen resultater", "no_results_description": "Prøv et synonym eller et mere generelt søgeord", "no_shared_albums_message": "Opret et album for at dele billeder og videoer med personer i dit netværk", "no_uploads_in_progress": "Ingen upload i gang", + "not_available": "ikke tilgængelig", "not_in_any_album": "Ikke i noget album", "not_selected": "Ikke valgt", "note_apply_storage_label_to_previously_uploaded assets": "Bemærk: For at anvende Lagringsmærkat på tidligere uploadede medier, kør", @@ -1389,6 +1451,9 @@ "notifications": "Notifikationer", "notifications_setting_description": "Administrér notifikationer", "oauth": "OAuth", + "obtainium_configurator": "Obtainium-konfigurator", + "obtainium_configurator_instructions": "Brug Obtainium til at installere og opdatere Android-appen direkte fra Immich-udgivelsen på GitHub. Opret en API-nøgle, og vælg en variant for at generere dit Obtainium-konfigurationslink", + "ocr": "OCR", "official_immich_resources": "Officielle Immich-ressourcer", "offline": "Offline", "offset": "Forskydning", @@ -1493,10 +1558,14 @@ "play_memories": "Afspil minder", "play_motion_photo": "Afspil bevægelsesbillede", "play_or_pause_video": "Afspil eller pause video", + "play_original_video": "Afspil original video", + "play_original_video_setting_description": "Foretrækker afspilning af originale videoer frem for transkodede videoer. Hvis det originale element ikke er kompatibelt, afspilles det muligvis ikke korrekt.", + "play_transcoded_video": "Afspil transkodet video", "please_auth_to_access": "Log venligst ind for at tilgå", "port": "Port", "preferences_settings_subtitle": "Administrer app-præferencer", "preferences_settings_title": "Præferencer", + "preparing": "Forberedelse", "preset": "Forudindstilling", "preview": "Forhåndsvisning", "previous": "Forrige", @@ -1509,13 +1578,9 @@ "privacy": "Privatliv", "profile": "Profil", "profile_drawer_app_logs": "Log", - "profile_drawer_client_out_of_date_major": "Mobilapp er forældet. Opdater venligst til den nyeste større version.", - "profile_drawer_client_out_of_date_minor": "Mobilapp er forældet. Opdater venligst til den nyeste mindre version.", "profile_drawer_client_server_up_to_date": "Klient og server er ajour", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Skrivebeskyttet tilstand aktiveret. Lang tryk på bruger avatar ikonet for at afslutte.", - "profile_drawer_server_out_of_date_major": "Server er forældet. Opdater venligst til den nyeste større version.", - "profile_drawer_server_out_of_date_minor": "Server er forældet. Opdater venligst til den nyeste mindre version.", "profile_image_of_user": "Profilbillede af {user}", "profile_picture_set": "Profilbillede indstillet.", "public_album": "Offentligt album", @@ -1562,6 +1627,7 @@ "read_changelog": "Læs ændringslog", "readonly_mode_disabled": "Skrivebeskyttet tilstand deaktiveret", "readonly_mode_enabled": "Skrivebeskyttet tilstand aktiveret", + "ready_for_upload": "Klar til upload", "reassign": "Gentildel", "reassigned_assets_to_existing_person": "{count, plural, one {# mediefil} other {# mediefiler}} er blevet gentildelt til {name, select, null {en eksisterende person} other {{name}}}", "reassigned_assets_to_new_person": "Gentildelt {count, plural, one {# aktiv} other {# aktiver}} til en ny person", @@ -1586,6 +1652,7 @@ "regenerating_thumbnails": "Regenererer forhåndsvisninger", "remote": "Eksternt", "remote_assets": "Eksterne objekter", + "remote_media_summary": "Oversigt over eksterne media", "remove": "Fjern", "remove_assets_album_confirmation": "Er du sikker på, at du vil fjerne {count, plural, one {# aktiv} other {# aktiver}} fra albummet?", "remove_assets_shared_link_confirmation": "Er du sikker på, at du vil fjerne {count, plural, one {# aktiv} other {# aktiver}} fra dette delte link?", @@ -1630,6 +1697,7 @@ "reset_sqlite_confirmation": "Er du sikker på, at du vil nulstille SQLite databasen? Du er nødt til at logge ud og ind igen for at gensynkronisere dine data", "reset_sqlite_success": "Vellykket reset af SQLite databasen", "reset_to_default": "Nulstil til standard", + "resolution": "Opløsning", "resolve_duplicates": "Løs dubletter", "resolved_all_duplicates": "Alle dubletter løst", "restore": "Gendan", @@ -1648,6 +1716,7 @@ "running": "Kører", "save": "Gem", "save_to_gallery": "Gem til galleri", + "saved": "Gemt", "saved_api_key": "Gemt API-nøgle", "saved_profile": "Gemte profil", "saved_settings": "Gemte indstillinger", @@ -1664,6 +1733,9 @@ "search_by_description_example": "Vandredag i Paris", "search_by_filename": "Søg efter filnavn eller filtypenavn", "search_by_filename_example": "dvs. IMG_1234.JPG eller PNG", + "search_by_ocr": "Søg via OCR", + "search_by_ocr_example": "Latte", + "search_camera_lens_model": "Søg objektiv model...", "search_camera_make": "Søg efter kameraproducent...", "search_camera_model": "Søg efter kameramodel...", "search_city": "Søg efter by...", @@ -1680,6 +1752,7 @@ "search_filter_location_title": "Vælg lokation", "search_filter_media_type": "Medietype", "search_filter_media_type_title": "Vælg medietype", + "search_filter_ocr": "Søg via OCR", "search_filter_people_title": "Vælg personer", "search_for": "Søg efter", "search_for_existing_person": "Søg efter eksisterende person", @@ -1742,6 +1815,7 @@ "server_online": "Server online", "server_privacy": "Serverens privatliv", "server_stats": "Serverstatus", + "server_update_available": "Serveropdatering er tilgængelig", "server_version": "Server version", "set": "Indstil", "set_as_album_cover": "Indstil som albumcover", @@ -1770,6 +1844,8 @@ "setting_notifications_subtitle": "Tilpas dine notifikationspræferencer", "setting_notifications_total_progress_subtitle": "Samlet uploadstatus (færdige/samlet antal elementer)", "setting_notifications_total_progress_title": "Vis samlet baggrundsuploadstatus", + "setting_video_viewer_auto_play_subtitle": "Begynd automatisk at afspille videoer, når de åbnes", + "setting_video_viewer_auto_play_title": "Automatisk afspilning af videoer", "setting_video_viewer_looping_title": "Looper", "setting_video_viewer_original_video_subtitle": "Når der streames video fra serveren, afspil da den originale selv når en omkodet udgave er tilgængelig. Kan føre til buffering. Videoer, der er tilgængelige lokalt, afspilles i original kvalitet uanset denne indstilling.", "setting_video_viewer_original_video_title": "Tving original video", @@ -1861,6 +1937,7 @@ "show_slideshow_transition": "Vis overgang til diasshow", "show_supporter_badge": "Supportermærke", "show_supporter_badge_description": "Vis et supportermærke", + "show_text_search_menu": "Vis tekstsøgningsmenu", "shuffle": "Bland", "sidebar": "Sidebjælke", "sidebar_display_description": "Vis et link til visningen i sidebjælken", @@ -1891,6 +1968,7 @@ "stacktrace": "Stacktrace", "start": "Start", "start_date": "Startdato", + "start_date_before_end_date": "Startdato skal ligge før slutdato", "state": "Stat", "status": "Status", "stop_casting": "Stop støbning", @@ -1947,6 +2025,7 @@ "theme_setting_three_stage_loading_title": "Slå tre-trins indlæsning til", "they_will_be_merged_together": "De vil blive slået sammen", "third_party_resources": "Tredjepartsressourcer", + "time": "Tid", "time_based_memories": "Tidsbaserede minder", "timeline": "Tidslinje", "timezone": "Tidszone", @@ -1979,6 +2058,7 @@ "troubleshoot": "Fejlfinding", "type": "Type", "unable_to_change_pin_code": "Kunne ikke ændre PIN kode", + "unable_to_check_version": "Kan ikke tjekke app- eller serverversion", "unable_to_setup_pin_code": "Kunne ikke sætte PIN kode", "unarchive": "Afakivér", "unarchive_action_prompt": "{count} slettet fra Arkiv", @@ -2093,5 +2173,6 @@ "yes": "Ja", "you_dont_have_any_shared_links": "Du har ikke nogen delte links", "your_wifi_name": "Dit Wi-Fi navn", - "zoom_image": "Zoom billede" + "zoom_image": "Zoom billede", + "zoom_to_bounds": "Zoom til grænserne" } diff --git a/i18n/de.json b/i18n/de.json index cef8a5b4d5..0cae9c0762 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -28,10 +28,12 @@ "add_to_album": "Zu Album hinzufügen", "add_to_album_bottom_sheet_added": "Zu {album} hinzugefügt", "add_to_album_bottom_sheet_already_exists": "Bereits in {album}", + "add_to_album_bottom_sheet_some_local_assets": "Einige lokale Dateien konnten nicht zum Album hinzugefügt werden", "add_to_album_toggle": "Auswahl umschalten für {album}", "add_to_albums": "Zu Alben hinzufügen", "add_to_albums_count": "Zu Alben hinzufügen ({count})", "add_to_shared_album": "Zu geteiltem Album hinzufügen", + "add_upload_to_stack": "Upload zum Stapel hinzufügen", "add_url": "URL hinzufügen", "added_to_archive": "Zum Archiv hinzugefügt", "added_to_favorites": "Zu Favoriten hinzugefügt", @@ -47,14 +49,14 @@ "background_task_job": "Hintergrundaufgaben", "backup_database": "Datenbanksicherung erstellen", "backup_database_enable_description": "Datenbank regelmäßig sichern", - "backup_keep_last_amount": "Anzahl der aufzubewahrenden früheren Backups", + "backup_keep_last_amount": "Anzahl der aufzubewahrenden früheren Sicherungen", "backup_onboarding_1_description": "Offsite-Kopie in der Cloud oder an einem anderen physischen Ort.", - "backup_onboarding_2_description": "Lokale Kopien auf verschiedenen Geräten. Dazu gehören die Hauptdateien und eine lokale Sicherung dieser Dateien.", - "backup_onboarding_3_description": "3 komplette Kopien deiner Daten, inkl. der Originaldateien. Dies umfasst 1 Kopie an einem anderen Ort und 2 lokale Kopie.", - "backup_onboarding_description": "Eine 3-2-1 Backup-Strategie wird empfohlen, um deine Daten zu schützen. Du solltest sowohl Kopien deiner hochgeladenen Fotos/Videos als auch der Immich-Datenbank aufbewahren, um eine umfassende Backup-Lösung zu haben.", + "backup_onboarding_2_description": "lokale Kopien auf verschiedenen Geräten. Dazu gehören die Hauptdateien und eine lokale Sicherung dieser Dateien.", + "backup_onboarding_3_description": "Kopien deiner Daten inklusive Originaldateien. Dies umfasst 1 Kopie an einem anderen Ort und 2 lokale Kopien.", + "backup_onboarding_description": "Eine 3-2-1 Sicherungsstrategie wird empfohlen, um deine Daten zu schützen. Du solltest sowohl Kopien deiner hochgeladenen Fotos/Videos als auch der Immich-Datenbank aufbewahren, um eine umfassende Sicherungslösung zu haben.", "backup_onboarding_footer": "Weitere Informationen zum Sichern von Immich findest du in der Dokumentation.", "backup_onboarding_parts_title": "Eine 3-2-1-Sicherung umfasst:", - "backup_onboarding_title": "Backups", + "backup_onboarding_title": "Sicherungen", "backup_settings": "Einstellungen für Datenbanksicherung", "backup_settings_description": "Einstellungen zur regelmäßigen Sicherung der Datenbank. Hinweis: Diese Jobs werden nicht überwacht und du wirst nicht über Fehler informiert.", "cleared_jobs": "Folgende Aufgaben zurückgesetzt: {job}", @@ -64,11 +66,11 @@ "confirm_email_below": "Bestätige, indem du unten \"{email}\" eingibst", "confirm_reprocess_all_faces": "Bist du sicher, dass du alle Gesichter erneut verarbeiten möchtest? Dies löscht auch alle bereits benannten Personen.", "confirm_user_password_reset": "Bist du sicher, dass du das Passwort für {user} zurücksetzen möchtest?", - "confirm_user_pin_code_reset": "Bist du sicher, dass du den PIN Code von {user} zurücksetzen möchtest?", + "confirm_user_pin_code_reset": "Bist du sicher, dass du den PIN-Code von {user} zurücksetzen möchtest?", "create_job": "Aufgabe erstellen", - "cron_expression": "Cron Zeitangabe", - "cron_expression_description": "Setze ein Intervall für die Sicherung mittels cron. Hilfe mit dem Format bietet dir dabei z. B. der Crontab Guru", - "cron_expression_presets": "Nützliche Zeitangaben für Cron", + "cron_expression": "Cron-Zeitangabe", + "cron_expression_description": "Setze das Scanintervall im Cron-Format. Hilfe mit dem Format bietet dir dabei z. B. der Crontab Guru", + "cron_expression_presets": "Vorlagen für Cron-Zeitangabe", "disable_login": "Login deaktivieren", "duplicate_detection_job_description": "Diese Aufgabe führt das maschinelle Lernen für jede Datei aus, um Duplikate zu finden. Diese Aufgabe beruht auf der intelligenten Suche", "exclusion_pattern_description": "Mit Ausschlussmustern können Dateien und Ordner beim Scannen Ihrer Bibliothek ignoriert werden. Dies ist nützlich, wenn du Ordner hast, die Dateien enthalten, die du nicht importieren möchtest, wie z. B. RAW-Dateien.", @@ -118,7 +120,7 @@ "library_settings_description": "Einstellungen externer Bibliotheken verwalten", "library_tasks_description": "Überprüfe externe Bibliotheken auf neue und/oder veränderte Medien", "library_watching_enable_description": "Überwache externe Bibliotheken auf Dateiänderungen", - "library_watching_settings": "Bibliotheksüberwachung (EXPERIMENTELL)", + "library_watching_settings": "Überwache Bibliothek [EXPERIMENTELL]", "library_watching_settings_description": "Automatisch auf geänderte Dateien prüfen", "logging_enable_description": "Aktiviere Logging", "logging_level_description": "Wenn aktiviert, welches Log-Level genutzt wird.", @@ -152,6 +154,18 @@ "machine_learning_min_detection_score_description": "Minimale Konfidenzrate für die Erkennung eines Gesichts von 0-1. Bei niedrigeren Werten werden mehr Gesichter erkannt, aber es kann zu falsch-positiven Ergebnissen kommen.", "machine_learning_min_recognized_faces": "Mindestens erkannte Gesichter", "machine_learning_min_recognized_faces_description": "Die Mindestanzahl von erkannten Gesichtern, damit eine Person erstellt werden kann. Eine Erhöhung dieses Wertes macht die Gesichtserkennung präziser, erhöht aber die Wahrscheinlichkeit, dass ein Gesicht nicht zu einer Person zugeordnet wird.", + "machine_learning_ocr": "OCR", + "machine_learning_ocr_description": "Maschinelles Lernen nutzen um Texte in Bildern zu erkennen", + "machine_learning_ocr_enabled": "OCR aktivieren", + "machine_learning_ocr_enabled_description": "Wenn deaktiviert, werden die Bilder nicht von der Texterkennung bearbeitet.", + "machine_learning_ocr_max_resolution": "Maximale Auflösung", + "machine_learning_ocr_max_resolution_description": "Vorschauen über dieser Auflösung werden unter Beibehaltung des Seitenverhältnisses verkleinert. Höhere Werte sind genauer, benötigen jedoch mehr Zeit für die Verarbeitung und verbrauchen mehr Speicher.", + "machine_learning_ocr_min_detection_score": "Minimaler Erkennungswert", + "machine_learning_ocr_min_detection_score_description": "Minimale Konfidenzrate für die Texterkennung von 0–1. Niedrigere Werte führen dazu, dass mehr Text erkannt wird, können jedoch zu falsch-positiven Ergebnissen führen.", + "machine_learning_ocr_min_recognition_score": "Minimale Erkennungsrate", + "machine_learning_ocr_min_score_recognition_description": "Minimale Konfidenzrate für die Erkennung von erkanntem Text von 0–1. Niedrigere Werte führen dazu, dass mehr Text erkannt wird, können jedoch zu falsch-positiven Ergebnissen führen.", + "machine_learning_ocr_model": "OCR Modell", + "machine_learning_ocr_model_description": "Server Modelle sind genauer als mobile Modelle, brauchen aber länger zur Verarbeitung und brauchen mehr Speicher.", "machine_learning_settings": "Einstellungen für maschinelles Lernen", "machine_learning_settings_description": "Funktionen und Einstellungen des maschinellen Lernens verwalten", "machine_learning_smart_search": "Intelligente Suche", @@ -209,6 +223,8 @@ "notification_email_ignore_certificate_errors_description": "TLS-Zertifikatsvalidierungsfehler ignorieren (nicht empfohlen)", "notification_email_password_description": "Passwort für die Anmeldung am E-Mail-Server", "notification_email_port_description": "Port des E-Mail-Servers (z.B. 25, 465, oder 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "Benutze SMTPS (SMTP über TLS)", "notification_email_sent_test_email_button": "Test-E-Mail versenden und speichern", "notification_email_setting_description": "Einstellungen für E-Mail-Benachrichtigungen", "notification_email_test_email": "Test-E-Mail senden", @@ -241,6 +257,7 @@ "oauth_storage_quota_default_description": "Kontingent in GiB, das verwendet werden soll, wenn keines übermittelt wird.", "oauth_timeout": "Zeitüberschreitung bei Anfrage", "oauth_timeout_description": "Zeitüberschreitung für Anfragen in Millisekunden", + "ocr_job_description": "Verwende Machine Learning zur Erkennung von Text in Bildern", "password_enable_description": "Mit E-Mail und Passwort anmelden", "password_settings": "Passwort-Anmeldung", "password_settings_description": "Passwort-Anmeldeeinstellungen verwalten", @@ -285,13 +302,13 @@ "storage_template_user_label": "{label} is die Speicherpfadbezeichnung des Benutzers", "system_settings": "Systemeinstellungen", "tag_cleanup_job": "Tags aufräumen", - "template_email_available_tags": "In deiner Vorlage kannst du die folgenden Variablen verwenden: {tags}", - "template_email_if_empty": "Wenn die Vorlage leer ist, wird die Standard-E-Mail verwendet.", - "template_email_invite_album": "E-Mail-Vorlage: Einladung zu Album", + "template_email_available_tags": "Du kannst die folgenden Variablen in deiner Vorlage verwenden: {tags}", + "template_email_if_empty": "Wenn die Vorlage leer ist, wird die Standard-E-Mail-Vorlage verwendet.", + "template_email_invite_album": "Einladung zu Album", "template_email_preview": "Vorschau", "template_email_settings": "E-Mail-Vorlagen", - "template_email_update_album": "Album-Vorlage aktualisieren", - "template_email_welcome": "Willkommen bei den E-Mail-Vorlagen", + "template_email_update_album": "Aktualisiertes Album", + "template_email_welcome": "Willkommens-E-Mail", "template_settings": "Benachrichtigungsvorlagen", "template_settings_description": "Benutzerdefinierte Vorlagen für Benachrichtigungen verwalten", "theme_custom_css_settings": "Benutzerdefiniertes CSS", @@ -331,7 +348,7 @@ "transcoding_max_b_frames": "Maximale B-Frames", "transcoding_max_b_frames_description": "Höhere Werte verbessern die Komprimierungseffizienz, verlangsamen aber die Kodierung. Ist möglicherweise nicht mit der Hardware-Beschleunigung älterer Geräte kompatibel. 0 deaktiviert die B-Frames, während -1 diesen Wert automatisch setzt.", "transcoding_max_bitrate": "Maximale Bitrate", - "transcoding_max_bitrate_description": "Die Festlegung einer maximalen Bitrate kann die Dateigrößen vorhersagbarer machen, ohne dass die Qualität darunter leidet. Bei 720p sind typische Werte 2600 kbit/s für VP9 oder HEVC oder 4500 kbit/s für H.264. Deaktiviert, wenn der Wert auf 0 gesetzt ist.", + "transcoding_max_bitrate_description": "Das Festlegen einer maximalen Bitrate kann die Dateigrößen vorhersagbarer machen, ohne dass die Qualität darunter leidet. Bei 720p sind typische Werte 2600 kbit/s für VP9 oder HEVC oder 4500 kbit/s für H.264. Deaktiviert, wenn der Wert auf 0 gesetzt ist. Wenn keine Einheit angegeben wird, wird von k (für kbit/s) ausgegangen; also sind 5000, 5000k und 5M (für Mbit/s) identisch.", "transcoding_max_keyframe_interval": "Maximales Keyframe-Intervall", "transcoding_max_keyframe_interval_description": "Legt den maximalen Frame-Abstand zwischen Keyframes fest. Niedrigere Werte verschlechtern die Komprimierungseffizienz, verbessern aber die Suchzeiten und können die Qualität in Szenen mit schnellen Bewegungen verbessern. Bei 0 wird dieser Wert automatisch eingestellt.", "transcoding_optimal_description": "Videos mit einer höheren Auflösung als der Zielauflösung oder in einem nicht akzeptierten Format", @@ -349,7 +366,7 @@ "transcoding_target_resolution": "Ziel-Auflösung", "transcoding_target_resolution_description": "Höhere Auflösungen können mehr Details erhalten, benötigen aber mehr Zeit für die Codierung, haben größere Dateigrößen und können die Reaktionszeit der Anwendung beeinträchtigen.", "transcoding_temporal_aq": "Temporäre AQ", - "transcoding_temporal_aq_description": "Gilt nur für NVENC. Verbessert die Qualität von Szenen mit hohem Detailreichtum und geringen Bewegungen. Dies ist möglicherweise nicht mit älteren Geräten kompatibel.", + "transcoding_temporal_aq_description": "Gilt nur für NVENC. Zeitlich adaptive Quantisierung verbessert die Qualität von Szenen mit hohem Detailreichtum und geringen Bewegungen. Dies ist möglicherweise nicht mit älteren Geräten kompatibel.", "transcoding_threads": "Threads", "transcoding_threads_description": "Höhere Werte führen zu einer schnelleren Kodierung, lassen dem Server jedoch weniger Spielraum für die Verarbeitung anderer Aufgaben im aktiven Zustand. Dieser Wert sollte nicht höher sein als die Anzahl der CPU-Kerne. Maximiert die Auslastung, wenn der Wert auf 0 gesetzt wird.", "transcoding_tone_mapping": "Farbton-Mapping", @@ -400,11 +417,11 @@ "advanced_settings_prefer_remote_subtitle": "Einige Geräte sind sehr langsam beim Laden von lokalen Vorschaubildern. Aktivieren Sie diese Einstellung, um stattdessen die Server-Bilder zu laden.", "advanced_settings_prefer_remote_title": "Server-Bilder bevorzugen", "advanced_settings_proxy_headers_subtitle": "Definiere einen Proxy-Header, den Immich bei jeder Netzwerkanfrage mitschicken soll", - "advanced_settings_proxy_headers_title": "Proxy-Headers", + "advanced_settings_proxy_headers_title": "Benutzerdefinierte Proxy-Header [Experimentell]", "advanced_settings_readonly_mode_subtitle": "Aktiviert den schreibgeschützten Modus, in dem die Fotos nur angezeigt werden können. Funktionen wie das Auswählen mehrerer Bilder, das Teilen, das Übertragen und das Löschen sind deaktiviert. Aktivieren/Deaktiviere den schreibgeschützten Modus über den Benutzer-Avatar auf dem Hauptbildschirm", "advanced_settings_readonly_mode_title": "Schreibgeschützter Modus", "advanced_settings_self_signed_ssl_subtitle": "Verifizierung von SSL-Zertifikaten vom Server überspringen. Notwendig bei selbstsignierten Zertifikaten.", - "advanced_settings_self_signed_ssl_title": "Selbstsignierte SSL-Zertifikate erlauben", + "advanced_settings_self_signed_ssl_title": "Selbstsignierte SSL-Zertifikate erlauben [Experimentell]", "advanced_settings_sync_remote_deletions_subtitle": "Automatisches Löschen oder Wiederherstellen einer Datei auf diesem Gerät, wenn diese Aktion im Web durchgeführt wird", "advanced_settings_sync_remote_deletions_title": "Mit Server-Löschungen synchronisieren [Experimentell]", "advanced_settings_tile_subtitle": "Erweiterte Benutzereinstellungen", @@ -464,10 +481,14 @@ "api_key_description": "Dieser Wert wird nur einmal angezeigt. Bitte kopiere ihn, bevor du das Fenster schließt.", "api_key_empty": "Dein API-Schlüssel-Name darf nicht leer sein", "api_keys": "API-Schlüssel", + "app_architecture_variant": "Variante (Architektur)", "app_bar_signout_dialog_content": "Bist du dir sicher, dass du dich abmelden möchtest?", "app_bar_signout_dialog_ok": "Ja", "app_bar_signout_dialog_title": "Abmelden", + "app_download_links": "App Download Links", "app_settings": "App-Einstellungen", + "app_stores": "App Stores", + "app_update_available": "App Update verfügbar", "appears_in": "Erscheint in", "apply_count": "Anwenden ({count, number})", "archive": "Archiv", @@ -537,20 +558,21 @@ "autoplay_slideshow": "Automatische Diashow", "back": "Zurück", "back_close_deselect": "Zurück, Schließen oder Abwählen", - "background_backup_running_error": "Hintergrund Sicherung läuft, kann manuelle Sicherung nicht starten", + "background_backup_running_error": "Sicherung läuft im Hintergrund. Manuelle Sicherung kann nicht gestartet werden", "background_location_permission": "Hintergrund Standortfreigabe", "background_location_permission_content": "Um im Hintergrund zwischen den Netzwerken wechseln zu können, muss Immich *immer* Zugriff auf den genauen Standort haben, damit die App den Namen des WLAN-Netzwerks ermitteln kann", "background_options": "Hintergrund Optionen", "backup": "Sicherung", "backup_album_selection_page_albums_device": "Alben auf dem Gerät ({count})", - "backup_album_selection_page_albums_tap": "Einmalig das Album antippen um es zu sichern, doppelt antippen um es nicht mehr zu sichern", + "backup_album_selection_page_albums_tap": "Antippen zum sichern, erneut antippen zum Ausschließen", "backup_album_selection_page_assets_scatter": "Elemente (Fotos / Videos) können sich über mehrere Alben verteilen. Daher können diese vor der Sicherung eingeschlossen oder ausgeschlossen werden.", "backup_album_selection_page_select_albums": "Alben auswählen", - "backup_album_selection_page_selection_info": "Information", - "backup_album_selection_page_total_assets": "Elemente", - "backup_albums_sync": "Synchronisation von Alben beim Backup", + "backup_album_selection_page_selection_info": "Auswahlinformation", + "backup_album_selection_page_total_assets": "Elemente gesamt", + "backup_albums_sync": "Synchronisation der Sicherungsalben", "backup_all": "Alle", "backup_background_service_backup_failed_message": "Es trat ein Fehler bei der Sicherung auf. Erneuter Versuch…", + "backup_background_service_complete_notification": "Datei Backup abgeschlossen", "backup_background_service_connection_failed_message": "Es konnte keine Verbindung zum Server hergestellt werden. Erneuter Versuch…", "backup_background_service_current_upload_notification": "Lädt {filename} hoch", "backup_background_service_default_notification": "Suche nach neuen Elementen…", @@ -598,6 +620,7 @@ "backup_controller_page_turn_on": "Sicherung im Vordergrund einschalten", "backup_controller_page_uploading_file_info": "Informationen", "backup_err_only_album": "Das einzige Album kann nicht entfernt werden", + "backup_error_sync_failed": "Synchronisierung fehlgeschlagen. Sicherung kann nicht verarbeitet werden.", "backup_info_card_assets": "Elemente", "backup_manual_cancelled": "Abgebrochen", "backup_manual_in_progress": "Sicherung läuft bereits. Bitte versuche es später erneut", @@ -659,10 +682,12 @@ "change_password_description": "Dies ist entweder das erste Mal, dass du dich im System anmeldest, oder es wurde eine Anfrage zur Änderung deines Passworts gestellt. Bitte gib unten dein neues Passwort ein.", "change_password_form_confirm_password": "Passwort bestätigen", "change_password_form_description": "Hallo {name}\n\nDas ist entweder das erste Mal dass du dich einloggst oder es wurde eine Anfrage zur Änderung deines Passwortes gestellt. Bitte gib das neue Passwort ein.", + "change_password_form_log_out": "Von allen Geräte abmelden", + "change_password_form_log_out_description": "Es wird empfohlen, alle anderen Geräte abzumelden", "change_password_form_new_password": "Neues Passwort", "change_password_form_password_mismatch": "Passwörter stimmen nicht überein", "change_password_form_reenter_new_password": "Passwort erneut eingeben", - "change_pin_code": "PIN Code ändern", + "change_pin_code": "PIN-Code ändern", "change_your_password": "Ändere dein Passwort", "changed_visibility_successfully": "Die Sichtbarkeit wurde erfolgreich geändert", "charging": "Aufladen", @@ -686,7 +711,7 @@ "client_cert_invalid_msg": "Ungültige Zertifikatsdatei oder falsches Passwort", "client_cert_remove_msg": "Client Zertifikat wurde entfernt", "client_cert_subtitle": "Unterstützt nur das PKCS12 (.p12, .pfx) Format. Zertifikatsimporte oder -entfernungen sind nur vor dem Login möglich", - "client_cert_title": "SSL-Client-Zertifikat", + "client_cert_title": "SSL-Client-Zertifikat [Experimentell]", "clockwise": "Im Uhrzeigersinn", "close": "Schließen", "collapse": "Zusammenklappen", @@ -698,14 +723,13 @@ "comments_and_likes": "Kommentare & Likes", "comments_are_disabled": "Kommentare sind deaktiviert", "common_create_new_album": "Neues Album erstellen", - "common_server_error": "Bitte überprüfe deine Netzwerkverbindung und stelle sicher, dass die App und Server Versionen kompatibel sind.", "completed": "Abgeschlossen", "confirm": "Bestätigen", "confirm_admin_password": "Administrator Passwort bestätigen", "confirm_delete_face": "Bist du sicher dass du das Gesicht von {name} aus der Datei entfernen willst?", "confirm_delete_shared_link": "Bist du sicher, dass du diesen geteilten Link löschen willst?", "confirm_keep_this_delete_others": "Alle anderen Dateien im Stapel bis auf diese werden gelöscht. Bist du sicher, dass du fortfahren möchten?", - "confirm_new_pin_code": "Neuen PIN Code bestätigen", + "confirm_new_pin_code": "Neuen PIN-Code bestätigen", "confirm_password": "Passwort bestätigen", "confirm_tag_face": "Wollen Sie dieses Gesicht mit {name} markieren?", "confirm_tag_face_unnamed": "Möchten Sie dieses Gesicht markieren?", @@ -737,6 +761,7 @@ "create": "Erstellen", "create_album": "Album erstellen", "create_album_page_untitled": "Unbenannt", + "create_api_key": "API Key erstellen", "create_library": "Bibliothek erstellen", "create_link": "Link erstellen", "create_link_to_share": "Link zum Teilen erstellen", @@ -757,7 +782,7 @@ "crop": "Zuschneiden", "curated_object_page_title": "Dinge", "current_device": "Aktuelles Gerät", - "current_pin_code": "Aktueller PIN Code", + "current_pin_code": "Aktueller PIN-Code", "current_server_address": "Aktuelle Serveradresse", "custom_locale": "Benutzerdefinierte Sprache", "custom_locale_description": "Datumsangaben und Zahlen je nach Sprache und Land formatieren", @@ -766,6 +791,7 @@ "daily_title_text_date_year": "E, dd MMM, yyyy", "dark": "Dunkel", "dark_theme": "Dunkle Ansicht umschalten", + "date": "Datum", "date_after": "Datum nach", "date_and_time": "Datum und Zeit", "date_before": "Datum vor", @@ -880,7 +906,6 @@ "edit_tag": "Tag bearbeiten", "edit_title": "Titel bearbeiten", "edit_user": "Nutzer bearbeiten", - "edited": "Bearbeitet", "editor": "Bearbeiter", "editor_close_without_save_prompt": "Die Änderungen werden nicht gespeichert", "editor_close_without_save_title": "Editor schließen?", @@ -893,13 +918,13 @@ "empty_trash_confirmation": "Bist du sicher, dass du den Papierkorb leeren willst?\nDies entfernt alle Dateien im Papierkorb endgültig aus Immich und kann nicht rückgängig gemacht werden!", "enable": "Aktivieren", "enable_backup": "Sicherung aktivieren", - "enable_biometric_auth_description": "Gib deinen PIN Code ein, um die biometrische Authentifizierung zu aktivieren", + "enable_biometric_auth_description": "Gib deinen PIN-Code ein, um die biometrische Authentifizierung zu aktivieren", "enabled": "Aktiviert", "end_date": "Enddatum", "enqueued": "Eingereiht", "enter_wifi_name": "WLAN-Name eingeben", - "enter_your_pin_code": "PIN Code eingeben", - "enter_your_pin_code_subtitle": "Gib deinen PIN Code ein, um auf den gesperrten Ordner zuzugreifen", + "enter_your_pin_code": "PIN-Code eingeben", + "enter_your_pin_code_subtitle": "Gib deinen PIN-Code ein, um auf den gesperrten Ordner zuzugreifen", "error": "Fehler", "error_change_sort_album": "Ändern der Anzeigereihenfolge fehlgeschlagen", "error_delete_face": "Fehler beim Löschen des Gesichts", @@ -938,7 +963,7 @@ "failed_to_load_notifications": "Fehler beim Laden der Benachrichtigungen", "failed_to_load_people": "Fehler beim Laden von Personen", "failed_to_remove_product_key": "Fehler beim Entfernen des Produktschlüssels", - "failed_to_reset_pin_code": "Zurücksetzen des PIN Codes fehlgeschlagen", + "failed_to_reset_pin_code": "Zurücksetzen des PIN-Codes fehlgeschlagen", "failed_to_stack_assets": "Dateien konnten nicht gestapelt werden", "failed_to_unstack_assets": "Dateien konnten nicht entstapelt werden", "failed_to_update_notification_status": "Benachrichtigungsstatus aktualisieren fehlgeschlagen", @@ -1003,7 +1028,7 @@ "unable_to_remove_partner": "Partner kann nicht entfernt werden", "unable_to_remove_reaction": "Reaktion kann nicht entfernt werden", "unable_to_reset_password": "Passwort kann nicht zurückgesetzt werden", - "unable_to_reset_pin_code": "Zurücksetzen des PIN Code nicht möglich", + "unable_to_reset_pin_code": "Zurücksetzen des PIN-Code nicht möglich", "unable_to_resolve_duplicate": "Duplikate können nicht aufgelöst werden", "unable_to_restore_assets": "Dateien konnten nicht wiederhergestellt werden", "unable_to_restore_trash": "Papierkorb kann nicht wiederhergestellt werden", @@ -1036,6 +1061,7 @@ "exif_bottom_sheet_description_error": "Fehler bei der Aktualisierung der Beschreibung", "exif_bottom_sheet_details": "DETAILS", "exif_bottom_sheet_location": "STANDORT", + "exif_bottom_sheet_no_description": "Keine Beschreibung", "exif_bottom_sheet_people": "PERSONEN", "exif_bottom_sheet_person_add_person": "Namen hinzufügen", "exit_slideshow": "Diashow beenden", @@ -1074,6 +1100,7 @@ "features_setting_description": "Funktionen der App verwalten", "file_name": "Dateiname", "file_name_or_extension": "Dateiname oder -erweiterung", + "file_size": "Dateigröße", "filename": "Dateiname", "filetype": "Dateityp", "filter": "Filter", @@ -1086,7 +1113,7 @@ "folder_not_found": "Ordner nicht gefunden", "folders": "Ordner", "folders_feature_description": "Durchsuchen der Ordneransicht für Fotos und Videos im Dateisystem", - "forgot_pin_code_question": "PIN Code vergessen?", + "forgot_pin_code_question": "PIN-Code vergessen?", "forward": "Vorwärts", "gcast_enabled": "Google Cast", "gcast_enabled_description": "Diese Funktion lädt externe Quellen von Google, um zu funktionieren.", @@ -1117,7 +1144,6 @@ "header_settings_field_validator_msg": "Der Wert darf nicht leer sein", "header_settings_header_name_input": "Header-Name", "header_settings_header_value_input": "Header-Wert", - "headers_settings_tile_subtitle": "Definiere einen Proxy-Header, den die Anwendung bei jeder Netzwerkanfrage mitschicken soll", "headers_settings_tile_title": "Benutzerdefinierte Proxy-Header", "hi_user": "Hallo {name} ({email})", "hide_all_people": "Alle Personen verbergen", @@ -1238,6 +1264,7 @@ "local_media_summary": "Zusammenfassung der lokalen Medien", "local_network": "Lokales Netzwerk", "local_network_sheet_info": "Die App stellt über diese URL eine Verbindung zum Server her, wenn sie das angegebene WLAN-Netzwerk verwendet", + "location": "Standort", "location_permission": "Standort Genehmigung", "location_permission_content": "Um die automatische Umschaltfunktion nutzen zu können, benötigt Immich genaue Standortberechtigung, damit es den Namen des aktuellen WLAN-Netzwerks ermitteln kann", "location_picker_choose_on_map": "Auf der Karte auswählen", @@ -1328,7 +1355,7 @@ "memories_check_back_tomorrow": "Schau morgen wieder vorbei für weitere Erinnerungen", "memories_setting_description": "Verwalte, was du in deinen Erinnerungen siehst", "memories_start_over": "Erneut beginnen", - "memories_swipe_to_close": "Nach oben Wischen zum schließen", + "memories_swipe_to_close": "Nach oben Wischen zum Schließen", "memory": "Erinnerung", "memory_lane_title": "Foto-Erinnerungen {title}", "menu": "Menü", @@ -1342,6 +1369,8 @@ "minute": "Minute", "minutes": "Minuten", "missing": "Fehlende", + "mobile_app": "Mobile App", + "mobile_app_download_onboarding_note": "Herunterladen der mobilen Begleiter-App über einen der folgenden Möglichkeiten", "model": "Modell", "month": "Monat", "monthly_title_text_date_format": "MMMM y", @@ -1360,8 +1389,10 @@ "my_albums": "Meine Alben", "name": "Name", "name_or_nickname": "Name oder Nickname", - "network_requirement_photos_upload": "Mobiles Datennetz verwenden, um Fotos zu sichern", - "network_requirement_videos_upload": "Mobiles Datennetz verwenden, um Videos zu sichern", + "navigate": "Navigation", + "navigate_to_time": "Navigiere zu Zeit", + "network_requirement_photos_upload": "Mobile Daten verwenden, um Fotos zu sichern", + "network_requirement_videos_upload": "Mobile Daten verwenden, um Videos zu sichern", "network_requirements": "Anforderungen ans Netzwerk", "network_requirements_updated": "Netzwerk-Abhängigkeiten haben sich geändert, Backup-Warteschlange wird zurückgesetzt", "networking_settings": "Netzwerk", @@ -1369,10 +1400,11 @@ "never": "Niemals", "new_album": "Neues Album", "new_api_key": "Neuer API-Schlüssel", + "new_date_range": "Neuer Datumsbereich", "new_password": "Neues Passwort", "new_person": "Neue Person", - "new_pin_code": "Neuer PIN Code", - "new_pin_code_subtitle": "Dies ist dein erster Zugriff auf den gesperrten Ordner. Erstelle einen PIN Code für den sicheren Zugriff auf diese Seite", + "new_pin_code": "Neuer PIN-Code", + "new_pin_code_subtitle": "Dies ist dein erster Zugriff auf den gesperrten Ordner. Erstelle einen PIN-Code für den sicheren Zugriff auf diese Seite", "new_timeline": "Neue Zeitleiste", "new_user_created": "Neuer Benutzer wurde erstellt", "new_version_available": "NEUE VERSION VERFÜGBAR", @@ -1419,6 +1451,9 @@ "notifications": "Benachrichtigungen", "notifications_setting_description": "Benachrichtigungen verwalten", "oauth": "OAuth", + "obtainium_configurator": "Obtainium Konfiguratior", + "obtainium_configurator_instructions": "Du kannst Obtainium benutzen, um die App direkt aus den Github Releases zu installieren oder zu aktualisieren. Bitte erstelle dazu einen API-Schlüssel und wähle eine Variante aus um einen Obtainium-Konfigurationslink zu erstellen", + "ocr": "OCR", "official_immich_resources": "Offizielle Immich Quellen", "offline": "Offline", "offset": "Verschiebung", @@ -1503,7 +1538,7 @@ "person": "Person", "person_age_months": "{months, plural, one {# month} other {# months}} alt", "person_age_year_months": "1 Jahr, {months, plural, one {# month} other {# months}} alt", - "person_age_years": "{years, plural, other {# years}} alt", + "person_age_years": "{years, plural, one {# Jahr} other {# Jahre}} alt", "person_birthdate": "Geboren am {date}", "person_hidden": "{name}{hidden, select, true { (verborgen)} other {}}", "photo_shared_all_users": "Es sieht so aus, als hättest du deine Fotos mit allen Benutzern geteilt oder du hast keine Benutzer, mit denen du teilen kannst.", @@ -1512,10 +1547,10 @@ "photos_count": "{count, plural, one {{count, number} Foto} other {{count, number} Fotos}}", "photos_from_previous_years": "Fotos von vorherigen Jahren", "pick_a_location": "Wähle einen Ort", - "pin_code_changed_successfully": "PIN Code erfolgreich geändert", - "pin_code_reset_successfully": "PIN Code erfolgreich zurückgesetzt", - "pin_code_setup_successfully": "PIN Code erfolgreich festgelegt", - "pin_verification": "PIN Code Überprüfung", + "pin_code_changed_successfully": "PIN-Code erfolgreich geändert", + "pin_code_reset_successfully": "PIN-Code erfolgreich zurückgesetzt", + "pin_code_setup_successfully": "PIN-Code erfolgreich festgelegt", + "pin_verification": "PIN-Code Überprüfung", "place": "Ort", "places": "Orte", "places_count": "{count, plural, one {{count, number} Ort} other {{count, number} Orte}}", @@ -1523,10 +1558,14 @@ "play_memories": "Erinnerungen abspielen", "play_motion_photo": "Bewegte Bilder abspielen", "play_or_pause_video": "Video abspielen oder pausieren", + "play_original_video": "Originales Video abspielen", + "play_original_video_setting_description": "Bevorzugen die Wiedergabe von Originalvideos gegenüber transkodierten Videos. Wenn das Original nicht kompatibel ist, wird es möglicherweise nicht korrekt wiedergegeben.", + "play_transcoded_video": "Transkodiertes Video abspielen", "please_auth_to_access": "Für den Zugriff bitte Authentifizieren", "port": "Port", "preferences_settings_subtitle": "App-Einstellungen verwalten", "preferences_settings_title": "Voreinstellungen", + "preparing": "Vorbereiten", "preset": "Voreinstellung", "preview": "Vorschau", "previous": "Vorherige", @@ -1539,13 +1578,9 @@ "privacy": "Privatsphäre", "profile": "Profil", "profile_drawer_app_logs": "Logs", - "profile_drawer_client_out_of_date_major": "Mobile-App ist veraltet. Bitte aktualisiere auf die neueste Major-Version.", - "profile_drawer_client_out_of_date_minor": "Mobile-App ist veraltet. Bitte aktualisiere auf die neueste Minor-Version.", "profile_drawer_client_server_up_to_date": "Die App- und Server-Versionen sind aktuell", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Schreibgeschützter Modus aktiviert. Halte das Benutzer-Avatar-Symbol gedrückt, um den Modus zu verlassen.", - "profile_drawer_server_out_of_date_major": "Server-Version ist veraltet. Bitte aktualisiere auf die neueste Major-Version.", - "profile_drawer_server_out_of_date_minor": "Server-Version ist veraltet. Bitte aktualisiere auf die neueste Minor-Version.", "profile_image_of_user": "Profilbild von {user}", "profile_picture_set": "Profilbild gesetzt.", "public_album": "Öffentliches Album", @@ -1569,7 +1604,7 @@ "purchase_license_subtitle": "Kaufe Immich, um die fortlaufende Entwicklung zu unterstützen", "purchase_lifetime_description": "Lebenslange Gültigkeit", "purchase_option_title": "KAUFOPTIONEN", - "purchase_panel_info_1": "Die Entwicklung von Immich erfordert viel Zeit und Mühe, und wir haben Vollzeit-Entwickler, die daran arbeiten es möglichst perfekt zu machen. Unser Ziel ist es, dass Open-Source-Software und moralische Geschäftsmethoden zu einer nachhaltigen Einkommensquelle für Entwickler werden und ein datenschutzfreundliches Ökosystem mit echten Alternativen zu ausbeuterischen Cloud-Diensten geschaffen wird.", + "purchase_panel_info_1": "Die Entwicklung von Immich erfordert viel Zeit und Mühe und wir haben Vollzeit-Entwickler, die daran arbeiten Immich möglichst perfekt zu machen. Unser Ziel ist es, Open-Source-Software und ethische Geschäftspraktiken zu einer verlässlichen Einkommensquelle für Entwickler zu machen und ein datenschutzfreundliches Ökosystem mit echten Alternativen zu ausbeuterischen Cloud-Diensten zu schaffen.", "purchase_panel_info_2": "Weil wir uns dagegen entschieden haben, eine Bezahlschranke einzusetzen, wird dieser Kauf keine zusätzlichen Funktionen in Immich freischalten. Wir verlassen uns auf Nutzende wie dich, um die Entwicklung von Immich zu unterstützen.", "purchase_panel_title": "Das Projekt unterstützen", "purchase_per_server": "Pro Server", @@ -1592,6 +1627,7 @@ "read_changelog": "Changelog lesen", "readonly_mode_disabled": "Schreibgeschützter Modus deaktiviert", "readonly_mode_enabled": "Schreibgeschützter Modus aktiviert", + "ready_for_upload": "Bereit zum Hochladen", "reassign": "Neu zuweisen", "reassigned_assets_to_existing_person": "{count, plural, one {# Datei wurde} other {# Dateien wurden}} {name, select, null {einer vorhandenen Person} other {{name}}} zugewiesen", "reassigned_assets_to_new_person": "{count, plural, one {# Datei wurde} other {# Dateien wurden}} einer neuen Person zugewiesen", @@ -1646,21 +1682,22 @@ "repair": "Reparatur", "repair_no_results_message": "Nicht auffindbare und fehlende Dateien werden hier angezeigt", "replace_with_upload": "Durch Upload ersetzen", - "repository": "Repository", + "repository": "Repositorium", "require_password": "Passwort erforderlich", "require_user_to_change_password_on_first_login": "Benutzer muss das Passwort beim ersten Login ändern", "rescan": "Erneut scannen", "reset": "Zurücksetzen", "reset_password": "Passwort zurücksetzen", "reset_people_visibility": "Sichtbarkeit von Personen zurücksetzen", - "reset_pin_code": "PIN Code zurücksetzen", - "reset_pin_code_description": "Falls du deinen PIN Code vergessen hast, wende dich an deinen Immich-Administrator um ihn zurücksetzen zu lassen", - "reset_pin_code_success": "PIN Code erfolgreich zurückgesetzt", - "reset_pin_code_with_password": "Mit deinem Passwort kannst du jederzeit deinen PIN Code zurücksetzen", + "reset_pin_code": "PIN-Code zurücksetzen", + "reset_pin_code_description": "Falls du deinen PIN-Code vergessen hast, kannst du dich an den Server-Administrator wenden, um ihn zurückzusetzen", + "reset_pin_code_success": "PIN-Code erfolgreich zurückgesetzt", + "reset_pin_code_with_password": "Mit deinem Passwort kannst du jederzeit deinen PIN-Code zurücksetzen", "reset_sqlite": "SQLite Datenbank zurücksetzen", "reset_sqlite_confirmation": "Bist du sicher, dass du die SQLite-Datenbank zurücksetzen willst? Du musst dich ab- und wieder anmelden, um die Daten neu zu synchronisieren", "reset_sqlite_success": "SQLite Datenbank erfolgreich zurückgesetzt", "reset_to_default": "Auf Standard zurücksetzen", + "resolution": "Auflösung", "resolve_duplicates": "Duplikate entfernen", "resolved_all_duplicates": "Alle Duplikate aufgelöst", "restore": "Wiederherstellen", @@ -1679,6 +1716,7 @@ "running": "Läuft", "save": "Speichern", "save_to_gallery": "In Galerie speichern", + "saved": "Gespeichert", "saved_api_key": "API-Schlüssel wurde gespeichert", "saved_profile": "Profil gespeichert", "saved_settings": "Einstellungen gespeichert", @@ -1695,6 +1733,9 @@ "search_by_description_example": "Wandern in Sapa", "search_by_filename": "Suche nach Dateiname oder -erweiterung", "search_by_filename_example": "z.B. IMG_1234.JPG oder PNG", + "search_by_ocr": "Suche per OCR", + "search_by_ocr_example": "Latte", + "search_camera_lens_model": "Suche nach Kameralinse...", "search_camera_make": "Suche nach Kameramarke...", "search_camera_model": "Suche nach Kameramodell...", "search_city": "Suche nach Stadt...", @@ -1711,6 +1752,7 @@ "search_filter_location_title": "Ort auswählen", "search_filter_media_type": "Medientyp", "search_filter_media_type_title": "Medientyp auswählen", + "search_filter_ocr": "Suche per OCR", "search_filter_people_title": "Personen auswählen", "search_for": "Suche nach", "search_for_existing_person": "Suche nach vorhandener Person", @@ -1773,6 +1815,7 @@ "server_online": "Server online", "server_privacy": "Privatsphäre auf dem Server", "server_stats": "Server-Statistiken", + "server_update_available": "Server Update verfügbar", "server_version": "Server-Version", "set": "Speichern", "set_as_album_cover": "Als Albumcover festlegen", @@ -1801,13 +1844,15 @@ "setting_notifications_subtitle": "Benachrichtigungen anpassen", "setting_notifications_total_progress_subtitle": "Gesamter Upload-Fortschritt (abgeschlossen/Anzahl Elemente)", "setting_notifications_total_progress_title": "Zeige den Gesamtfortschritt der Hintergrundsicherung", + "setting_video_viewer_auto_play_subtitle": "Videos automatisch wiedergeben sobald sie geöffnet werden", + "setting_video_viewer_auto_play_title": "Videos automatisch wiedergeben", "setting_video_viewer_looping_title": "Video-Wiederholung", "setting_video_viewer_original_video_subtitle": "Beim Streaming eines Videos vom Server wird das Original abgespielt, auch wenn eine Transkodierung verfügbar ist. Kann zu Pufferung führen. Lokal verfügbare Videos werden unabhängig von dieser Einstellung in Originalqualität wiedergegeben.", "setting_video_viewer_original_video_title": "Originalvideo erzwingen", "settings": "Einstellungen", "settings_require_restart": "Bitte starte Immich neu, um diese Einstellung anzuwenden", "settings_saved": "Einstellungen gespeichert", - "setup_pin_code": "Einen PIN Code festlegen", + "setup_pin_code": "Einen PIN-Code festlegen", "share": "Teilen", "share_action_prompt": "{count} Dateien geteilt", "share_add_photos": "Fotos hinzufügen", @@ -1950,7 +1995,7 @@ "sync_remote": "mit Server synchronisieren", "sync_status": "Synchronisierungstatus", "sync_status_subtitle": "Synchronisierungssystem anzeigen und bearbeiten", - "sync_upload_album_setting_subtitle": "Erstelle deine ausgewählten Alben in Immich und lade die Fotos und Videos dort hoch", + "sync_upload_album_setting_subtitle": "Erstelle und lade deine ausgewählten Fotos und Videos in die ausgewählten Alben auf Immich hoch", "tag": "Tag", "tag_assets": "Dateien taggen", "tag_created": "Tag erstellt: {tag}", @@ -1980,6 +2025,7 @@ "theme_setting_three_stage_loading_title": "Dreistufiges Laden aktivieren", "they_will_be_merged_together": "Sie werden zusammengeführt", "third_party_resources": "Drittanbieter-Quellen", + "time": "Zeit", "time_based_memories": "Zeitbasierte Erinnerungen", "timeline": "Zeitleiste", "timezone": "Zeitzone", @@ -2008,11 +2054,12 @@ "trash_page_restore_all": "Alle wiederherstellen", "trash_page_select_assets_btn": "Elemente auswählen", "trash_page_title": "Papierkorb ({count})", - "trashed_items_will_be_permanently_deleted_after": "Gelöschte Objekte werden nach {days, plural, one {# Tag} other {# Tagen}} endgültig gelöscht.", + "trashed_items_will_be_permanently_deleted_after": "Objekte im Papierkorb werden nach {days, plural, one {# Tag} other {# Tagen}} endgültig gelöscht.", "troubleshoot": "Fehler beheben", "type": "Typ", - "unable_to_change_pin_code": "PIN Code konnte nicht geändert werden", - "unable_to_setup_pin_code": "PIN Code konnte nicht festgelegt werden", + "unable_to_change_pin_code": "PIN-Code konnte nicht geändert werden", + "unable_to_check_version": "App oder Server Versionscheck nicht möglich", + "unable_to_setup_pin_code": "PIN-Code konnte nicht festgelegt werden", "unarchive": "Entarchivieren", "unarchive_action_prompt": "{count} aus dem Archiv entfernt", "unarchived_count": "{count, plural, other {# entarchiviert}}", @@ -2069,8 +2116,8 @@ "user_has_been_deleted": "Dieser Benutzer wurde gelöscht.", "user_id": "Nutzer-ID", "user_liked": "{type, select, photo {Dieses Foto} video {Dieses Video} asset {Diese Datei} other {Dies}} gefällt {user}", - "user_pin_code_settings": "PIN Code", - "user_pin_code_settings_description": "Verwalte deinen PIN Code", + "user_pin_code_settings": "PIN-Code", + "user_pin_code_settings_description": "Verwalte deinen PIN-Code", "user_privacy": "Datenschutzeinstellungen Nutzer", "user_purchase_settings": "Kauf", "user_purchase_settings_description": "Kauf verwalten", @@ -2081,7 +2128,7 @@ "username": "Nutzername", "users": "Benutzer", "users_added_to_album_count": "{count, plural, one {# Benutzer} other {# Benutzer}} zum Album hinzugefügt", - "utilities": "Hilfsmittel", + "utilities": "Werkzeuge", "validate": "Validieren", "validate_endpoint_error": "Bitte gib eine gültige URL ein", "variables": "Variablen", @@ -2120,7 +2167,7 @@ "welcome": "Willkommen", "welcome_to_immich": "Willkommen bei Immich", "wifi_name": "WLAN-Name", - "wrong_pin_code": "PIN Code falsch", + "wrong_pin_code": "PIN-Code falsch", "year": "Jahr", "years_ago": "Vor {years, plural, one {einem Jahr} other {# Jahren}}", "yes": "Ja", diff --git a/i18n/el.json b/i18n/el.json index a8eeb59740..0ff48a02ba 100644 --- a/i18n/el.json +++ b/i18n/el.json @@ -11,10 +11,10 @@ "activity_changed": "Η δραστηριότητα είναι {enabled, select, true {ενεργοποιημένη} other {απενεργοποιημένη}}", "add": "Προσθήκη", "add_a_description": "Προσθήκη περιγραφής", - "add_a_location": "Προσθήκη μίας τοποθεσίας", - "add_a_name": "Προσθέστε ένα όνομα", + "add_a_location": "Προσθήκη τοποθεσίας", + "add_a_name": "Προσθήκη ονόματος", "add_a_title": "Προσθήκη τίτλου", - "add_birthday": "Προσθέστε την ημερομηνία γενεθλίων", + "add_birthday": "Προσθήκη γενεθλίων", "add_endpoint": "Προσθήκη τελικού σημείου", "add_exclusion_pattern": "Προσθήκη μοτίβου αποκλεισμού", "add_import_path": "Προσθήκη μονοπατιού εισαγωγής", @@ -28,10 +28,12 @@ "add_to_album": "Προσθήκη σε άλμπουμ", "add_to_album_bottom_sheet_added": "Προστέθηκε στο {album}", "add_to_album_bottom_sheet_already_exists": "Ήδη στο {album}", + "add_to_album_bottom_sheet_some_local_assets": "Ορισμένα τοπικά στοιχεία δεν μπόρεσαν να προστεθούν στο άλμπουμ", "add_to_album_toggle": "Εναλλαγή επιλογής για το {album}", "add_to_albums": "Προσθήκη στα άλμπουμ", "add_to_albums_count": "Προσθήκη στα άλμπουμ ({count})", "add_to_shared_album": "Προσθήκη σε κοινόχρηστο άλμπουμ", + "add_upload_to_stack": "Προσθήκη αρχείου στην ουρά", "add_url": "Προσθήκη Συνδέσμου", "added_to_archive": "Προστέθηκε στο αρχείο", "added_to_favorites": "Προστέθηκε στα αγαπημένα", @@ -123,6 +125,13 @@ "logging_enable_description": "Ενεργοποίηση καταγραφής συμβάντων", "logging_level_description": "Το επίπεδο καταγραφής συμβάντων που θα εφαρμοστεί, όταν αυτή είναι ενεργοποιημένη.", "logging_settings": "Καταγραφή Συμβάντων", + "machine_learning_availability_checks": "Έλεγχοι διαθεσιμότητας", + "machine_learning_availability_checks_description": "Αυτόματος ανίχνευση και προτίμηση διαθέσιμων διακομιστών μηχανικής μάθησης", + "machine_learning_availability_checks_enabled": "Ενεργοποίηση ελέγχων διαθεσιμότητας", + "machine_learning_availability_checks_interval": "Διάστημα ελέγχου", + "machine_learning_availability_checks_interval_description": "Διάστημα σε χιλιοστά δευτερολέπτου μεταξύ των ελέγχων διαθεσιμότητας", + "machine_learning_availability_checks_timeout": "Αίτημα χρονικού ορίου λήξης", + "machine_learning_availability_checks_timeout_description": "Χρονικό όριο σε χιλιοστά δευτερολέπτου για ελέγχους διαθεσιμότητας", "machine_learning_clip_model": "Μοντέλο CLIP", "machine_learning_clip_model_description": "Το όνομα ενός μοντέλου CLIP που αναφέρεται εδώ. Σημειώστε ότι πρέπει να επανεκτελέσετε την εργασία 'Έξυπνη Αναζήτηση' για όλες τις εικόνες μετά την αλλαγή μοντέλου.", "machine_learning_duplicate_detection": "Εντοπισμός Διπλότυπων", @@ -202,6 +211,7 @@ "notification_email_ignore_certificate_errors_description": "Παράβλεψη σφαλμάτων επικύρωσης της πιστοποίησης TLS (δεν προτείνεται)", "notification_email_password_description": "Κωδικός για την αυθεντικοποίηση με τον server του email", "notification_email_port_description": "Θύρα του email server (πχ 25, 465, ή 587)", + "notification_email_secure_description": "Χρήση SMTPS (SMTP over TLS)", "notification_email_sent_test_email_button": "Αποστολή test email και αποθήκευση", "notification_email_setting_description": "Ρυθμίσεις για την αποστολή ειδοποιήσεων μέσω email", "notification_email_test_email": "Αποστολή test email", @@ -324,7 +334,7 @@ "transcoding_max_b_frames": "Μέγιστος αριθμός B-frames(Bidirectional Predictive Frames)", "transcoding_max_b_frames_description": "Οι υψηλότερες τιμές βελτιώνουν την αποδοτικότητα της συμπίεσης, αλλά επιβραδύνουν την κωδικοποίηση. Ενδέχεται να μην είναι συμβατές με την επιτάχυνση υλικού σε παλαιότερες συσκευές. Η τιμή 0 απενεργοποιεί τα B-frames, ενώ η -1, τη ρυθμίζει αυτόματα.", "transcoding_max_bitrate": "Μέγιστος ρυθμός μετάδοσης (bitrate)", - "transcoding_max_bitrate_description": "Η ρύθμιση ενός μέγιστου ρυθμού μετάδοσης(bitrate) μπορεί να κάνει το μέγεθος των αρχείων πιο προβλέψιμο, αλλά με ένα μικρό κόστος στην ποιότητα. Στην ανάλυση των 720p, οι τυπικές τιμές είναι 2600 kbit/s για VP9 ή HEVC, ή 4500 kbit/s για H.264. Απενεργοποιείται εάν οριστεί σε 0.", + "transcoding_max_bitrate_description": "Ο καθορισμός του μέγιστου bitrate μπορεί να κάνει το μέγεθος των αρχείων πιο προβλέψιμο, με ένα μικρό κόστος στην ποιότητα. Στα 720p, οι τυπικές τιμές είναι 2600 kbit/s για VP9 ή HEVC, ή 4500 kbit/s για H.264. Αν οριστεί σε 0, η ρύθμιση απενεργοποιείται. Όταν δεν καθορίζεται, θεωρείται το k (για kbit/s)· επομένως τα 5000, 5000k και 5M (για Mbit/s) είναι ισοδύναμα.", "transcoding_max_keyframe_interval": "Μέγιστο χρονικό διάστημα μεταξύ των καρέ αναφοράς (keyframe)", "transcoding_max_keyframe_interval_description": "Ορίζει το μέγιστο διάστημα μεταξύ των καρέ αναφοράς. Χαμηλότερες τιμές μειώνουν την αποδοτικότητα συμπίεσης, αλλά βελτιώνουν τον χρόνο αναζήτησης και μπορεί να βελτιώσουν την ποιότητα σε σκηνές με γρήγορη κίνηση. Η τιμή 0 ρυθμίζει αυτό το διάστημα αυτόματα.", "transcoding_optimal_description": "Βίντεο με ανώτερη ανάλυση από την επιθυμητή ή σε μη αποδεκτή μορφή", @@ -342,7 +352,7 @@ "transcoding_target_resolution": "Επιθυμητή ανάλυση", "transcoding_target_resolution_description": "Οι υψηλότερες αναλύσεις μπορούν να διατηρήσουν περισσότερες λεπτομέρειες, αλλά απαιτούν περισσότερο χρόνο για κωδικοποίηση, παράγουν μεγαλύτερα αρχεία και μπορεί να μειώσουν την απόκριση της εφαρμογής.", "transcoding_temporal_aq": "Χρονική Προσαρμοστική Ποιότητα AQ(Adaptive Quantization)", - "transcoding_temporal_aq_description": "Ισχύει μόνο για NVENC. Αυξάνει την ποιότητα σε σκηνές με υψηλή λεπτομέρεια και χαμηλή κίνηση. Ενδέχεται να μην είναι συμβατό με παλαιότερες συσκευές.", + "transcoding_temporal_aq_description": "Ισχύει μόνο για το NVENC. Η Χρονική προσαρμογή ποιότητας (Temporal Adaptive Quantization) βελτιώνει την ποιότητα σε σκηνές με υψηλή λεπτομέρεια και χαμηλή κίνηση. Ενδέχεται να μην είναι συμβατή με παλαιότερες συσκευές.", "transcoding_threads": "Νήματα (παράλληλες διεργασίες)", "transcoding_threads_description": "Οι υψηλότερες τιμές οδηγούν σε ταχύτερη κωδικοποίηση, αλλά αφήνουν λιγότερο χώρο στον διακομιστή για να επεξεργαστεί άλλες εργασίες όσο είναι ενεργή. Αυτή η τιμή δεν πρέπει να ξεπερνά τον αριθμό των πυρήνων του επεξεργαστή. Η μέγιστη αξιοποίηση επιτυγχάνεται αν οριστεί στο 0.", "transcoding_tone_mapping": "Χαρτογράφηση χρωματικών τόνων", @@ -394,6 +404,8 @@ "advanced_settings_prefer_remote_title": "Προτίμηση απομακρυσμένων εικόνων", "advanced_settings_proxy_headers_subtitle": "Καθορισμός κεφαλίδων διακομιστή μεσολάβησης που το Immich πρέπει να στέλνει με κάθε αίτημα δικτύου", "advanced_settings_proxy_headers_title": "Κεφαλίδες διακομιστή μεσολάβησης", + "advanced_settings_readonly_mode_subtitle": "Ενεργοποιεί τη λειτουργία μόνο-για-ανάγνωση, όπου οι φωτογραφίες μπορούν μόνο να προβληθούν. Ενέργειες όπως επιλογή πολλών εικόνων, κοινή χρήση, αποστολή (casting) και διαγραφή είναι απενεργοποιημένες. Η ενεργοποίηση/απενεργοποίηση της λειτουργίας μόνο-για-ανάγνωση γίνεται μέσω της εικόνας του χρήστη από την κεντρική οθόνη", + "advanced_settings_readonly_mode_title": "Λειτουργία μόνο-για-ανάγνωση", "advanced_settings_self_signed_ssl_subtitle": "Παρακάμπτει τον έλεγχο πιστοποιητικού SSL του διακομιστή. Απαραίτητο για αυτο-υπογεγραμμένα πιστοποιητικά.", "advanced_settings_self_signed_ssl_title": "Να επιτρέπονται αυτο-υπογεγραμμένα πιστοποιητικά SSL", "advanced_settings_sync_remote_deletions_subtitle": "Αυτόματη διαγραφή ή επαναφορά ενός περιουσιακού στοιχείου σε αυτή τη συσκευή, όταν η ενέργεια αυτή πραγματοποιείται στο διαδίκτυο", @@ -421,6 +433,7 @@ "album_remove_user_confirmation": "Είστε σίγουροι ότι θέλετε να αφαιρέσετε τον/την {user};", "album_search_not_found": "Δε βρέθηκαν άλμπουμ που να ταιριάζουν με την αναζήτησή σας", "album_share_no_users": "Φαίνεται ότι έχετε κοινοποιήσει αυτό το άλμπουμ σε όλους τους χρήστες ή δεν έχετε χρήστες για να το κοινοποιήσετε.", + "album_summary": "Περίληψη άλμπουμ", "album_updated": "Το άλμπουμ, ενημερώθηκε", "album_updated_setting_description": "Λάβετε ειδοποίηση μέσω email όταν ένα κοινόχρηστο άλμπουμ έχει νέα αρχεία", "album_user_left": "Αποχωρήσατε από το {album}", @@ -454,11 +467,14 @@ "api_key_description": "Αυτή η τιμή θα εμφανιστεί μόνο μία φορά. Παρακαλώ βεβαιωθείτε ότι την έχετε αντιγράψει πριν κλείσετε το παράθυρο.", "api_key_empty": "Το όνομα του κλειδιού API, δεν πρέπει να είναι κενό", "api_keys": "Κλειδιά API", + "app_architecture_variant": "Παραλλαγή (Αρχιτεκτονική)", "app_bar_signout_dialog_content": "Είστε βέβαιοι ότι θέλετε να αποσυνδεθείτε;", "app_bar_signout_dialog_ok": "Ναι", "app_bar_signout_dialog_title": "Αποσύνδεση", + "app_download_links": "Σύνδεσμοι Λήψης Εφαρμογής", "app_settings": "Ρυθμίσεις εφαρμογής", "appears_in": "Εμφανίζεται σε", + "apply_count": "Εφαρμογή ({count, number})", "archive": "Αρχείο", "archive_action_prompt": "Προστέθηκαν {count} στο Αρχείο", "archive_or_unarchive_photo": "Αρχειοθέτηση ή αποαρχειοθέτηση φωτογραφίας", @@ -491,6 +507,8 @@ "asset_restored_successfully": "Το στοιχείο αποκαταστάθηκε με επιτυχία", "asset_skipped": "Παραλείφθηκε", "asset_skipped_in_trash": "Στον κάδο απορριμμάτων", + "asset_trashed": "Το στοιχείο διαγράφηκε", + "asset_troubleshoot": "Αντιμετώπιση προβλήματος στοιχείου", "asset_uploaded": "Ανεβάστηκε", "asset_uploading": "Ανεβάζεται…", "asset_viewer_settings_subtitle": "Διαχείριση ρυθμίσεων προβολής συλλογής", @@ -524,8 +542,10 @@ "autoplay_slideshow": "Αυτόματη αναπαραγωγή παρουσίασης", "back": "Πίσω", "back_close_deselect": "Πίσω, κλείσιμο ή αποεπιλογή", + "background_backup_running_error": "Η δημιουργία αντιγράφων ασφάλειας στο παρασκήνιο εκτελείται ήδη, δεν μπορεί να ξεκινήσετε χειροκίνητο αντίγραφο ασφάλειας", "background_location_permission": "Άδεια τοποθεσίας στο παρασκήνιο", "background_location_permission_content": "Το Immich για να μπορεί να αλλάζει δίκτυα όταν τρέχει στο παρασκήνιο, πρέπει *πάντα* να έχει πρόσβαση στην ακριβή τοποθεσία ώστε η εφαρμογή να μπορεί να διαβάζει το όνομα του δικτύου Wi-Fi", + "background_options": "Επιλογές παρασκηνίου", "backup": "Αντίγραφο ασφαλείας", "backup_album_selection_page_albums_device": "Άλμπουμ στη συσκευή ({count})", "backup_album_selection_page_albums_tap": "Πάτημα για συμπερίληψη, διπλό πάτημα για εξαίρεση", @@ -533,6 +553,7 @@ "backup_album_selection_page_select_albums": "Επιλογή άλμπουμ", "backup_album_selection_page_selection_info": "Πληροφορίες επιλογής", "backup_album_selection_page_total_assets": "Συνολικά μοναδικά στοιχεία", + "backup_albums_sync": "Συγχρονισμός αντιγράφων ασφαλείας άλμπουμ", "backup_all": "Όλα", "backup_background_service_backup_failed_message": "Αποτυχία δημιουργίας αντιγράφων ασφαλείας. Επανάληψη…", "backup_background_service_connection_failed_message": "Αποτυχία σύνδεσης με το διακομιστή. Επανάληψη…", @@ -582,6 +603,7 @@ "backup_controller_page_turn_on": "Ενεργοποίηση δημιουργίας αντιγράφου ασφαλείας στο προσκήνιο", "backup_controller_page_uploading_file_info": "Μεταφόρτωση πληροφοριών αρχείου", "backup_err_only_album": "Δεν είναι δυνατή η αφαίρεση του μοναδικού άλμπουμ", + "backup_error_sync_failed": "Ο συγχρονισμός απέτυχε. Δεν είναι δυνατή η επεξεργασία του αντιγράφου ασφαλείας.", "backup_info_card_assets": "στοιχεία", "backup_manual_cancelled": "Ακυρώθηκε", "backup_manual_in_progress": "Μεταφόρτωση σε εξέλιξη. Δοκιμάστε αργότερα", @@ -649,6 +671,8 @@ "change_pin_code": "Αλλαγή κωδικού PIN", "change_your_password": "Αλλάξτε τον κωδικό σας", "changed_visibility_successfully": "Η προβολή, άλλαξε με επιτυχία", + "charging": "Φόρτιση", + "charging_requirement_mobile_backup": "Η δημιουργία αντιγράφων ασφάλειας στο παρασκήνιο απαιτεί η συσκευή να φορτίζει", "check_corrupt_asset_backup": "Έλεγχος για κατεστραμμένα αντίγραφα ασφαλείας στοιχείων", "check_corrupt_asset_backup_button": "Εκτέλεση ελέγχου", "check_corrupt_asset_backup_description": "Εκτέλεσε αυτόν τον έλεγχο μόνο μέσω Wi-Fi και αφού έχουν αποθηκευτεί όλα τα αντίγραφα ασφαλείας των στοιχείων. Η διαδικασία μπορεί να διαρκέσει μερικά λεπτά.", @@ -680,7 +704,6 @@ "comments_and_likes": "Σχόλια & αντιδράσεις (likes)", "comments_are_disabled": "Τα σχόλια είναι απενεργοποιημένα", "common_create_new_album": "Δημιουργία νέου άλμπουμ", - "common_server_error": "Ελέγξτε τη σύνδεσή σας, βεβαιωθείτε ότι ο διακομιστής είναι προσβάσιμος και ότι οι εκδόσεις της εφαρμογής/διακομιστή είναι συμβατές.", "completed": "Ολοκληρώθηκε", "confirm": "Επιβεβαίωση", "confirm_admin_password": "Επιβεβαίωση κωδικού Διαχειριστή", @@ -735,6 +758,7 @@ "create_user": "Δημιουργία χρήστη", "created": "Δημιουργήθηκε", "created_at": "Δημιουργήθηκε", + "creating_linked_albums": "Δημιουργία συνδεδεμένων άλμπουμ...", "crop": "Αποκοπή", "curated_object_page_title": "Πράγματα", "current_device": "Τρέχουσα συσκευή", @@ -861,7 +885,6 @@ "edit_tag": "Επεξεργασία ετικέτας", "edit_title": "Επεξεργασία Τίτλου", "edit_user": "Επεξεργασία χρήστη", - "edited": "Επεξεργάστηκε", "editor": "Επεξεργαστής", "editor_close_without_save_prompt": "Αυτές οι αλλαγές δεν θα αποθηκευτούν", "editor_close_without_save_title": "Κλείσιμο επεξεργαστή;", @@ -884,7 +907,9 @@ "error": "Σφάλμα", "error_change_sort_album": "Απέτυχε η αλλαγή σειράς του άλμπουμ", "error_delete_face": "Σφάλμα διαγραφής προσώπου από το στοιχείο", + "error_getting_places": "Σφάλμα κατά την ανάκτηση τοποθεσιών", "error_loading_image": "Σφάλμα κατά τη φόρτωση της εικόνας", + "error_loading_partners": "Σφάλμα κατά τη φόρτωση συνεργατών: {error}", "error_saving_image": "Σφάλμα: {error}", "error_tag_face_bounding_box": "Σφάλμα επισήμανσης προσώπου - δεν μπορούν να ληφθούν οι συντεταγμένες του πλαισίου οριοθέτησης", "error_title": "Σφάλμα - Κάτι πήγε στραβά", @@ -1015,6 +1040,7 @@ "exif_bottom_sheet_description_error": "Σφάλμα κατά την ενημέρωση της περιγραφής", "exif_bottom_sheet_details": "ΛΕΠΤΟΜΕΡΕΙΕΣ", "exif_bottom_sheet_location": "ΤΟΠΟΘΕΣΙΑ", + "exif_bottom_sheet_no_description": "Καμία περιγραφή", "exif_bottom_sheet_people": "ΑΤΟΜΑ", "exif_bottom_sheet_person_add_person": "Προσθήκη ονόματος", "exit_slideshow": "Έξοδος από την παρουσίαση", @@ -1049,6 +1075,7 @@ "favorites_page_no_favorites": "Δεν βρέθηκαν αγαπημένα στοιχεία", "feature_photo_updated": "Η φωτογραφία προβολής ενημερώθηκε", "features": "Χαρακτηριστικά", + "features_in_development": "Λειτουργίες υπό Ανάπτυξη", "features_setting_description": "Διαχειριστείτε τα χαρακτηριστικά της εφαρμογής", "file_name": "Όνομα αρχείου", "file_name_or_extension": "Όνομα αρχείου ή επέκταση", @@ -1069,12 +1096,15 @@ "gcast_enabled": "Μετάδοση περιεχομένου Google Cast", "gcast_enabled_description": "Αυτό το χαρακτηριστικό φορτώνει εξωτερικούς πόρους από τη Google για να λειτουργήσει.", "general": "Γενικά", + "geolocation_instruction_location": "Κάνε κλικ σε ένα στοιχείο με συντεταγμένες GPS για να χρησιμοποιήσεις την τοποθεσία του, ή επίλεξε απευθείας μια τοποθεσία από τον χάρτη", "get_help": "Ζητήστε βοήθεια", "get_wifiname_error": "Δεν ήταν δυνατή η λήψη του ονόματος Wi-Fi. Βεβαιωθείτε ότι έχετε δώσει τις απαραίτητες άδειες και ότι είστε συνδεδεμένοι σε δίκτυο Wi-Fi", "getting_started": "Ξεκινώντας", "go_back": "Πηγαίνετε πίσω", "go_to_folder": "Μετάβαση στο φάκελο", "go_to_search": "Πηγαίνετε στην αναζήτηση", + "gps": "GPS", + "gps_missing": "Χωρίς GPS", "grant_permission": "Επιτρέψτε την άδεια", "group_albums_by": "Ομαδοποίηση άλμπουμ κατά...", "group_country": "Ομαδοποίηση κατά χώρα", @@ -1092,7 +1122,6 @@ "header_settings_field_validator_msg": "Η τιμή δεν μπορεί να είναι κενή", "header_settings_header_name_input": "Όνομα κεφαλίδας", "header_settings_header_value_input": "Τιμή κεφαλίδας", - "headers_settings_tile_subtitle": "Καθορίστε τις κεφαλίδες διακομιστή μεσολάβησης που θα πρέπει να στέλνει η εφαρμογή με κάθε αίτημα δικτύου", "headers_settings_tile_title": "Προσαρμοσμένες κεφαλίδες διακομιστή μεσολάβησης", "hi_user": "Γειά σου {name} {email}", "hide_all_people": "Απόκρυψη όλων των ατόμων", @@ -1210,6 +1239,7 @@ "local": "Τοπικά", "local_asset_cast_failed": "Αδυναμία μετάδοσης στοιχείου που δεν έχει ανέβει στον διακομιστή", "local_assets": "Τοπικά στοιχεία", + "local_media_summary": "Περίληψη τοπικών πολυμέσων", "local_network": "Τοπικό δίκτυο", "local_network_sheet_info": "Η εφαρμογή θα συνδεθεί με τον διακομιστή μέσω αυτού του URL όταν χρησιμοποιείται το καθορισμένο δίκτυο Wi-Fi", "location_permission": "Άδεια τοποθεσίας", @@ -1221,6 +1251,7 @@ "location_picker_longitude_hint": "Εισαγάγετε εδώ το γεωγραφικό σας μήκος", "lock": "Κλείδωμα", "locked_folder": "Κλειδωμένος φάκελος", + "log_detail_title": "Λεπτομέρεια καταγραφής", "log_out": "Αποσύνδεση", "log_out_all_devices": "Αποσύνδεση από Όλες τις Συσκευές", "logged_in_as": "Συνδεδεμένος ως {user}", @@ -1251,6 +1282,7 @@ "login_password_changed_success": "Ο κωδικός πρόσβασης ενημερώθηκε με επιτυχία", "logout_all_device_confirmation": "Είστε βέβαιοι ότι θέλετε να αποσυνδεθείτε από όλες τις συσκευές;", "logout_this_device_confirmation": "Είστε βέβαιοι ότι θέλετε να αποσυνδεθείτε από αυτήν τη συσκευή;", + "logs": "Καταγραφές", "longitude": "Γεωγραφικό μήκος", "look": "Εμφάνιση", "loop_videos": "Επανάληψη βίντεο", @@ -1258,6 +1290,7 @@ "main_branch_warning": "Χρησιμοποιείτε μια έκδοση σε ανάπτυξη· συνιστούμε ανεπιφύλακτα τη χρήση μιας τελικής έκδοσης!", "main_menu": "Κύριο μενού", "make": "Κατασκευαστής", + "manage_geolocation": "Διαχείριση τοποθεσίας", "manage_shared_links": "Διαχείριση κοινόχρηστων συνδέσμων", "manage_sharing_with_partners": "Διαχειριστείτε την κοινή χρήση με συνεργάτες", "manage_the_app_settings": "Διαχειριστείτε τις ρυθμίσεις της εφαρμογής", @@ -1292,6 +1325,7 @@ "mark_as_read": "Επισήμανση ως αναγνωσμένο", "marked_all_as_read": "Όλα επισημάνθηκαν ως αναγνωσμένα", "matches": "Αντιστοιχίες", + "matching_assets": "Αντιστοιχία στοιχείων", "media_type": "Τύπος πολυμέσου", "memories": "Αναμνήσεις", "memories_all_caught_up": "Συγχρονισμένα", @@ -1312,6 +1346,8 @@ "minute": "Λεπτό", "minutes": "Λεπτά", "missing": "Όσα Λείπουν", + "mobile_app": "Εφαρμογή για κινητά", + "mobile_app_download_onboarding_note": "Μπορείτε να αποκτήσετε ξανά πρόσβαση σε αυτές τις επιλογές από τη σελίδα Βοηθήματα.", "model": "Μοντέλο", "month": "Μήνας", "monthly_title_text_date_format": "ΜΜΜΜ y", @@ -1330,18 +1366,23 @@ "my_albums": "Τα άλμπουμ μου", "name": "Όνομα", "name_or_nickname": "Όνομα ή ψευδώνυμο", + "navigate": "Πλοηγηθείτε", + "navigate_to_time": "Πλοηγηθείτε στο Χρόνο", "network_requirement_photos_upload": "Χρήση δεδομένων κινητής τηλεφωνίας για τη δημιουργία αντιγράφων ασφαλείας των φωτογραφιών", "network_requirement_videos_upload": "Χρήση δεδομένων κινητής τηλεφωνίας για τη δημιουργία αντιγράφων ασφαλείας των βίντεο", + "network_requirements": "Απαιτήσεις Δυκτίου", "network_requirements_updated": "Οι απαιτήσεις δικτύου άλλαξαν, γίνεται επαναφορά της ουράς αντιγράφων ασφαλείας", "networking_settings": "Δικτύωση", "networking_subtitle": "Διαχείριση ρυθμίσεων τελικών σημείων διακομιστή", "never": "Ποτέ", "new_album": "Νέο Άλμπουμ", "new_api_key": "Νέο API Key", + "new_date_range": "Εύρος νέας ημερομηνίας", "new_password": "Νέος κωδικός πρόσβασης", "new_person": "Νέο άτομο", "new_pin_code": "Νέος κωδικός PIN", "new_pin_code_subtitle": "Αυτή είναι η πρώτη φορά που αποκτάτε πρόσβαση στον κλειδωμένο φάκελο. Δημιουργήστε έναν κωδικό PIN για ασφαλή πρόσβαση σε αυτή τη σελίδα", + "new_timeline": "Νέο Χρονολόγιο", "new_user_created": "Ο νέος χρήστης δημιουργήθηκε", "new_version_available": "ΔΙΑΘΕΣΙΜΗ ΝΕΑ ΕΚΔΟΣΗ", "newest_first": "Τα νεότερα πρώτα", @@ -1355,20 +1396,25 @@ "no_assets_message": "ΚΑΝΤΕ ΚΛΙΚ ΓΙΑ ΝΑ ΑΝΕΒΑΣΕΤΕ ΤΗΝ ΠΡΩΤΗ ΣΑΣ ΦΩΤΟΓΡΑΦΙΑ", "no_assets_to_show": "Δεν υπάρχουν στοιχεία προς εμφάνιση", "no_cast_devices_found": "Δε βρέθηκαν συσκευές μετάδοσης", + "no_checksum_local": "Δεν υπάρχει διαθέσιμο checksum για έλεγχο ακεραιότητας – δεν μπορούν να ανακτηθούν τα τοπικά στοιχεία", + "no_checksum_remote": "Δεν υπάρχει διαθέσιμο checksum για έλεγχο ακεραιότητας – δεν μπορούν να ανακτηθούν τα απομακρυσμένα στοιχεία", "no_duplicates_found": "Δεν βρέθηκαν διπλότυπα.", "no_exif_info_available": "Καμία πληροφορία exif διαθέσιμη", "no_explore_results_message": "Ανεβάστε περισσότερες φωτογραφίες για να περιηγηθείτε στη συλλογή σας.", "no_favorites_message": "Προσθέστε αγαπημένα για να βρείτε γρήγορα τις καλύτερες φωτογραφίες και τα βίντεό σας", "no_libraries_message": "Δημιουργήστε μια εξωτερική βιβλιοθήκη για να προβάλετε τις φωτογραφίες και τα βίντεό σας", + "no_local_assets_found": "Δεν βρέθηκαν τοπικά στοιχεία με αυτό το checksum", "no_locked_photos_message": "Οι φωτογραφίες και τα βίντεο στον κλειδωμένο φάκελο, είναι κρυμμένες και δεν θα εμφανίζονται κατά την περιήγηση ή την αναζήτηση στη βιβλιοθήκη σας.", "no_name": "Χωρίς Όνομα", "no_notifications": "Καμία ειδοποίηση", "no_people_found": "Δεν βρέθηκαν άτομα που να ταιριάζουν", "no_places": "Καμία τοποθεσία", + "no_remote_assets_found": "Δεν βρέθηκαν απομακρυσμένα στοιχεία με αυτό το checksum", "no_results": "Κανένα αποτέλεσμα", "no_results_description": "Δοκιμάστε ένα συνώνυμο ή πιο γενική λέξη-κλειδί", "no_shared_albums_message": "Δημιουργήστε ένα άλμπουμ για να μοιράζεστε φωτογραφίες και βίντεο με άτομα στο δίκτυό σας", "no_uploads_in_progress": "Καμία μεταφόρτωση σε εξέλιξη", + "not_available": "Μ/Δ (Μη Διαθέσιμο)", "not_in_any_album": "Σε κανένα άλμπουμ", "not_selected": "Δεν επιλέχθηκε", "note_apply_storage_label_to_previously_uploaded assets": "Σημείωση: Για να εφαρμόσετε την Ετικέτα Αποθήκευσης σε στοιχεία που έχουν μεταφορτωθεί προηγουμένως, εκτελέστε το", @@ -1382,6 +1428,8 @@ "notifications": "Ειδοποιήσεις", "notifications_setting_description": "Διαχείριση ειδοποιήσεων", "oauth": "OAuth", + "obtainium_configurator": "Ρυθμιστής Obtainium", + "obtainium_configurator_instructions": "Δημιουργήστε ένα κλειδί API και επιλέξτε μια παραλλαγή για να δημιουργήσετε τον σύνδεσμο σας ρύθμισης Obtainium.", "official_immich_resources": "Επίσημοι Πόροι του Immich", "offline": "Εκτός σύνδεσης", "offset": "Μετατόπιση", @@ -1403,6 +1451,8 @@ "open_the_search_filters": "Ανοίξτε τα φίλτρα αναζήτησης", "options": "Επιλογές", "or": "ή", + "organize_into_albums": "Οργάνωση σε άλμπουμ", + "organize_into_albums_description": "Τοποθετείστε τις υπάρχουσες φωτογραφίες σε άλμπουμ χρησιμοποιώντας τις τρέχουσες ρυθμίσεις συγχρονισμού", "organize_your_library": "Οργανώστε τη βιβλιοθήκη σας", "original": "πρωτότυπο", "other": "Άλλες", @@ -1488,6 +1538,7 @@ "port": "Θύρα", "preferences_settings_subtitle": "Διαχειριστείτε τις προτιμήσεις της εφαρμογής", "preferences_settings_title": "Προτιμήσεις", + "preparing": "Προετοιμασία", "preset": "Προκαθορισμένη ρύθμιση", "preview": "Προεπισκόπηση", "previous": "Προηγούμενο", @@ -1500,12 +1551,9 @@ "privacy": "Ιδιωτικότητα", "profile": "Προφίλ", "profile_drawer_app_logs": "Καταγραφές", - "profile_drawer_client_out_of_date_major": "Παρακαλώ ενημερώστε την εφαρμογή στην πιο πρόσφατη κύρια έκδοση.", - "profile_drawer_client_out_of_date_minor": "Παρακαλώ ενημερώστε την εφαρμογή στην πιο πρόσφατη δευτερεύουσα έκδοση.", "profile_drawer_client_server_up_to_date": "Ο πελάτης και ο διακομιστής είναι ενημερωμένοι", "profile_drawer_github": "GitHub", - "profile_drawer_server_out_of_date_major": "Παρακαλώ ενημερώστε τον διακομιστή στην πιο πρόσφατη κύρια έκδοση.", - "profile_drawer_server_out_of_date_minor": "Παρακαλώ ενημερώστε τον διακομιστή στην πιο πρόσφατη δευτερεύουσα έκδοση.", + "profile_drawer_readonly_mode": "Η λειτουργία μόνο-για-ανάγνωση ενεργοποιήθηκε. Κρατήστε πατημένο το εικονίδιο του χρήστη για απενεργοποίηση.", "profile_image_of_user": "Εικόνα προφίλ του χρήστη {user}", "profile_picture_set": "Ορισμός εικόνας προφίλ.", "public_album": "Δημόσιο άλμπουμ", @@ -1542,6 +1590,7 @@ "purchase_server_description_2": "Κατάσταση υποστηρικτή", "purchase_server_title": "Διακομιστής", "purchase_settings_server_activated": "Η διαχείριση του κλειδιού προϊόντος του διακομιστή γίνεται από τον διαχειριστή", + "query_asset_id": "Αναζήτηση ID Στοιχείου", "queue_status": "Τοποθέτηση στη ουρά {count} από {total}", "rating": "Αξιολόγηση με αστέρια", "rating_clear": "Εκκαθάριση αξιολόγησης", @@ -1549,6 +1598,9 @@ "rating_description": "Εμφάνιση της αξιολόγησης EXIF στον πίνακα πληροφοριών", "reaction_options": "Επιλογές αντίδρασης", "read_changelog": "Διαβάστε το Αρχείο Καταγραφής Αλλαγών", + "readonly_mode_disabled": "Η λειτουργία μόνο-για-ανάγνωση απενεργοποιήθηκε", + "readonly_mode_enabled": "Η λειτουργία μόνο-για-ανάγνωση ενεργοποιήθηκε", + "ready_for_upload": "Έτοιμο για μεταφόρτωση", "reassign": "Ανάθεση", "reassigned_assets_to_existing_person": "Η ανάθεση {count, plural, one {# αρχείου} other {# αρχείων}} στον/στην {name, select, null {έναν/μία υπάρχοντα/ουσα χρήστη} other {{name}}}", "reassigned_assets_to_new_person": "Η ανάθεση {count, plural, one {# αρχείου} other {# αρχείων}} σε νέο άτομο", @@ -1573,6 +1625,7 @@ "regenerating_thumbnails": "Οι μικρογραφίες αναγεννώνται", "remote": "Απομακρυσμένος", "remote_assets": "Απομακρυσμένα στοιχεία", + "remote_media_summary": "Περίληψη απομακρυσμένων πολυμέσων", "remove": "Αφαίρεση", "remove_assets_album_confirmation": "Είστε σίγουροι ότι θέλετε να αφαιρέσετε {count, plural, one {# στοιχείο} other {# στοιχεία}} από το άλμπουμ;", "remove_assets_shared_link_confirmation": "Είστε σίγουροι ότι θέλετε να αφαιρέσετε {count, plural, one {# στοιχείο} other {# στοιχεία}} από αυτόν τον κοινόχρηστο σύνδεσμο;", @@ -1625,6 +1678,7 @@ "restore_user": "Επαναφορά χρήστη", "restored_asset": "Ανακτήθηκε το αρχείο", "resume": "Συνέχιση", + "resume_paused_jobs": "Συνέχιση {count, plural, one {# σε παύση εργασία} other {# σε παύση εργασίες}}", "retry_upload": "Επανάληψη ανεβάσματος", "review_duplicates": "Προβολή διπλότυπων", "review_large_files": "Επισκόπηση μεγάλων αρχείων", @@ -1718,6 +1772,7 @@ "select_user_for_sharing_page_err_album": "Αποτυχία δημιουργίας άλπουμ", "selected": "Επιλεγμένοι", "selected_count": "{count, plural, other {# επιλεγμένοι}}", + "selected_gps_coordinates": "Επιλεγμένες συντεταγμένες GPS", "send_message": "Αποστολή μηνύματος", "send_welcome_email": "Αποστολή email καλωσορίσματος", "server_endpoint": "Τελικό σημείο Διακομιστή", @@ -1755,6 +1810,8 @@ "setting_notifications_subtitle": "Προσαρμόστε τις προτιμήσεις ειδοποίησης", "setting_notifications_total_progress_subtitle": "Συνολική πρόοδος μεταφόρτωσης (ολοκληρώθηκε/σύνολο στοιχείων)", "setting_notifications_total_progress_title": "Εμφάνιση συνολικής προόδου δημιουργίας αντιγράφων ασφαλείας παρασκηνίου", + "setting_video_viewer_auto_play_subtitle": "Αυτόματη αναπαραγωγή βίντεο κατά το άνοιγμά τους", + "setting_video_viewer_auto_play_title": "Αυτόματη αναπαραγωγή βίντεο", "setting_video_viewer_looping_title": "Συνεχής Επανάληψη", "setting_video_viewer_original_video_subtitle": "Όταν μεταδίδετε ένα βίντεο από τον διακομιστή, αναπαράγετε το αυθεντικό ακόμη και όταν υπάρχει διαθέσιμο με διαφορετική κωδικοποίηση. Μπορεί να προκαλέσει καθυστέρηση φόρτωσης. Τα βίντεο που είναι διαθέσιμα τοπικά, αναπαράγονται στην αυθεντική ποιότητα, ανεξαρτήτως αυτής της ρύθμισης.", "setting_video_viewer_original_video_title": "Αναγκαστική αναπαραγωγή αυθεντικού βίντεο", @@ -1846,6 +1903,7 @@ "show_slideshow_transition": "Εμφάνιση μετάβασης παρουσίασης", "show_supporter_badge": "Σήμα υποστηρικτή", "show_supporter_badge_description": "Εμφάνιση σήματος υποστηρικτή", + "show_text_search_menu": "Εμφάνιση μενού αναζήτησης κειμένου", "shuffle": "Ανάμειξη", "sidebar": "Πλαϊνή μπάρα", "sidebar_display_description": "Εμφάνιση συνδέσμου για προβολή στην πλαϊνή μπάρα", @@ -1876,6 +1934,7 @@ "stacktrace": "Καταγραφή στοίβας", "start": "Έναρξη", "start_date": "Από", + "start_date_before_end_date": "Η ημερομηνία έναρξης πρέπει να είναι πριν από την ημερομηνία λήξης", "state": "Νομός", "status": "Κατάσταση", "stop_casting": "Διακοπή μετάδοσης", @@ -1900,6 +1959,8 @@ "sync_albums_manual_subtitle": "Συγχρονίστε όλα τα μεταφορτωμένα βίντεο και φωτογραφίες με τα επιλεγμένα εφεδρικά άλμπουμ", "sync_local": "Τοπικός Συγχρονισμός", "sync_remote": "Απομακρυσμένος Συγχρονισμός", + "sync_status": "Κατάσταση συγχρονισμού", + "sync_status_subtitle": "Προβολή και διαχείριση του συστήματος συγχρονισμού", "sync_upload_album_setting_subtitle": "Δημιουργήστε και ανεβάστε τις φωτογραφίες και τα βίντεό σας στα επιλεγμένα άλμπουμ στο Immich", "tag": "Ετικέτα", "tag_assets": "Ετικετοποίηση στοιχείων", @@ -1937,7 +1998,9 @@ "to_change_password": "Αλλαγή κωδικού πρόσβασης", "to_favorite": "Αγαπημένο", "to_login": "Είσοδος", + "to_multi_select": "για πολλαπλή επιλογή", "to_parent": "Μεταβείτε στο γονικό φάκελο", + "to_select": "για επιλογή", "to_trash": "Κάδος απορριμμάτων", "toggle_settings": "Εναλλαγή ρυθμίσεων", "total": "Σύνολο", @@ -1957,6 +2020,7 @@ "trash_page_select_assets_btn": "Επιλέξτε στοιχεία", "trash_page_title": "Κάδος Απορριμμάτων ({count})", "trashed_items_will_be_permanently_deleted_after": "Τα στοιχεία που βρίσκονται στον κάδο απορριμμάτων θα διαγραφούν οριστικά μετά από {days, plural, one {# ημέρα} other {# ημέρες}}.", + "troubleshoot": "Επίλυση προβλημάτων", "type": "Τύπος", "unable_to_change_pin_code": "Αδυναμία αλλαγής κωδικού PIN", "unable_to_setup_pin_code": "Αδυναμία ρύθμισης κωδικού PIN", @@ -1987,6 +2051,7 @@ "unstacked_assets_count": "Αποστοιβάξατε {count, plural, one {# στοιχείο} other {# στοιχεία}}", "untagged": "Χωρίς ετικέτα", "up_next": "Ακολουθεί", + "update_location_action_prompt": "Ενημέρωση τοποθεσίας για {count} επιλεγμένα στοιχεία με:", "updated_at": "Ενημερωμένο", "updated_password": "Ο κωδικός πρόσβασης ενημερώθηκε", "upload": "Μεταφόρτωση", @@ -2053,6 +2118,7 @@ "view_next_asset": "Προβολή επόμενου στοιχείου", "view_previous_asset": "Προβολή προηγούμενου στοιχείου", "view_qr_code": "Προβολή κωδικού QR", + "view_similar_photos": "Προβολή παρόμοιων φωτογραφιών", "view_stack": "Προβολή της στοίβας", "view_user": "Προβολή Χρήστη", "viewer_remove_from_stack": "Κατάργηση από τη Στοίβα", @@ -2071,5 +2137,6 @@ "yes": "Ναι", "you_dont_have_any_shared_links": "Δεν έχετε κοινόχρηστους συνδέσμους", "your_wifi_name": "Το όνομα του Wi-Fi σας", - "zoom_image": "Ζουμ Εικόνας" + "zoom_image": "Ζουμ Εικόνας", + "zoom_to_bounds": "Εστίαση στα όρια" } diff --git a/i18n/en.json b/i18n/en.json index 23e1071a2d..6da205d85a 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -28,10 +28,13 @@ "add_to_album": "Add to album", "add_to_album_bottom_sheet_added": "Added to {album}", "add_to_album_bottom_sheet_already_exists": "Already in {album}", + "add_to_album_bottom_sheet_some_local_assets": "Some local assets could not be added to album", "add_to_album_toggle": "Toggle selection for {album}", "add_to_albums": "Add to albums", "add_to_albums_count": "Add to albums ({count})", + "add_to_bottom_bar": "Add to", "add_to_shared_album": "Add to shared album", + "add_upload_to_stack": "Add upload to stack", "add_url": "Add URL", "added_to_archive": "Added to archive", "added_to_favorites": "Added to favorites", @@ -118,7 +121,7 @@ "library_settings_description": "Manage external library settings", "library_tasks_description": "Scan external libraries for new and/or changed assets", "library_watching_enable_description": "Watch external libraries for file changes", - "library_watching_settings": "Library watching (EXPERIMENTAL)", + "library_watching_settings": "Library watching [EXPERIMENTAL]", "library_watching_settings_description": "Automatically watch for changed files", "logging_enable_description": "Enable logging", "logging_level_description": "When enabled, what log level to use.", @@ -152,6 +155,18 @@ "machine_learning_min_detection_score_description": "Minimum confidence score for a face to be detected from 0-1. Lower values will detect more faces but may result in false positives.", "machine_learning_min_recognized_faces": "Minimum recognized faces", "machine_learning_min_recognized_faces_description": "The minimum number of recognized faces for a person to be created. Increasing this makes Facial Recognition more precise at the cost of increasing the chance that a face is not assigned to a person.", + "machine_learning_ocr": "OCR", + "machine_learning_ocr_description": "Use machine learning to recognize text in images", + "machine_learning_ocr_enabled": "Enable OCR", + "machine_learning_ocr_enabled_description": "If disabled, images will not undergo text recognition.", + "machine_learning_ocr_max_resolution": "Maximum resolution", + "machine_learning_ocr_max_resolution_description": "Previews above this resolution will be resized while preserving aspect ratio. Higher values are more accurate, but take longer to process and use more memory.", + "machine_learning_ocr_min_detection_score": "Minimum detection score", + "machine_learning_ocr_min_detection_score_description": "Minimum confidence score for text to be detected from 0-1. Lower values will detect more text but may result in false positives.", + "machine_learning_ocr_min_recognition_score": "Minimum recognition score", + "machine_learning_ocr_min_score_recognition_description": "Minimum confidence score for detected text to be recognized from 0-1. Lower values will recognize more text but may result in false positives.", + "machine_learning_ocr_model": "OCR model", + "machine_learning_ocr_model_description": "Server models are more accurate than mobile models, but take longer to process and use more memory.", "machine_learning_settings": "Machine Learning Settings", "machine_learning_settings_description": "Manage machine learning features and settings", "machine_learning_smart_search": "Smart Search", @@ -209,6 +224,8 @@ "notification_email_ignore_certificate_errors_description": "Ignore TLS certificate validation errors (not recommended)", "notification_email_password_description": "Password to use when authenticating with the email server", "notification_email_port_description": "Port of the email server (e.g 25, 465, or 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "Use SMTPS (SMTP over TLS)", "notification_email_sent_test_email_button": "Send test email and save", "notification_email_setting_description": "Settings for sending email notifications", "notification_email_test_email": "Send test email", @@ -241,6 +258,7 @@ "oauth_storage_quota_default_description": "Quota in GiB to be used when no claim is provided.", "oauth_timeout": "Request Timeout", "oauth_timeout_description": "Timeout for requests in milliseconds", + "ocr_job_description": "Use machine learning to recognize text in images", "password_enable_description": "Login with email and password", "password_settings": "Password Login", "password_settings_description": "Manage password login settings", @@ -331,7 +349,7 @@ "transcoding_max_b_frames": "Maximum B-frames", "transcoding_max_b_frames_description": "Higher values improve compression efficiency, but slow down encoding. May not be compatible with hardware acceleration on older devices. 0 disables B-frames, while -1 sets this value automatically.", "transcoding_max_bitrate": "Maximum bitrate", - "transcoding_max_bitrate_description": "Setting a max bitrate can make file sizes more predictable at a minor cost to quality. At 720p, typical values are 2600 kbit/s for VP9 or HEVC, or 4500 kbit/s for H.264. Disabled if set to 0.", + "transcoding_max_bitrate_description": "Setting a max bitrate can make file sizes more predictable at a minor cost to quality. At 720p, typical values are 2600 kbit/s for VP9 or HEVC, or 4500 kbit/s for H.264. Disabled if set to 0. When no unit is specified, k (for kbit/s) is assumed; therefore 5000, 5000k, and 5M (for Mbit/s) are equivalent.", "transcoding_max_keyframe_interval": "Maximum keyframe interval", "transcoding_max_keyframe_interval_description": "Sets the maximum frame distance between keyframes. Lower values worsen compression efficiency, but improve seek times and may improve quality in scenes with fast movement. 0 sets this value automatically.", "transcoding_optimal_description": "Videos higher than target resolution or not in an accepted format", @@ -349,7 +367,7 @@ "transcoding_target_resolution": "Target resolution", "transcoding_target_resolution_description": "Higher resolutions can preserve more detail but take longer to encode, have larger file sizes, and can reduce app responsiveness.", "transcoding_temporal_aq": "Temporal AQ", - "transcoding_temporal_aq_description": "Applies only to NVENC. Increases quality of high-detail, low-motion scenes. May not be compatible with older devices.", + "transcoding_temporal_aq_description": "Applies only to NVENC. Temporal Adaptive Quantization increases quality of high-detail, low-motion scenes. May not be compatible with older devices.", "transcoding_threads": "Threads", "transcoding_threads_description": "Higher values lead to faster encoding, but leave less room for the server to process other tasks while active. This value should not be more than the number of CPU cores. Maximizes utilization if set to 0.", "transcoding_tone_mapping": "Tone-mapping", @@ -400,11 +418,11 @@ "advanced_settings_prefer_remote_subtitle": "Some devices are painfully slow to load thumbnails from local assets. Activate this setting to load remote images instead.", "advanced_settings_prefer_remote_title": "Prefer remote images", "advanced_settings_proxy_headers_subtitle": "Define proxy headers Immich should send with each network request", - "advanced_settings_proxy_headers_title": "Proxy Headers", + "advanced_settings_proxy_headers_title": "Custom proxy headers [EXPERIMENTAL]", "advanced_settings_readonly_mode_subtitle": "Enables the read-only mode where the photos can be only viewed, things like selecting multiple images, sharing, casting, delete are all disabled. Enable/Disable read-only via user avatar from the main screen", - "advanced_settings_readonly_mode_title": "Read-only Mode", + "advanced_settings_readonly_mode_title": "Read-only mode", "advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.", - "advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates", + "advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates [EXPERIMENTAL]", "advanced_settings_sync_remote_deletions_subtitle": "Automatically delete or restore an asset on this device when that action is taken on the web", "advanced_settings_sync_remote_deletions_title": "Sync remote deletions [EXPERIMENTAL]", "advanced_settings_tile_subtitle": "Advanced user's settings", @@ -413,6 +431,7 @@ "age_months": "Age {months, plural, one {# month} other {# months}}", "age_year_months": "Age 1 year, {months, plural, one {# month} other {# months}}", "age_years": "{years, plural, other {Age #}}", + "album": "Album", "album_added": "Album added", "album_added_notification_setting_description": "Receive an email notification when you are added to a shared album", "album_cover_updated": "Album cover updated", @@ -458,16 +477,21 @@ "allow_edits": "Allow edits", "allow_public_user_to_download": "Allow public user to download", "allow_public_user_to_upload": "Allow public user to upload", + "allowed": "Allowed", "alt_text_qr_code": "QR code image", "anti_clockwise": "Anti-clockwise", "api_key": "API Key", "api_key_description": "This value will only be shown once. Please be sure to copy it before closing the window.", "api_key_empty": "Your API Key name shouldn't be empty", "api_keys": "API Keys", + "app_architecture_variant": "Variant (Architecture)", "app_bar_signout_dialog_content": "Are you sure you want to sign out?", "app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_title": "Sign out", + "app_download_links": "App Download Links", "app_settings": "App Settings", + "app_stores": "App Stores", + "app_update_available": "App update is available", "appears_in": "Appears in", "apply_count": "Apply ({count, number})", "archive": "Archive", @@ -551,6 +575,7 @@ "backup_albums_sync": "Backup albums synchronization", "backup_all": "All", "backup_background_service_backup_failed_message": "Failed to backup assets. Retrying…", + "backup_background_service_complete_notification": "Asset backup complete", "backup_background_service_connection_failed_message": "Failed to connect to the server. Retrying…", "backup_background_service_current_upload_notification": "Uploading {filename}", "backup_background_service_default_notification": "Checking for new assets…", @@ -598,6 +623,7 @@ "backup_controller_page_turn_on": "Turn on foreground backup", "backup_controller_page_uploading_file_info": "Uploading file info", "backup_err_only_album": "Cannot remove the only album", + "backup_error_sync_failed": "Sync failed. Cannot process backup.", "backup_info_card_assets": "assets", "backup_manual_cancelled": "Cancelled", "backup_manual_in_progress": "Upload already in progress. Try after sometime", @@ -659,6 +685,8 @@ "change_password_description": "This is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", "change_password_form_confirm_password": "Confirm Password", "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", + "change_password_form_log_out": "Log out all other devices", + "change_password_form_log_out_description": "It is recommended to log out of all other devices", "change_password_form_new_password": "New Password", "change_password_form_password_mismatch": "Passwords do not match", "change_password_form_reenter_new_password": "Re-enter New Password", @@ -685,8 +713,8 @@ "client_cert_import_success_msg": "Client certificate is imported", "client_cert_invalid_msg": "Invalid certificate file or wrong password", "client_cert_remove_msg": "Client certificate is removed", - "client_cert_subtitle": "Supports PKCS12 (.p12, .pfx) format only. Certificate Import/Remove is available only before login", - "client_cert_title": "SSL Client Certificate", + "client_cert_subtitle": "Supports PKCS12 (.p12, .pfx) format only. Certificate import/removal is available only before login", + "client_cert_title": "SSL client certificate [EXPERIMENTAL]", "clockwise": "Сlockwise", "close": "Close", "collapse": "Collapse", @@ -698,7 +726,6 @@ "comments_and_likes": "Comments & likes", "comments_are_disabled": "Comments are disabled", "common_create_new_album": "Create new album", - "common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.", "completed": "Completed", "confirm": "Confirm", "confirm_admin_password": "Confirm Admin Password", @@ -737,6 +764,7 @@ "create": "Create", "create_album": "Create album", "create_album_page_untitled": "Untitled", + "create_api_key": "Create API key", "create_library": "Create Library", "create_link": "Create link", "create_link_to_share": "Create link to share", @@ -766,6 +794,7 @@ "daily_title_text_date_year": "E, MMM dd, yyyy", "dark": "Dark", "dark_theme": "Toggle dark theme", + "date": "Date", "date_after": "Date after", "date_and_time": "Date and Time", "date_before": "Date before", @@ -880,7 +909,6 @@ "edit_tag": "Edit tag", "edit_title": "Edit Title", "edit_user": "Edit user", - "edited": "Edited", "editor": "Editor", "editor_close_without_save_prompt": "The changes will not be saved", "editor_close_without_save_title": "Close editor?", @@ -1036,6 +1064,7 @@ "exif_bottom_sheet_description_error": "Error updating description", "exif_bottom_sheet_details": "DETAILS", "exif_bottom_sheet_location": "LOCATION", + "exif_bottom_sheet_no_description": "No description", "exif_bottom_sheet_people": "PEOPLE", "exif_bottom_sheet_person_add_person": "Add name", "exit_slideshow": "Exit Slideshow", @@ -1074,6 +1103,7 @@ "features_setting_description": "Manage the app features", "file_name": "File name", "file_name_or_extension": "File name or extension", + "file_size": "File size", "filename": "Filename", "filetype": "Filetype", "filter": "Filter", @@ -1113,11 +1143,10 @@ "hash_asset": "Hash asset", "hashed_assets": "Hashed assets", "hashing": "Hashing", - "header_settings_add_header_tip": "Add Header", + "header_settings_add_header_tip": "Add header", "header_settings_field_validator_msg": "Value cannot be empty", "header_settings_header_name_input": "Header name", "header_settings_header_value_input": "Header value", - "headers_settings_tile_subtitle": "Define proxy headers the app should send with each network request", "headers_settings_tile_title": "Custom proxy headers", "hi_user": "Hi {name} ({email})", "hide_all_people": "Hide all people", @@ -1170,6 +1199,8 @@ "import_path": "Import path", "in_albums": "In {count, plural, one {# album} other {# albums}}", "in_archive": "In archive", + "in_year": "In {year}", + "in_year_selector": "In", "include_archived": "Include archived", "include_shared_albums": "Include shared albums", "include_shared_partner_assets": "Include shared partner assets", @@ -1206,6 +1237,7 @@ "language_setting_description": "Select your preferred language", "large_files": "Large Files", "last": "Last", + "last_months": "{count, plural, one {Last month} other {Last # months}}", "last_seen": "Last seen", "latest_version": "Latest Version", "latitude": "Latitude", @@ -1238,6 +1270,7 @@ "local_media_summary": "Local Media Summary", "local_network": "Local network", "local_network_sheet_info": "The app will connect to the server through this URL when using the specified Wi-Fi network", + "location": "Location", "location_permission": "Location permission", "location_permission_content": "In order to use the auto-switching feature, Immich needs precise location permission so it can read the current Wi-Fi network's name", "location_picker_choose_on_map": "Choose on map", @@ -1287,6 +1320,10 @@ "main_menu": "Main menu", "make": "Make", "manage_geolocation": "Manage location", + "manage_media_access_rationale": "This permission is required for proper handling of moving assets to the trash and restoring them from it.", + "manage_media_access_settings": "Open settings", + "manage_media_access_subtitle": "Allow the Immich app to manage and move media files.", + "manage_media_access_title": "Media Management Access", "manage_shared_links": "Manage shared links", "manage_sharing_with_partners": "Manage sharing with partners", "manage_the_app_settings": "Manage the app settings", @@ -1342,12 +1379,15 @@ "minute": "Minute", "minutes": "Minutes", "missing": "Missing", + "mobile_app": "Mobile App", + "mobile_app_download_onboarding_note": "Download the companion mobile app using the following options", "model": "Model", "month": "Month", "monthly_title_text_date_format": "MMMM y", "more": "More", "move": "Move", "move_off_locked_folder": "Move out of locked folder", + "move_to": "Move to", "move_to_lock_folder_action_prompt": "{count} added to the locked folder", "move_to_locked_folder": "Move to locked folder", "move_to_locked_folder_confirmation": "These photos and video will be removed from all albums, and only viewable from the locked folder", @@ -1360,6 +1400,8 @@ "my_albums": "My albums", "name": "Name", "name_or_nickname": "Name or nickname", + "navigate": "Navigate", + "navigate_to_time": "Navigate to Time", "network_requirement_photos_upload": "Use cellular data to backup photos", "network_requirement_videos_upload": "Use cellular data to backup videos", "network_requirements": "Network Requirements", @@ -1369,11 +1411,13 @@ "never": "Never", "new_album": "New Album", "new_api_key": "New API Key", + "new_date_range": "New date range", "new_password": "New password", "new_person": "New person", "new_pin_code": "New PIN code", "new_pin_code_subtitle": "This is your first time accessing the locked folder. Create a PIN code to securely access this page", "new_timeline": "New Timeline", + "new_update": "New update", "new_user_created": "New user created", "new_version_available": "NEW VERSION AVAILABLE", "newest_first": "Newest first", @@ -1389,6 +1433,7 @@ "no_cast_devices_found": "No cast devices found", "no_checksum_local": "No checksum available - cannot fetch local assets", "no_checksum_remote": "No checksum available - cannot fetch remote asset", + "no_devices": "No authorized devices", "no_duplicates_found": "No duplicates were found.", "no_exif_info_available": "No exif info available", "no_explore_results_message": "Upload more photos to explore your collection.", @@ -1405,6 +1450,7 @@ "no_results_description": "Try a synonym or more general keyword", "no_shared_albums_message": "Create an album to share photos and videos with people in your network", "no_uploads_in_progress": "No uploads in progress", + "not_allowed": "Not allowed", "not_available": "N/A", "not_in_any_album": "Not in any album", "not_selected": "Not selected", @@ -1419,6 +1465,9 @@ "notifications": "Notifications", "notifications_setting_description": "Manage notifications", "oauth": "OAuth", + "obtainium_configurator": "Obtainium Configurator", + "obtainium_configurator_instructions": "Use Obtainium to install and update the Android app directly from Immich GitHub's release. Create an API key and select a variant to create your Obtainium configuration link", + "ocr": "OCR", "official_immich_resources": "Official Immich Resources", "offline": "Offline", "offset": "Offset", @@ -1512,6 +1561,8 @@ "photos_count": "{count, plural, one {{count, number} Photo} other {{count, number} Photos}}", "photos_from_previous_years": "Photos from previous years", "pick_a_location": "Pick a location", + "pick_custom_range": "Custom range", + "pick_date_range": "Select a date range", "pin_code_changed_successfully": "Successfully changed PIN code", "pin_code_reset_successfully": "Successfully reset PIN code", "pin_code_setup_successfully": "Successfully setup a PIN code", @@ -1523,6 +1574,9 @@ "play_memories": "Play memories", "play_motion_photo": "Play Motion Photo", "play_or_pause_video": "Play or pause video", + "play_original_video": "Play original video", + "play_original_video_setting_description": "Prefer playback of original videos rather than transcoded videos. If original asset is not compatible it may not playback correctly.", + "play_transcoded_video": "Play transcoded video", "please_auth_to_access": "Please authenticate to access", "port": "Port", "preferences_settings_subtitle": "Manage the app's preferences", @@ -1540,13 +1594,9 @@ "privacy": "Privacy", "profile": "Profile", "profile_drawer_app_logs": "Logs", - "profile_drawer_client_out_of_date_major": "Mobile App is out of date. Please update to the latest major version.", - "profile_drawer_client_out_of_date_minor": "Mobile App is out of date. Please update to the latest minor version.", "profile_drawer_client_server_up_to_date": "Client and Server are up-to-date", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Read-only mode enabled. Long-press the user avatar icon to exit.", - "profile_drawer_server_out_of_date_major": "Server is out of date. Please update to the latest major version.", - "profile_drawer_server_out_of_date_minor": "Server is out of date. Please update to the latest minor version.", "profile_image_of_user": "Profile image of {user}", "profile_picture_set": "Profile picture set.", "public_album": "Public album", @@ -1663,6 +1713,7 @@ "reset_sqlite_confirmation": "Are you sure you want to reset the SQLite database? You will need to log out and log in again to resync the data", "reset_sqlite_success": "Successfully reset the SQLite database", "reset_to_default": "Reset to default", + "resolution": "Resolution", "resolve_duplicates": "Resolve duplicates", "resolved_all_duplicates": "Resolved all duplicates", "restore": "Restore", @@ -1681,6 +1732,7 @@ "running": "Running", "save": "Save", "save_to_gallery": "Save to gallery", + "saved": "Saved", "saved_api_key": "Saved API Key", "saved_profile": "Saved profile", "saved_settings": "Saved settings", @@ -1697,6 +1749,9 @@ "search_by_description_example": "Hiking day in Sapa", "search_by_filename": "Search by file name or extension", "search_by_filename_example": "i.e. IMG_1234.JPG or PNG", + "search_by_ocr": "Search by OCR", + "search_by_ocr_example": "Latte", + "search_camera_lens_model": "Search lens model...", "search_camera_make": "Search camera make...", "search_camera_model": "Search camera model...", "search_city": "Search city...", @@ -1713,6 +1768,7 @@ "search_filter_location_title": "Select location", "search_filter_media_type": "Media Type", "search_filter_media_type_title": "Select media type", + "search_filter_ocr": "Search by OCR", "search_filter_people_title": "Select people", "search_for": "Search for", "search_for_existing_person": "Search for existing person", @@ -1775,6 +1831,7 @@ "server_online": "Server Online", "server_privacy": "Server Privacy", "server_stats": "Server Stats", + "server_update_available": "Server update is available", "server_version": "Server Version", "set": "Set", "set_as_album_cover": "Set as album cover", @@ -1803,6 +1860,8 @@ "setting_notifications_subtitle": "Adjust your notification preferences", "setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)", "setting_notifications_total_progress_title": "Show background backup total progress", + "setting_video_viewer_auto_play_subtitle": "Automatically start playing videos when they are opened", + "setting_video_viewer_auto_play_title": "Auto play videos", "setting_video_viewer_looping_title": "Looping", "setting_video_viewer_original_video_subtitle": "When streaming a video from the server, play the original even when a transcode is available. May lead to buffering. Videos available locally are played in original quality regardless of this setting.", "setting_video_viewer_original_video_title": "Force original video", @@ -1982,7 +2041,9 @@ "theme_setting_three_stage_loading_title": "Enable three-stage loading", "they_will_be_merged_together": "They will be merged together", "third_party_resources": "Third-Party Resources", + "time": "Time", "time_based_memories": "Time-based memories", + "time_based_memories_duration": "Number of seconds to display each image.", "timeline": "Timeline", "timezone": "Timezone", "to_archive": "Archive", @@ -2014,6 +2075,7 @@ "troubleshoot": "Troubleshoot", "type": "Type", "unable_to_change_pin_code": "Unable to change PIN code", + "unable_to_check_version": "Unable to check app or server version", "unable_to_setup_pin_code": "Unable to setup PIN code", "unarchive": "Unarchive", "unarchive_action_prompt": "{count} removed from Archive", @@ -2122,6 +2184,7 @@ "welcome": "Welcome", "welcome_to_immich": "Welcome to Immich", "wifi_name": "Wi-Fi Name", + "workflow": "Workflow", "wrong_pin_code": "Wrong PIN code", "year": "Year", "years_ago": "{years, plural, one {# year} other {# years}} ago", diff --git a/i18n/es.json b/i18n/es.json index e4873051f6..c2260b2012 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -28,10 +28,12 @@ "add_to_album": "Incluir en álbum", "add_to_album_bottom_sheet_added": "Agregado a {album}", "add_to_album_bottom_sheet_already_exists": "Ya se encuentra en {album}", + "add_to_album_bottom_sheet_some_local_assets": "Algunos recursos locales no se pudieron añadir al álbum", "add_to_album_toggle": "Alternar selección para el {album}", "add_to_albums": "Incluir en álbumes", "add_to_albums_count": "Incluir en {count} álbumes", "add_to_shared_album": "Incluir en álbum compartido", + "add_upload_to_stack": "Añadir archivo y apilar", "add_url": "Agregar URL", "added_to_archive": "Agregado al Archivado", "added_to_favorites": "Agregado a favoritos", @@ -118,18 +120,18 @@ "library_settings_description": "Administrar configuración biblioteca externa", "library_tasks_description": "Buscar elementos nuevos o modificados en bibliotecas externas", "library_watching_enable_description": "Vigilar las bibliotecas externas para detectar cambios en los archivos", - "library_watching_settings": "Vigilancia de la biblioteca (EXPERIMENTAL)", + "library_watching_settings": "Vigilancia de la biblioteca [EXPERIMENTAL]", "library_watching_settings_description": "Vigilar automaticamente en busca de archivos modificados", "logging_enable_description": "Habilitar registro", "logging_level_description": "Indica el nivel de registro a utilizar cuando está habilitado.", "logging_settings": "Registro", - "machine_learning_availability_checks": "Verificaciones de disponibilidad", + "machine_learning_availability_checks": "Comprobaciones de disponibilidad", "machine_learning_availability_checks_description": "Automáticamente detectar y preferir servidores de machine learning disponibles", - "machine_learning_availability_checks_enabled": "Habilitar verificaciones de disponibilidad", - "machine_learning_availability_checks_interval": "Intervalo de verificación", - "machine_learning_availability_checks_interval_description": "Intervalo en milisegundos entre las verificaciones de disponibilidad", + "machine_learning_availability_checks_enabled": "Habilitar comprobaciones de disponibilidad", + "machine_learning_availability_checks_interval": "Intervalo de comprobación", + "machine_learning_availability_checks_interval_description": "Intervalo en milisegundos entre las comprobaciones de disponibilidad", "machine_learning_availability_checks_timeout": "Tiempo de espera de solicitud", - "machine_learning_availability_checks_timeout_description": "Tiempo de espera en milisegundos para verificaciones de disponibilidad", + "machine_learning_availability_checks_timeout_description": "Tiempo de espera en milisegundos para comprobaciones de disponibilidad", "machine_learning_clip_model": "Modelo CLIP (Contrastive Language-Image Pre-Training)", "machine_learning_clip_model_description": "El nombre de un modelo CLIP listado aquí. Tendrás que relanzar el trabajo 'Búsqueda Inteligente' para todos los elementos al cambiar de modelo.", "machine_learning_duplicate_detection": "Detección de duplicados", @@ -152,6 +154,18 @@ "machine_learning_min_detection_score_description": "Puntuación de confianza mínima para que se detecte una cara de 0 a 1. Los valores más bajos detectarán más rostros pero pueden generar falsos positivos.", "machine_learning_min_recognized_faces": "Rostros mínimos reconocidos", "machine_learning_min_recognized_faces_description": "El número mínimo de rostros reconocidos para que se cree una persona. Aumentar esto permite que el reconocimiento facial sea más preciso a costa de aumentar la posibilidad de que no se asigne una cara a una persona.", + "machine_learning_ocr": "OCR", + "machine_learning_ocr_description": "Usa el aprendizaje automático para reconocer texto en imágenes", + "machine_learning_ocr_enabled": "Activar OCR", + "machine_learning_ocr_enabled_description": "Si está desactivado, las imágenes no se someterán al reconocimiento de texto.", + "machine_learning_ocr_max_resolution": "Resolución máxima", + "machine_learning_ocr_max_resolution_description": "Las vistas previas por encima de esta resolución se redimensionarán manteniendo la relación de aspecto. Los valores más altos son más precisos, pero tardan más en procesarse y consumen más memoria.", + "machine_learning_ocr_min_detection_score": "Puntuación mínima de detección", + "machine_learning_ocr_min_detection_score_description": "Puntuación mínima de confianza para que el texto sea detectado de 0 a 1. Los valores más bajos detectarán más texto, pero pueden producir falsos positivos.", + "machine_learning_ocr_min_recognition_score": "Puntuación mínima de reconocimiento", + "machine_learning_ocr_min_score_recognition_description": "Puntuación mínima de confianza para que el texto detectado sea reconocido de 0 a 1. Los valores más bajos reconocerán más texto, pero pueden producir falsos positivos.", + "machine_learning_ocr_model": "Modelo de OCR", + "machine_learning_ocr_model_description": "Los modelos del servidor son más precisos que los modelos para móviles móviles, pero tardan más en procesar y consumen más memoria.", "machine_learning_settings": "Configuración de aprendizaje automático", "machine_learning_settings_description": "Administrar funciones y configuraciones de aprendizaje automático", "machine_learning_smart_search": "Busqueda inteligente", @@ -200,7 +214,7 @@ "nightly_tasks_sync_quota_usage_setting_description": "Actualizar la cuota de almacenamiento del usuario, según el uso actual", "no_paths_added": "No se han agregado rutas", "no_pattern_added": "No se han agregado patrones", - "note_apply_storage_label_previous_assets": "Nota: Para aplicar la etiqueta de almacenamiento a los elementos que ya se subieron, ejecuta la", + "note_apply_storage_label_previous_assets": "Nota: Para aplicar la Etiqueta de Almacenamiento a los elementos previamente subidos, ejecuta la", "note_cannot_be_changed_later": "NOTA: ¡No se puede cambiar posteriormente!", "notification_email_from_address": "Desde", "notification_email_from_address_description": "Dirección de correo electrónico del remitente, por ejemplo: \"Immich Photo Server \". Asegúrate de utilizar una dirección desde la que puedas enviar correos electrónicos.", @@ -209,6 +223,8 @@ "notification_email_ignore_certificate_errors_description": "Ignorar los errores de validación del certificado TLS (no recomendado)", "notification_email_password_description": "Contraseña a utilizar al autenticarse con el servidor de correo electrónico", "notification_email_port_description": "Puerto del servidor de correo electrónico (por ejemplo: 25, 465 o 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "Usar SMTPS (SMTP sobre TLS)", "notification_email_sent_test_email_button": "Enviar correo electrónico de prueba y guardar", "notification_email_setting_description": "Configuraciones para enviar notificaciones por correo electrónico", "notification_email_test_email": "Enviar email de prueba", @@ -239,8 +255,9 @@ "oauth_storage_quota_claim_description": "Fijar la cuota de almacenamiento del usuario automáticamente al valor solicitado.", "oauth_storage_quota_default": "Cuota de almacenamiento predeterminada (GiB)", "oauth_storage_quota_default_description": "Cuota (en GiB) que se usará cuando no se solicite un valor específico.", - "oauth_timeout": "Tiempo de espera agotado para la solicitud", + "oauth_timeout": "Tiempo de espera de la solicitud agotado", "oauth_timeout_description": "Tiempo de espera de solicitudes en milisegundos", + "ocr_job_description": "Usar aprendizaje automático para reconocer texto en imágenes", "password_enable_description": "Iniciar sesión con correo electrónico y contraseña", "password_settings": "Contraseña de Acceso", "password_settings_description": "Administrar la configuración de inicio de sesión con contraseña", @@ -331,7 +348,7 @@ "transcoding_max_b_frames": "Maximos B-frames", "transcoding_max_b_frames_description": "Los valores más altos mejoran la eficiencia de la compresión, pero ralentizan la codificación. Puede que no sea compatible con la aceleración de hardware en dispositivos más antiguos. 0 desactiva los fotogramas B, mientras que -1 establece este valor automáticamente.", "transcoding_max_bitrate": "Máxima tasa de bits", - "transcoding_max_bitrate_description": "Establecer una tasa de bits máxima puede hacer que los tamaños de archivos sean más predecibles con un costo menor para la calidad. A 720p, los valores típicos son 2600 kbit/s para VP9 o HEVC, o 4500 kbit/s para H.264. Deshabilitado si se establece en 0.", + "transcoding_max_bitrate_description": "Establecer una tasa de bits máxima puede hacer que los tamaños de archivo sean más predecibles a un coste menor en la calidad. A 720p, los valores típicos son 2600 kbit/s para VP9 o HEVC, o 4500 kbit/s para H.264. Se desactiva si se establece en 0. Cuando no se especifica una unidad, se asume k (para kbit/s); por lo tanto, 5000, 5000k y 5M (para Mbit/s) son equivalentes.", "transcoding_max_keyframe_interval": "Intervalo máximo de fotogramas clave", "transcoding_max_keyframe_interval_description": "Establece la distancia máxima de fotograma entre fotogramas clave. Los valores más bajos empeoran la eficiencia de la compresión, pero mejoran los tiempos de búsqueda y pueden mejorar la calidad en escenas con movimientos rápidos. 0 establece este valor automáticamente.", "transcoding_optimal_description": "Vídeos con una resolución superior a la fijada o que no están en un formato aceptado", @@ -349,7 +366,7 @@ "transcoding_target_resolution": "Resolución deseada", "transcoding_target_resolution_description": "Las resoluciones más altas pueden conservar más detalles, pero la codificación tarda más, tienen tamaños de archivo más grandes y pueden reducir la capacidad de respuesta de la aplicación.", "transcoding_temporal_aq": "AQ temporal", - "transcoding_temporal_aq_description": "Se aplica únicamente a NVENC. Aumenta la calidad de escenas con mucho detalle y poco movimiento. Puede que no sea compatible con dispositivos más antiguos.", + "transcoding_temporal_aq_description": "Solo se aplica a NVENC. La Cuantificación Adaptativa Temporal aumenta la calidad de las escenas con mucho detalle y poco movimiento. Podría no ser compatible con dispositivos más antiguos.", "transcoding_threads": "Hilos", "transcoding_threads_description": "Los valores más altos conducen a una codificación más rápida, pero dejan menos espacio para que el servidor procese otras tareas mientras está activo. Este valor no debe ser mayor que la cantidad de núcleos de CPU. Maximiza la utilización si se establece en 0.", "transcoding_tone_mapping": "Mapeo de tonos", @@ -400,11 +417,11 @@ "advanced_settings_prefer_remote_subtitle": "Algunos dispositivos tardan mucho en cargar las miniaturas desde los archivos locales. Activa esta opción para cargar imágenes remotas en su lugar.", "advanced_settings_prefer_remote_title": "Preferir imágenes remotas", "advanced_settings_proxy_headers_subtitle": "Configura headers HTTP que Immich incluirá en cada petición de red", - "advanced_settings_proxy_headers_title": "Cabeceras Proxy", + "advanced_settings_proxy_headers_title": "Cabeceras proxy personalizadas [EXPERIMENTAL]", "advanced_settings_readonly_mode_subtitle": "Habilita el modo de solo lectura donde las fotografías sólo pueden ser vistas, funciones como seleccionar múltiples imágenes, compartir, transmitir, eliminar son deshabilitadas. Habilita/Deshabilita solo lectura vía el avatar del usuario en la pantalla principal", - "advanced_settings_readonly_mode_title": "Modo Solo lectura", + "advanced_settings_readonly_mode_title": "Modo solo lectura", "advanced_settings_self_signed_ssl_subtitle": "Omitir verificación del certificado SSL del servidor. Requerido para certificados autofirmados.", - "advanced_settings_self_signed_ssl_title": "Permitir certificados autofirmados", + "advanced_settings_self_signed_ssl_title": "Permitir certificados SSL autofirmados [EXPERIMENTAL]", "advanced_settings_sync_remote_deletions_subtitle": "Eliminar o restaurar automáticamente un recurso en este dispositivo cuando se realice esa acción en la web", "advanced_settings_sync_remote_deletions_title": "Sincronizar eliminaciones remotas [EXPERIMENTAL]", "advanced_settings_tile_subtitle": "Configuraciones avanzadas del usuario", @@ -443,7 +460,7 @@ "album_viewer_appbar_share_leave": "Abandonar álbum", "album_viewer_appbar_share_to": "Compartir Con", "album_viewer_page_share_add_users": "Agregar usuarios", - "album_with_link_access": "Permite que cualquiera que tenga este enlace vea las fotos y las personas del álbum.", + "album_with_link_access": "Permitir que cualquiera que tenga el enlace vea las fotos y las personas del álbum.", "albums": "Álbumes", "albums_count": "{count, plural, one {{count, number} álbum} other {{count, number} álbumes}}", "albums_default_sort_order": "Ordenación por defecto de los álbumes", @@ -457,17 +474,21 @@ "allow_dark_mode": "Permitir modo oscuro", "allow_edits": "Permitir edición", "allow_public_user_to_download": "Permitir descargas a los usuarios públicos", - "allow_public_user_to_upload": "Permitir subir fotos a los usuarios públicos", + "allow_public_user_to_upload": "Permitir a los usuarios públicos subir fotos", "alt_text_qr_code": "Código QR", "anti_clockwise": "En sentido antihorario", "api_key": "Clave API", "api_key_description": "Este valor sólo se mostrará una vez. Asegúrese de copiarlo antes de cerrar la ventana.", "api_key_empty": "El nombre de su clave API no debe estar vacío", "api_keys": "Claves API", + "app_architecture_variant": "Variante (Arquitectura)", "app_bar_signout_dialog_content": "¿Estás seguro que quieres cerrar sesión?", "app_bar_signout_dialog_ok": "Sí", "app_bar_signout_dialog_title": "Cerrar sesión", + "app_download_links": "Enlaces de Descarga de la Aplicación", "app_settings": "Ajustes de la aplicacion", + "app_stores": "App Stores", + "app_update_available": "Actualización de aplicación está disponible", "appears_in": "Aparece en", "apply_count": "Aplicar ({count, number})", "archive": "Archivo", @@ -551,6 +572,7 @@ "backup_albums_sync": "Sincronización de álbumes de respaldo", "backup_all": "Todos", "backup_background_service_backup_failed_message": "Error al copiar elementos. Reintentando…", + "backup_background_service_complete_notification": "Copia de seguridad de activos completada", "backup_background_service_connection_failed_message": "Error al conectar con el servidor. Reintentando…", "backup_background_service_current_upload_notification": "Subiendo {filename}", "backup_background_service_default_notification": "Comprobando nuevos elementos…", @@ -598,6 +620,7 @@ "backup_controller_page_turn_on": "Activar la copia de seguridad", "backup_controller_page_uploading_file_info": "Subiendo información del archivo", "backup_err_only_album": "No se puede eliminar el único álbum", + "backup_error_sync_failed": "La sincronización falló. No es posible procesar la copia de seguridad.", "backup_info_card_assets": "elementos", "backup_manual_cancelled": "Cancelado", "backup_manual_in_progress": "Subida ya en progreso. Vuelve a intentarlo más tarde", @@ -646,7 +669,7 @@ "cannot_merge_people": "No se pueden fusionar personas", "cannot_undo_this_action": "¡No puedes deshacer esta acción!", "cannot_update_the_description": "No se puede actualizar la descripción", - "cast": "Convertir", + "cast": "Enviar contenido", "cast_description": "Configura los posibles destinos de retransmisión", "change_date": "Cambiar fecha", "change_description": "Cambiar descripción", @@ -659,6 +682,8 @@ "change_password_description": "Esta es la primera vez que inicia sesión en el sistema o se ha realizado una solicitud para cambiar su contraseña. Por favor ingrese la nueva contraseña a continuación.", "change_password_form_confirm_password": "Confirmar contraseña", "change_password_form_description": "Hola {name},\n\nEsta es la primera vez que inicias sesión en el sistema o se ha solicitado cambiar tu contraseña. Por favor, introduce la nueva contraseña a continuación.", + "change_password_form_log_out": "Cerrar sesión los demás dispositivos", + "change_password_form_log_out_description": "Se recomienda cerrar sesión en todos los demás dispositivos", "change_password_form_new_password": "Nueva contraseña", "change_password_form_password_mismatch": "Las contraseñas no coinciden", "change_password_form_reenter_new_password": "Vuelve a ingresar la nueva contraseña", @@ -686,7 +711,7 @@ "client_cert_invalid_msg": "Archivo de certificado no válido o contraseña incorrecta", "client_cert_remove_msg": "El certificado de cliente se ha eliminado", "client_cert_subtitle": "Solo se admite el formato PKCS12 (.p12, .pfx). La importación/eliminación de certificados solo está disponible antes de iniciar sesión", - "client_cert_title": "Certificado de cliente SSL", + "client_cert_title": "Certificado de cliente SSL [EXPERIMENTAL]", "clockwise": "En el sentido de las agujas del reloj", "close": "Cerrar", "collapse": "Agrupar", @@ -698,7 +723,6 @@ "comments_and_likes": "Comentarios y me gusta", "comments_are_disabled": "Los comentarios están deshabilitados", "common_create_new_album": "Crear nuevo álbum", - "common_server_error": "Por favor, verifica tu conexión de red, asegúrate de que el servidor esté accesible y las versiones de la aplicación y del servidor sean compatibles.", "completed": "Completado", "confirm": "Confirmar", "confirm_admin_password": "Confirmar contraseña del administrador", @@ -737,6 +761,7 @@ "create": "Crear", "create_album": "Crear álbum", "create_album_page_untitled": "Sin título", + "create_api_key": "Crear clave API", "create_library": "Crear biblioteca", "create_link": "Crear enlace", "create_link_to_share": "Crear enlace compartido", @@ -766,6 +791,7 @@ "daily_title_text_date_year": "E dd de MMM, yyyy", "dark": "Oscuro", "dark_theme": "Alternar tema oscuro", + "date": "Fecha", "date_after": "Fecha posterior", "date_and_time": "Fecha y Hora", "date_before": "Fecha anterior", @@ -812,7 +838,7 @@ "deletes_missing_assets": "Elimina archivos que faltan en el disco duro", "description": "Descripción", "description_input_hint_text": "Agregar descripción...", - "description_input_submit_error": "Error al actualizar la descripción, verifica el registro para obtener más detalles", + "description_input_submit_error": "Error al actualizar la descripción, comprueba el registro para obtener más detalles", "deselect_all": "Deseleccionar Todo", "details": "Detalles", "direction": "Dirección", @@ -880,7 +906,6 @@ "edit_tag": "Editar etiqueta", "edit_title": "Editar Titulo", "edit_user": "Editar usuario", - "edited": "Editado", "editor": "Editor", "editor_close_without_save_prompt": "No se guardarán los cambios", "editor_close_without_save_title": "¿Cerrar el editor?", @@ -1036,6 +1061,7 @@ "exif_bottom_sheet_description_error": "Error al actualizar la descripción", "exif_bottom_sheet_details": "DETALLES", "exif_bottom_sheet_location": "UBICACIÓN", + "exif_bottom_sheet_no_description": "Sin descripción", "exif_bottom_sheet_people": "PERSONAS", "exif_bottom_sheet_person_add_person": "Agregar nombre", "exit_slideshow": "Salir de la presentación", @@ -1074,9 +1100,10 @@ "features_setting_description": "Administrar las funciones de la aplicación", "file_name": "Nombre de archivo", "file_name_or_extension": "Nombre del archivo o extensión", + "file_size": "Tamaño del archivo", "filename": "Nombre del archivo", "filetype": "Tipo de archivo", - "filter": "Filtrar", + "filter": "Filtros", "filter_people": "Filtrar personas", "filter_places": "Filtrar lugares", "find_them_fast": "Encuéntrelos rápidamente por nombre con la búsqueda", @@ -1087,7 +1114,7 @@ "folders": "Carpetas", "folders_feature_description": "Explorar la vista de carpetas para las fotos y los videos en el sistema de archivos", "forgot_pin_code_question": "¿Olvidaste tu código PIN?", - "forward": "Reenviar", + "forward": "Avanzar", "gcast_enabled": "Google Cast", "gcast_enabled_description": "Esta funcionalidad carga recursos externos desde Google para poder funcionar.", "general": "General", @@ -1117,7 +1144,6 @@ "header_settings_field_validator_msg": "El valor no puede estar vacío", "header_settings_header_name_input": "Nombre de la cabecera", "header_settings_header_value_input": "Valor de la cabecera", - "headers_settings_tile_subtitle": "Configura headers HTTP que la aplicación incluirá en cada petición de red", "headers_settings_tile_title": "Cabeceras de proxy personalizadas", "hi_user": "Hola {name} ({email})", "hide_all_people": "Ocultar a todas las personas", @@ -1238,6 +1264,7 @@ "local_media_summary": "Resumen de Medios Locales", "local_network": "Red local", "local_network_sheet_info": "La aplicación se conectará al servidor a través de esta URL cuando utilice la red Wi-Fi especificada", + "location": "Ubicación", "location_permission": "Permiso de ubicación", "location_permission_content": "Para usar la función de cambio automático, Immich necesita permiso de ubicación precisa para poder leer el nombre de la red Wi-Fi actual", "location_picker_choose_on_map": "Elegir en el mapa", @@ -1255,7 +1282,7 @@ "logged_out_device": "Dispositivo desconectado", "login": "Inicio de sesión", "login_disabled": "El inicio de sesión ha sido desactivado", - "login_form_api_exception": "Excepción producida por API. Por favor, verifica el URL del servidor e inténtalo de nuevo.", + "login_form_api_exception": "Excepción producida por API. Por favor, comprueba el URL del servidor e inténtalo de nuevo.", "login_form_back_button_text": "Atrás", "login_form_email_hint": "tucorreo@correo.com", "login_form_endpoint_hint": "http://tu-ip-de-servidor:puerto", @@ -1265,7 +1292,7 @@ "login_form_err_invalid_url": "URL no válida", "login_form_err_leading_whitespace": "Espacio en blanco inicial", "login_form_err_trailing_whitespace": "Espacio en blanco al final", - "login_form_failed_get_oauth_server_config": "Error al iniciar sesión con OAuth, verifica la URL del servidor", + "login_form_failed_get_oauth_server_config": "Error al iniciar sesión con OAuth, comprueba la URL del servidor", "login_form_failed_get_oauth_server_disable": "La función de OAuth no está disponible en este servidor", "login_form_failed_login": "Error al iniciar sesión, comprueba la URL del servidor, el correo electrónico y la contraseña", "login_form_handshake_exception": "Hubo una excepción de handshake con el servidor. Activa la compatibilidad con certificados autofirmados en la configuración si estás utilizando un certificado autofirmado.", @@ -1342,6 +1369,8 @@ "minute": "Minuto", "minutes": "Minutos", "missing": "Faltante", + "mobile_app": "Aplicación Móvil", + "mobile_app_download_onboarding_note": "Descarga la aplicación móvil utilizando las siguientes opciones", "model": "Modelo", "month": "Mes", "monthly_title_text_date_format": "MMMM a", @@ -1360,6 +1389,8 @@ "my_albums": "Mis álbumes", "name": "Nombre", "name_or_nickname": "Nombre o apodo", + "navigate": "Navegar", + "navigate_to_time": "Navegar a Hora", "network_requirement_photos_upload": "Usar datos móviles para crear una copia de seguridad de las fotos", "network_requirement_videos_upload": "Usar datos móviles para crear una copia de seguridad de los videos", "network_requirements": "Requisitos de red", @@ -1369,6 +1400,7 @@ "never": "Nunca", "new_album": "Nuevo álbum", "new_api_key": "Nueva clave API", + "new_date_range": "Nuevo rango de fechas", "new_password": "Nueva contraseña", "new_person": "Nueva persona", "new_pin_code": "Nuevo PIN", @@ -1419,6 +1451,9 @@ "notifications": "Notificaciones", "notifications_setting_description": "Administrar notificaciones", "oauth": "OAuth", + "obtainium_configurator": "Configurador de Obtainium", + "obtainium_configurator_instructions": "Usa Obtainium para instalar y actualizar la aplicación de Android directamente desde las versiones publicadas en el GitHub de Immich. Crea una clave API y selecciona una variante para generar tu enlace de configuración de Obtainium", + "ocr": "OCR", "official_immich_resources": "Recursos oficiales de Immich", "offline": "Desconectado", "offset": "Desviación", @@ -1523,6 +1558,9 @@ "play_memories": "Reproducir recuerdos", "play_motion_photo": "Reproducir foto en movimiento", "play_or_pause_video": "Reproducir o pausar vídeo", + "play_original_video": "Reproducir video original", + "play_original_video_setting_description": "Preferir la reproducción de videos originales en lugar de videos transcodificados. Si el recurso original no es compatible, es posible que no se reproduzca correctamente.", + "play_transcoded_video": "Reproducir video transcodificado", "please_auth_to_access": "Por favor, autentícate para acceder", "port": "Puerto", "preferences_settings_subtitle": "Configuraciones de la aplicación", @@ -1540,13 +1578,9 @@ "privacy": "Privacidad", "profile": "Perfil", "profile_drawer_app_logs": "Registros", - "profile_drawer_client_out_of_date_major": "La app está desactualizada. Por favor actualiza a la última versión principal.", - "profile_drawer_client_out_of_date_minor": "La app está desactualizada. Por favor actualiza a la última versión menor.", "profile_drawer_client_server_up_to_date": "Cliente y Servidor están actualizados", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Modo Solo lectura habilitado. Mantén pulsado el icono del avatar del usuario para salir.", - "profile_drawer_server_out_of_date_major": "El servidor está desactualizado. Por favor actualiza a la última versión principal.", - "profile_drawer_server_out_of_date_minor": "El servidor está desactualizado. Por favor actualiza a la última versión menor.", "profile_image_of_user": "Foto de perfil de {user}", "profile_picture_set": "Conjunto de imágenes de perfil.", "public_album": "Álbum público", @@ -1603,8 +1637,8 @@ "recent_searches": "Búsquedas recientes", "recently_added": "Añadidos recientemente", "recently_added_page_title": "Recién Agregadas", - "recently_taken": "Recientemente tomado", - "recently_taken_page_title": "Recientemente Tomado", + "recently_taken": "Tomadas recientemente", + "recently_taken_page_title": "Tomadas Recientemente", "refresh": "Actualizar", "refresh_encoded_videos": "Recargar los vídeos codificados", "refresh_faces": "Actualizar caras", @@ -1663,6 +1697,7 @@ "reset_sqlite_confirmation": "¿Estás seguro que deseas restablecer la base de datos SQLite? Deberás cerrar sesión y volver a iniciarla para resincronizar los datos", "reset_sqlite_success": "Restablecer exitosamente la base de datos SQLite", "reset_to_default": "Restablecer los valores predeterminados", + "resolution": "Resolución", "resolve_duplicates": "Resolver duplicados", "resolved_all_duplicates": "Todos los duplicados resueltos", "restore": "Restaurar", @@ -1681,6 +1716,7 @@ "running": "En ejecución", "save": "Guardar", "save_to_gallery": "Guardado en la galería", + "saved": "Guardado", "saved_api_key": "Clave API guardada", "saved_profile": "Perfil guardado", "saved_settings": "Configuraciones guardadas", @@ -1697,6 +1733,9 @@ "search_by_description_example": "Día de senderismo en Sapa", "search_by_filename": "Buscar por nombre de archivo o extensión", "search_by_filename_example": "es decir IMG_1234.JPG o PNG", + "search_by_ocr": "Buscar por OCR", + "search_by_ocr_example": "Café con leche", + "search_camera_lens_model": "Buscar modelo de lente...", "search_camera_make": "Buscar fabricante de cámara...", "search_camera_model": "Buscar modelo de cámara...", "search_city": "Buscar ciudad...", @@ -1713,6 +1752,7 @@ "search_filter_location_title": "Seleccionar una ubicación", "search_filter_media_type": "Tipo de archivo", "search_filter_media_type_title": "Seleccionar el tipo de archivo", + "search_filter_ocr": "Buscar por OCR", "search_filter_people_title": "Seleccionar personas", "search_for": "Buscar", "search_for_existing_person": "Buscar persona existente", @@ -1769,12 +1809,13 @@ "send_message": "Enviar mensaje", "send_welcome_email": "Enviar correo de bienvenida", "server_endpoint": "Punto final del servidor", - "server_info_box_app_version": "Versión de la Aplicación", + "server_info_box_app_version": "Versión de la aplicación", "server_info_box_server_url": "Enlace del servidor", "server_offline": "Servidor desconectado", "server_online": "Servidor en línea", "server_privacy": "Privacidad del Servidor", "server_stats": "Estadísticas del servidor", + "server_update_available": "Actualización de servidor disponible", "server_version": "Versión del servidor", "set": "Establecer", "set_as_album_cover": "Establecer portada del álbum", @@ -1803,6 +1844,8 @@ "setting_notifications_subtitle": "Ajusta tus preferencias de notificación", "setting_notifications_total_progress_subtitle": "Progreso general de subida (elementos completados/total)", "setting_notifications_total_progress_title": "Mostrar progreso total de copia de seguridad en segundo plano", + "setting_video_viewer_auto_play_subtitle": "Reproducir vídeos automáticamente al abrirlos", + "setting_video_viewer_auto_play_title": "Reproducir vídeos automáticamente", "setting_video_viewer_looping_title": "Bucle", "setting_video_viewer_original_video_subtitle": "Al reproducir un video en streaming desde el servidor, reproducir el original incluso cuando haya una transcodificación disponible. Puede causar buffering. Los videos disponibles localmente se reproducen en calidad original independientemente de esta configuración.", "setting_video_viewer_original_video_title": "Forzar vídeo original", @@ -1904,7 +1947,7 @@ "skip_to_content": "Saltar al contenido", "skip_to_folders": "Ir a las carpetas", "skip_to_tags": "Ir a las etiquetas", - "slideshow": "Diapositivas", + "slideshow": "Pase de diapositivas", "slideshow_settings": "Ajustes de diapositivas", "sort_albums_by": "Ordenar álbumes por…", "sort_created": "Fecha de creación", @@ -1982,6 +2025,7 @@ "theme_setting_three_stage_loading_title": "Activar carga en tres etapas", "they_will_be_merged_together": "Se fusionarán entre sí", "third_party_resources": "Recursos de terceros", + "time": "Tiempo", "time_based_memories": "Recuerdos basados en tiempo", "timeline": "Cronología", "timezone": "Zona horaria", @@ -2014,6 +2058,7 @@ "troubleshoot": "Solucionar problemas", "type": "Tipo", "unable_to_change_pin_code": "No se ha podido cambiar el PIN", + "unable_to_check_version": "No se puede comprobar la versión de la aplicación o del servidor", "unable_to_setup_pin_code": "No se ha podido establecer el PIN", "unarchive": "Desarchivar", "unarchive_action_prompt": "{count} eliminados del archivo", diff --git a/i18n/et.json b/i18n/et.json index 3f38f32d3e..7e611d3ec3 100644 --- a/i18n/et.json +++ b/i18n/et.json @@ -28,10 +28,12 @@ "add_to_album": "Lisa albumisse", "add_to_album_bottom_sheet_added": "Lisatud albumisse {album}", "add_to_album_bottom_sheet_already_exists": "On juba albumis {album}", + "add_to_album_bottom_sheet_some_local_assets": "Kõiki lokaalseid üksuseid ei õnnestunud albumisse lisada", "add_to_album_toggle": "Muuda albumi {album} valikut", "add_to_albums": "Lisa albumitesse", "add_to_albums_count": "Lisa albumitesse ({count})", "add_to_shared_album": "Lisa jagatud albumisse", + "add_upload_to_stack": "Virnasta üleslaaditud üksus", "add_url": "Lisa URL", "added_to_archive": "Lisatud arhiivi", "added_to_favorites": "Lisatud lemmikutesse", @@ -118,7 +120,7 @@ "library_settings_description": "Halda välise kogu seadeid", "library_tasks_description": "Otsi välistest kogudest uusi ja muutunud üksuseid", "library_watching_enable_description": "Jälgi välises kogus failide muudatusi", - "library_watching_settings": "Kogu jälgimine (EKSPERIMENTAALNE)", + "library_watching_settings": "Kogu jälgimine [EKSPERIMENTAALNE]", "library_watching_settings_description": "Jälgi automaatselt muutunud faile", "logging_enable_description": "Luba logimine", "logging_level_description": "Kui lubatud, millist logimistaset kasutada.", @@ -152,6 +154,18 @@ "machine_learning_min_detection_score_description": "Minimaalne usaldusskoor näo avastamiseks, vahemikus 0-1. Madalamad väärtused leiavad rohkem nägusid, kuid võib esineda valepositiivseid.", "machine_learning_min_recognized_faces": "Minimaalne tuvastatud nägude arv", "machine_learning_min_recognized_faces_description": "Minimaalne tuvastatud nägude arv, mida saab isikuks grupeerida. Selle suurendamine teeb näotuvastuse täpsemaks, kuid suureneb tõenäosus, et nägu ei seostata ühegi isikuga.", + "machine_learning_ocr": "OCR", + "machine_learning_ocr_description": "Kasuta piltidelt teksti tuvastamiseks masinõpet", + "machine_learning_ocr_enabled": "Luba OCR", + "machine_learning_ocr_enabled_description": "Kui keelatud, ei rakendata piltidele tekstituvastust.", + "machine_learning_ocr_max_resolution": "Maksimaalne resolutsioon", + "machine_learning_ocr_max_resolution_description": "Eelvaated üle selle resolutsiooni vähendatakse, säilitades külgede suhte. Suuremad väärtused on täpsemad, aga töötlemine võtab kauem aega ja kasutab rohkem mälu.", + "machine_learning_ocr_min_detection_score": "Minimaalne avastusskoor", + "machine_learning_ocr_min_detection_score_description": "Minimaalne usaldusskoor teksti avastamiseks, vahemikus 0-1. Madalamad väärtused leiavad rohkem teksti, kuid võib esineda valepositiivseid.", + "machine_learning_ocr_min_recognition_score": "Minimaalne tuvastusskoor", + "machine_learning_ocr_min_score_recognition_description": "Minimaalne usaldusskoor avastatud teksti tuvastamiseks, vahemikus 0-1. Madalamad väärtused tuvastavad rohkem teksti, kuid võib esineda valepositiivseid.", + "machine_learning_ocr_model": "OCR mudel", + "machine_learning_ocr_model_description": "Serverimudelid on täpsemad kui mobiilsed mudelid, aga töötlemine võtab rohkem aega ja kasutab rohkem mälu.", "machine_learning_settings": "Masinõppe seaded", "machine_learning_settings_description": "Halda masinõppe funktsioone ja seadeid", "machine_learning_smart_search": "Nutiotsing", @@ -209,6 +223,8 @@ "notification_email_ignore_certificate_errors_description": "Ignoreeri TLS sertifikaadi valideerimise vigu (mittesoovituslik)", "notification_email_password_description": "Parool e-posti serveriga autentimiseks", "notification_email_port_description": "E-posti serveri port (nt. 25, 465 või 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "Kasuta SMTPS-i (SMTP üle TLS-i)", "notification_email_sent_test_email_button": "Saada test e-kiri ja salvesta", "notification_email_setting_description": "E-posti teel teavituste saatmise seaded", "notification_email_test_email": "Saada test e-kiri", @@ -241,6 +257,7 @@ "oauth_storage_quota_default_description": "Kvoot (GiB), mida kasutada, kui ühtegi väidet pole esitatud.", "oauth_timeout": "Päringu ajalõpp", "oauth_timeout_description": "Päringute ajalõpp millisekundites", + "ocr_job_description": "Kasuta piltidelt teksti tuvastamiseks masinõpet", "password_enable_description": "Logi sisse e-posti aadressi ja parooliga", "password_settings": "Parooliga sisselogimine", "password_settings_description": "Halda parooliga sisselogimise seadeid", @@ -331,7 +348,7 @@ "transcoding_max_b_frames": "Maksimaalne B-kaadrite arv", "transcoding_max_b_frames_description": "Kõrgemad väärtused parandavad pakkimise efektiivsust, aga aeglustavad kodeerimist. See valik ei pruugi olla ühilduv riistvaralise kiirendusega vanematel seadmetel. 0 lülitab B-kaadrid välja, -1 määrab väärtuse automaatselt.", "transcoding_max_bitrate": "Maksimaalne bitisagedus", - "transcoding_max_bitrate_description": "Maksimaalse bitisageduse määramine teeb failisuurused ennustatavamaks, väikese kvaliteedikao hinnaga. 720p resolutsiooni puhul on tüüpilised väärtused 2600 kbit/s (VP9 ja HEVC) või 4500 kbit/s (H.264). Väärtus 0 eemaldab piirangu.", + "transcoding_max_bitrate_description": "Maksimaalse bitisageduse määramine teeb failisuurused ennustatavamaks, väikese kvaliteedikao hinnaga. 720p resolutsiooni puhul on tüüpilised väärtused 2600 kbit/s (VP9 ja HEVC) või 4500 kbit/s (H.264). Väärtus 0 eemaldab piirangu. Kui ühikut pole määratud, eeldatakse k (kbit/s); seega 5000, 5000k ja 5M (Mbit/s) on samaväärsed.", "transcoding_max_keyframe_interval": "Maksimaalne võtmekaadri intervall", "transcoding_max_keyframe_interval_description": "Määrab maksimaalse kauguse võtmekaadrite vahel. Madalamad väärtused vähendavad pakkimise efektiivsust, aga parandavad otsimiskiirust ning võivad tõsta kiire liikumisega stseenide kvaliteeti. 0 määrab väärtuse automaatselt.", "transcoding_optimal_description": "Kõrgema kui lubatud resolutsiooniga või mittelubatud formaadis videod", @@ -349,7 +366,7 @@ "transcoding_target_resolution": "Sihtresolutsioon", "transcoding_target_resolution_description": "Kõrgemad resolutsioonid säilitavad rohkem detaile, aga kodeerimine võtab kauem aega, tekitab suuremaid faile ning võib mõjutada rakenduse töökiirust.", "transcoding_temporal_aq": "Temporal AQ", - "transcoding_temporal_aq_description": "Rakendub NVENC puhul. Parandab paljude detailide, aga vähese liikumisega stseenide kvaliteeti. Ei pruugi ühilduda vanemate seadmetega.", + "transcoding_temporal_aq_description": "Rakendub NVENC puhul. Temporal Adaptive Quantization parandab paljude detailide, aga vähese liikumisega stseenide kvaliteeti. Ei pruugi ühilduda vanemate seadmetega.", "transcoding_threads": "Lõimed", "transcoding_threads_description": "Kõrgem väärtus tähendab kiiremat kodeerimist, aga jätab serverile muude tegevuste jaoks vähem ressursse. See väärtus ei tohiks olla suurem kui protsessori tuumade arv. Väärtus 0 tähendab maksimaalset kasutust.", "transcoding_tone_mapping": "Toonivastendus", @@ -400,11 +417,11 @@ "advanced_settings_prefer_remote_subtitle": "Mõned seadmed laadivad lokaalsete üksuste pisipilte piinavalt aeglaselt. Aktiveeri see seadistus, et laadida selle asemel kaugpilte.", "advanced_settings_prefer_remote_title": "Eelista kaugpilte", "advanced_settings_proxy_headers_subtitle": "Määra vaheserveri päised, mida Immich peaks iga päringuga saatma", - "advanced_settings_proxy_headers_title": "Vaheserveri päised", + "advanced_settings_proxy_headers_title": "Kohandatud vaheserveri päised [EKSPERIMENTAALNE]", "advanced_settings_readonly_mode_subtitle": "Lülitab sisse kirjutuskaitserežiimi, milles saab fotosid ainult vaadata ning toimingud nagu mitme pildi valimine, jagamine, edastamine ja kustutamine on keelatud. Lülita kirjutuskaitserežiim sisse/välja põhiekraanil oleva avatari kaudu", "advanced_settings_readonly_mode_title": "Kirjutuskaitserežiim", "advanced_settings_self_signed_ssl_subtitle": "Jätab serveri lõpp-punkti SSL-sertifikaadi kontrolli vahele. Nõutud endasigneeritud sertifikaatide jaoks.", - "advanced_settings_self_signed_ssl_title": "Luba endasigneeritud SSL-sertifikaadid", + "advanced_settings_self_signed_ssl_title": "Luba endasigneeritud SSL-sertifikaadid [EKSPERIMENTAALNE]", "advanced_settings_sync_remote_deletions_subtitle": "Kustuta või taasta üksus selles seadmes automaatself, kui sama tegevus toimub veebis", "advanced_settings_sync_remote_deletions_title": "Sünkrooni kaugkustutamised [EKSPERIMENTAALNE]", "advanced_settings_tile_subtitle": "Edasijõudnud kasutajate seaded", @@ -464,10 +481,14 @@ "api_key_description": "Seda väärtust kuvatakse ainult üks kord. Kopeeri see enne akna sulgemist.", "api_key_empty": "Su API võtme nimi ei tohiks olla tühi", "api_keys": "API võtmed", + "app_architecture_variant": "Variant (arhitektuur)", "app_bar_signout_dialog_content": "Kas oled kindel, et soovid välja logida?", "app_bar_signout_dialog_ok": "Jah", "app_bar_signout_dialog_title": "Logi välja", + "app_download_links": "Rakenduse allalaadimise lingid", "app_settings": "Rakenduse seaded", + "app_stores": "Rakendusepoed", + "app_update_available": "Rakenduse uuendus on saadaval", "appears_in": "Albumid", "apply_count": "Rakenda ({count, number})", "archive": "Arhiiv", @@ -551,6 +572,7 @@ "backup_albums_sync": "Varundusalbumite sünkroniseerimine", "backup_all": "Kõik", "backup_background_service_backup_failed_message": "Üksuste varundamine ebaõnnestus. Uuesti proovimine…", + "backup_background_service_complete_notification": "Üksuste varundamine lõppenud", "backup_background_service_connection_failed_message": "Serveriga ühendumine ebaõnnestus. Uuesti proovimine…", "backup_background_service_current_upload_notification": "{filename} üleslaadimine", "backup_background_service_default_notification": "Uute üksuste kontrollimine…", @@ -598,6 +620,7 @@ "backup_controller_page_turn_on": "Lülita esiplaanil varundus sisse", "backup_controller_page_uploading_file_info": "Faili info üleslaadimine", "backup_err_only_album": "Ei saa ainsat albumit eemaldada", + "backup_error_sync_failed": "Sünkroonimine ebaõnnestus. Varundust ei saa töödelda.", "backup_info_card_assets": "üksused", "backup_manual_cancelled": "Tühistatud", "backup_manual_in_progress": "Üleslaadimine juba käib. Proovi hiljem uuesti", @@ -659,6 +682,8 @@ "change_password_description": "See on su esimene kord süsteemi siseneda, või on tehtud taotlus parooli muutmiseks. Palun sisesta allpool uus parool.", "change_password_form_confirm_password": "Kinnita parool", "change_password_form_description": "Hei {name},\n\nSa kas logid süsteemi esimest korda sisse, või on esitatud taotlus sinu parooli muutmiseks. Palun sisesta allpool uus parool.", + "change_password_form_log_out": "Logi muudest seadmetest välja", + "change_password_form_log_out_description": "Soovituslik on kõigist muudest seadmetest välja logida", "change_password_form_new_password": "Uus parool", "change_password_form_password_mismatch": "Paroolid ei klapi", "change_password_form_reenter_new_password": "Korda uut parooli", @@ -686,7 +711,7 @@ "client_cert_invalid_msg": "Vigane sertifikaadi fail või vale parool", "client_cert_remove_msg": "Klientsertifikaat on eemaldatud", "client_cert_subtitle": "Toetab ainult PKCS12 (.p12, .pfx) formaati. Sertifikaadi importimine/eemaldamine on saadaval ainult enne sisselogimist", - "client_cert_title": "SSL klientsertifikaat", + "client_cert_title": "SSL klientsertifikaat [EKSPERIMENTAALNE]", "clockwise": "Päripäeva", "close": "Sulge", "collapse": "Peida", @@ -698,7 +723,6 @@ "comments_and_likes": "Kommentaarid ja meeldimised", "comments_are_disabled": "Kommentaarid on keelatud", "common_create_new_album": "Lisa uus album", - "common_server_error": "Kontrolli oma võrguühendust ja veendu, et server on kättesaadav ning rakenduse ja serveri versioonid on ühilduvad.", "completed": "Lõpetatud", "confirm": "Kinnita", "confirm_admin_password": "Kinnita administraatori parool", @@ -737,6 +761,7 @@ "create": "Lisa", "create_album": "Lisa album", "create_album_page_untitled": "Pealkirjata", + "create_api_key": "Lisa API võti", "create_library": "Lisa kogu", "create_link": "Lisa link", "create_link_to_share": "Lisa jagamiseks link", @@ -766,6 +791,7 @@ "daily_title_text_date_year": "d. MMMM yyyy", "dark": "Tume", "dark_theme": "Lülita tume teema", + "date": "Kuupäev", "date_after": "Kuupäev pärast", "date_and_time": "Kuupäev ja kellaaeg", "date_before": "Kuupäev enne", @@ -880,7 +906,6 @@ "edit_tag": "Muuda silti", "edit_title": "Muuda pealkirja", "edit_user": "Muuda kasutajat", - "edited": "Muudetud", "editor": "Muutja", "editor_close_without_save_prompt": "Muudatusi ei salvestata", "editor_close_without_save_title": "Sulge muutja?", @@ -1036,6 +1061,7 @@ "exif_bottom_sheet_description_error": "Viga kirjelduse muutmisel", "exif_bottom_sheet_details": "ÜKSIKASJAD", "exif_bottom_sheet_location": "ASUKOHT", + "exif_bottom_sheet_no_description": "Kirjeldus puudub", "exif_bottom_sheet_people": "ISIKUD", "exif_bottom_sheet_person_add_person": "Lisa nimi", "exit_slideshow": "Sulge slaidiesitlus", @@ -1074,6 +1100,7 @@ "features_setting_description": "Halda rakenduse funktsioone", "file_name": "Failinimi", "file_name_or_extension": "Failinimi või -laiend", + "file_size": "Failisuurus", "filename": "Failinimi", "filetype": "Failitüüp", "filter": "Filter", @@ -1117,7 +1144,6 @@ "header_settings_field_validator_msg": "Väärtus ei saa olla tühi", "header_settings_header_name_input": "Päise nimi", "header_settings_header_value_input": "Päise väärtus", - "headers_settings_tile_subtitle": "Määra vaheserveri päised, mida rakendus peaks iga päringuga saatma", "headers_settings_tile_title": "Kohandatud vaheserveri päised", "hi_user": "Tere {name} ({email})", "hide_all_people": "Peida kõik isikud", @@ -1238,6 +1264,7 @@ "local_media_summary": "Lokaalsete üksuste kokkuvõte", "local_network": "Kohalik võrk", "local_network_sheet_info": "Rakendus ühendub valitud Wi-Fi võrgus olles serveriga selle URL-i kaudu", + "location": "Asukoht", "location_permission": "Asukoha luba", "location_permission_content": "Automaatseks ümberlülitumiseks vajab Immich täpse asukoha luba, et saaks lugeda aktiivse WiFi-võrgu nime", "location_picker_choose_on_map": "Vali kaardil", @@ -1322,7 +1349,7 @@ "marked_all_as_read": "Kõik märgiti loetuks", "matches": "Ühtivad failid", "matching_assets": "Ühtivad üksused", - "media_type": "Meediumi tüüp", + "media_type": "Üksuse tüüp", "memories": "Mälestused", "memories_all_caught_up": "Ongi kõik", "memories_check_back_tomorrow": "Vaata homme juba uusi mälestusi", @@ -1342,6 +1369,8 @@ "minute": "Minut", "minutes": "Minutit", "missing": "Puuduvad", + "mobile_app": "Mobiilirakendus", + "mobile_app_download_onboarding_note": "Mobiilirakenduse allalaadimiseks kasuta järgnevaid valikuid", "model": "Mudel", "month": "Kuu", "monthly_title_text_date_format": "MMMM y", @@ -1360,6 +1389,8 @@ "my_albums": "Minu albumid", "name": "Nimi", "name_or_nickname": "Nimi või hüüdnimi", + "navigate": "Navigeeri", + "navigate_to_time": "Navigeeri aega", "network_requirement_photos_upload": "Kasuta fotode varundamiseks mobiilset andmesidet", "network_requirement_videos_upload": "Kasuta videote varundamiseks mobiilset andmesidet", "network_requirements": "Võrgu nõuded", @@ -1369,6 +1400,7 @@ "never": "Mitte kunagi", "new_album": "Uus album", "new_api_key": "Uus API võti", + "new_date_range": "Uus kuupäevavahemik", "new_password": "Uus parool", "new_person": "Uus isik", "new_pin_code": "Uus PIN-kood", @@ -1405,6 +1437,7 @@ "no_results_description": "Proovi sünonüümi või üldisemat märksõna", "no_shared_albums_message": "Lisa album, et fotosid ja videosid teistega jagada", "no_uploads_in_progress": "Üleslaadimisi käimas ei ole", + "not_available": "Pole saadaval", "not_in_any_album": "Pole üheski albumis", "not_selected": "Ei ole valitud", "note_apply_storage_label_to_previously_uploaded assets": "Märkus: Et rakendada talletussilt varem üleslaaditud üksustele, käivita", @@ -1418,6 +1451,9 @@ "notifications": "Teavitused", "notifications_setting_description": "Halda teavitusi", "oauth": "OAuth", + "obtainium_configurator": "Obtainiumi seadistamine", + "obtainium_configurator_instructions": "Androidi rakenduse otse GitHub'ist paigaldamiseks ja uuendamiseks kasuta Obtainiumit. Seadistamise lingi loomiseks lisa API võti ja vali rakenduse variant", + "ocr": "OCR", "official_immich_resources": "Ametlikud Immich'i ressursid", "offline": "Ühendus puudub", "offset": "Nihe", @@ -1522,10 +1558,14 @@ "play_memories": "Esita mälestused", "play_motion_photo": "Esita liikuv foto", "play_or_pause_video": "Esita või peata video", + "play_original_video": "Taasesita algne video", + "play_original_video_setting_description": "Eelista transkodeeritud video asemel algse video taasesitamist. Kui algne üksus ei ole ühilduv, võib taasesitamine ebaõnnestuda.", + "play_transcoded_video": "Taasesita transkodeeritud video", "please_auth_to_access": "Ligipääsemiseks palun autendi", "port": "Port", "preferences_settings_subtitle": "Halda rakenduse eelistusi", "preferences_settings_title": "Eelistused", + "preparing": "Ettevalmistamine", "preset": "Eelseadistus", "preview": "Eelvaade", "previous": "Eelmine", @@ -1538,13 +1578,9 @@ "privacy": "Privaatsus", "profile": "Profiil", "profile_drawer_app_logs": "Logid", - "profile_drawer_client_out_of_date_major": "Mobiilirakendus on aegunud. Palun uuenda uusimale suurele versioonile.", - "profile_drawer_client_out_of_date_minor": "Mobiilirakendus on aegunud. Palun uuenda uusimale väikesele versioonile.", "profile_drawer_client_server_up_to_date": "Klient ja server on uuendatud", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Kirjutuskaitserežiim sisse lülitatud. Väljumiseks puuduta pikalt avatari ikooni.", - "profile_drawer_server_out_of_date_major": "Server on aegunud. Palun uuenda uusimale suurele versioonile.", - "profile_drawer_server_out_of_date_minor": "Server on aegunud. Palun uuenda uusimale väikesele versioonile.", "profile_image_of_user": "Kasutaja {user} profiilipilt", "profile_picture_set": "Profiilipilt määratud.", "public_album": "Avalik album", @@ -1581,6 +1617,7 @@ "purchase_server_description_2": "Toetaja staatus", "purchase_server_title": "Server", "purchase_settings_server_activated": "Serveri tootevõtit haldab administraator", + "query_asset_id": "Päringu üksuse ID", "queue_status": "Järjekorras {count}/{total}", "rating": "Hinnang", "rating_clear": "Tühjenda hinnang", @@ -1590,6 +1627,7 @@ "read_changelog": "Vaata muudatuste ülevaadet", "readonly_mode_disabled": "Kirjutuskaitserežiim välja lülitatud", "readonly_mode_enabled": "Kirjutuskaitserežiim sisse lülitatud", + "ready_for_upload": "Valmis üleslaadimiseks", "reassign": "Määra uuesti", "reassigned_assets_to_existing_person": "{count, plural, one {# üksus} other {# üksust}} seostatud {name, select, null {olemasoleva isikuga} other {isikuga {name}}}", "reassigned_assets_to_new_person": "{count, plural, one {# üksus} other {# üksust}} seostatud uue isikuga", @@ -1659,6 +1697,7 @@ "reset_sqlite_confirmation": "Kas oled kindel, et soovid SQLite andmebaasi lähtestada? Andmete uuesti sünkroonimiseks pead välja ja jälle sisse logima", "reset_sqlite_success": "SQLite andmebaas edukalt lähtestatud", "reset_to_default": "Lähtesta", + "resolution": "Resolutsioon", "resolve_duplicates": "Lahenda duplikaadid", "resolved_all_duplicates": "Kõik duplikaadid lahendatud", "restore": "Taasta", @@ -1677,6 +1716,7 @@ "running": "Käimas", "save": "Salvesta", "save_to_gallery": "Salvesta galeriisse", + "saved": "Salvestatud", "saved_api_key": "API võti salvestatud", "saved_profile": "Profiil salvestatud", "saved_settings": "Seaded salvestatud", @@ -1693,6 +1733,9 @@ "search_by_description_example": "Matkapäev Sapas", "search_by_filename": "Otsi failinime või -laiendi järgi", "search_by_filename_example": "st. IMG_1234.JPG või PNG", + "search_by_ocr": "Otsi OCR-i abil", + "search_by_ocr_example": "Latte", + "search_camera_lens_model": "Otsi läätse mudelit...", "search_camera_make": "Otsi kaamera marki...", "search_camera_model": "Otsi kaamera mudelit...", "search_city": "Otsi linna...", @@ -1707,8 +1750,9 @@ "search_filter_filename": "Otsi failinime alusel", "search_filter_location": "Asukoht", "search_filter_location_title": "Vali asukoht", - "search_filter_media_type": "Meediumi tüüp", - "search_filter_media_type_title": "Vali meediumi tüüp", + "search_filter_media_type": "Üksuse tüüp", + "search_filter_media_type_title": "Vali üksuse tüüp", + "search_filter_ocr": "Otsi OCR-i abil", "search_filter_people_title": "Vali isikud", "search_for": "Otsi", "search_for_existing_person": "Otsi olemasolevat isikut", @@ -1771,6 +1815,7 @@ "server_online": "Server ühendatud", "server_privacy": "Serveri privaatsus", "server_stats": "Serveri statistika", + "server_update_available": "Serveri uuendus on saadaval", "server_version": "Serveri versioon", "set": "Määra", "set_as_album_cover": "Sea albumi kaanepildiks", @@ -1799,6 +1844,8 @@ "setting_notifications_subtitle": "Halda oma teavituste eelistusi", "setting_notifications_total_progress_subtitle": "Üldine üleslaadimise edenemine (üksuseid tehtud/kokku)", "setting_notifications_total_progress_title": "Kuva taustal varundamise üldist edenemist", + "setting_video_viewer_auto_play_subtitle": "Alusta videote avamisel automaatselt taasesitust", + "setting_video_viewer_auto_play_title": "Esita videod automaatselt", "setting_video_viewer_looping_title": "Taasesitus", "setting_video_viewer_original_video_subtitle": "Esita serverist video voogedastamisel originaal, isegi kui transkodeeritud video on saadaval. Võib põhjustada puhverdamist. Lokaalselt saadaolevad videod mängitakse originaalkvaliteediga sõltumata sellest seadest.", "setting_video_viewer_original_video_title": "Sunni originaalvideo", @@ -1978,6 +2025,7 @@ "theme_setting_three_stage_loading_title": "Luba kolmeastmeline laadimine", "they_will_be_merged_together": "Nad ühendatakse kokku", "third_party_resources": "Kolmanda osapoole ressursid", + "time": "Aeg", "time_based_memories": "Ajapõhised mälestused", "timeline": "Ajajoon", "timezone": "Ajavöönd", @@ -2010,6 +2058,7 @@ "troubleshoot": "Tõrkeotsing", "type": "Tüüp", "unable_to_change_pin_code": "PIN-koodi muutmine ebaõnnestus", + "unable_to_check_version": "Rakenduse või serveri versiooni kontrollimine ebaõnnestus", "unable_to_setup_pin_code": "PIN-koodi seadistamine ebaõnnestus", "unarchive": "Taasta arhiivist", "unarchive_action_prompt": "{count} eemaldatud arhiivist", diff --git a/i18n/eu.json b/i18n/eu.json index bb16f126d6..291a62a7a1 100644 --- a/i18n/eu.json +++ b/i18n/eu.json @@ -58,6 +58,7 @@ "image_format_description": "WebP ereduak JPEG baino fitxategi txikiagoak sortzen ditu, baina motelagoa da kodifikatzen.", "image_preview_title": "Aurreikusiaen Konfigurazioa", "image_quality": "Kalitatea", + "image_resolution": "Erresoluzioa", "image_settings": "Argazkien Konfigurazioa", "image_thumbnail_title": "Argazki Txikien Konfigurazioa", "job_created": "Zeregina sortuta", @@ -81,8 +82,11 @@ "metadata_faces_import_setting": "Gaitu aurpegien inportazioa", "metadata_settings": "Metadata Konfigurazioa", "metadata_settings_description": "Kudeatu metadaten konfigurazioa", - "migration_job": "Migrazio" + "migration_job": "Migrazio", + "oauth_settings": "OAuth", + "transcoding_acceleration_vaapi": "VAAPI" }, + "advanced": "Aurreratua", "advanced_settings_readonly_mode_title": "Irakurri-bakarrik Modua", "apply_count": "Ezarri ({count, number})", "assets_added_to_albums_count": "Gehituta {assetTotal, plural, one {# asset} other {# assets}} to {albumTotal, plural, one {# album} other {# albums}}", diff --git a/i18n/fa.json b/i18n/fa.json index 3b0be9a9b1..f8e47af9c1 100644 --- a/i18n/fa.json +++ b/i18n/fa.json @@ -13,6 +13,7 @@ "add_a_location": "افزودن یک مکان", "add_a_name": "افزودن نام", "add_a_title": "افزودن عنوان", + "add_birthday": "افزودن تاریخ تولد", "add_exclusion_pattern": "افزودن الگوی استثنا", "add_import_path": "افزودن مسیر ورودی", "add_location": "افزودن مکان", @@ -22,10 +23,13 @@ "add_photos": "افزودن عکس ها", "add_to": "افزودن به …", "add_to_album": "افزودن به آلبوم", + "add_to_album_bottom_sheet_added": "به آلبوم {album} اضافه شد", + "add_to_album_bottom_sheet_already_exists": "قبلا در آلبوم {album} موجود است", + "add_to_album_bottom_sheet_some_local_assets": "برخی از محتواهای محلی را نشد به آلبوم اضافه کرد", "add_to_shared_album": "افزودن به آلبوم اشتراکی", "added_to_archive": "به آرشیو اضافه شد", "added_to_favorites": "به علاقه مندی ها اضافه شد", - "added_to_favorites_count": "{count} تا اضافه شد به علاقه مندی ها", + "added_to_favorites_count": "{count, number} تا به علاقه مندی ها اضافه شد", "admin": { "add_exclusion_pattern_description": "الگوهای استثنا را اضافه کنید. پشتیبانی از گلابینگ با استفاده از *, ** و ? وجود دارد. برای نادیده گرفتن تمام فایل‌ها در هر دایرکتوری با نام \"Raw\"، از \"**/Raw/**\" استفاده کنید. برای نادیده گرفتن تمام فایل‌هایی که با \".tif\" پایان می‌یابند، از \"**/*.tif\" استفاده کنید. برای نادیده گرفتن یک مسیر مطلق، از \"/path/to/ignore/**\" استفاده کنید.", "authentication_settings": "تنظیمات احراز هویت", @@ -40,6 +44,7 @@ "confirm_email_below": "برای تأیید، \"{email}\" را در زیر تایپ کنید", "confirm_reprocess_all_faces": "آیا مطمئن هستید که می‌خواهید تمام چهره‌ها را مجددا پردازش کنید؟ این عمل باعث پاک شدن افراد مشخص شده نیز خواهد شد.", "confirm_user_password_reset": "آیا مطمئن هستید که می‌خواهید رمز عبور {user} را بازنشانی کنید؟", + "confirm_user_pin_code_reset": "آیا مطمئن هستید که می‌خواهید کد PIN ‏{user} را بازنشانی کنید؟", "disable_login": "غیرفعال کردن ورود", "duplicate_detection_job_description": "اجرای یادگیری ماشین بر روی فایل‌ها برای شناسایی تصاویر مشابه. این وابسته به جستجوی هوشمند است", "exclusion_pattern_description": "الگوهای استثنا به شما امکان می‌دهد هنگام اسکن کتابخانه خود فایل‌ها و پوشه‌ها را نادیده بگیرید . این مفید است اگر پوشه‌هایی دارید که فایل‌هایی را شامل می‌شوند که نمی‌خواهید وارد کنید، مانند فایل‌های RAW.", @@ -50,11 +55,21 @@ "failed_job_command": "دستور {command} برای کار: {job} ناموفق بود", "force_delete_user_warning": "هشدار: این عمل باعث حذف فوری کاربر و تمام فایل‌ها می‌شود. این عمل قابل بازگشت نیست و فایل‌ها قابل بازیابی نیستند.", "image_format_description": "فرمت WebP فایل‌های کوچکتری نسبت به JPEG ایجاد می‌کند، اما زمان کدگذاری آن کندتر است.", + "image_fullsize_description": "تصویر با اندازه کامل و بدون فراداده، مورد استفاده هنگام بزرگ‌نمایی", + "image_fullsize_enabled": "فعال‌سازی تولید تصویر با اندازه کامل", + "image_fullsize_enabled_description": "تولید تصویر با اندازه کامل برای فرمت‌های غیرسازگار با وب. هنگامی که گزینه «استفاده از پیش‌نمایش تعبیه‌شده» فعال باشد، پیش‌نمایش‌های تعبیه‌شده مستقیماً بدون تبدیل استفاده می‌شوند. این تنظیم بر فرمت‌های سازگار با وب مانند JPEG تأثیری ندارد.", + "image_fullsize_quality_description": "کیفیت تصویر با اندازه کامل از ۱ تا ۱۰۰. هرچه بالاتر باشد، کیفیت بهتر است، اما فایل‌های بزرگ‌تری ایجاد می‌کند.", + "image_fullsize_title": "تنظیمات تصویر با اندازه کامل", "image_prefer_embedded_preview": "ترجیحات پیش‌نمایش تعبیه‌شده", - "image_prefer_embedded_preview_setting_description": "استفاده از پیش‌نمایش داخلی در عکس‌های RAW به عنوان ورودی پردازش تصویر هنگامی که در دسترس باشد. این می‌تواند رنگ‌های دقیق‌تری را برای برخی تصاویر تولید کند، اما کیفیت پیش‌نمایش به دوربین بستگی دارد و ممکن است تصویر آثار فشرده‌سازی بیشتری داشته باشد.", + "image_prefer_embedded_preview_setting_description": "استفاده از پیش‌نمایش‌های تعبیه‌شده در عکس‌های RAW به‌عنوان ورودی برای پردازش تصویر، در صورت موجود بودن. این می‌تواند رنگ‌های دقیق‌تری برای برخی تصاویر ایجاد کند، اما کیفیت پیش‌نمایش به دوربین بستگی دارد و ممکن است تصویر دارای نویزهای فشرده‌سازی بیشتری باشد.", "image_prefer_wide_gamut": "ترجیحات گستره رنگی وسیع", "image_prefer_wide_gamut_setting_description": "برای تصاویر کوچک از فضای رنگی Display P3 استفاده کنید. این کار باعث حفظ زنده بودن رنگ‌ها در تصاویر با گستره رنگی وسیع می‌شود، اما ممکن است تصاویر در دستگاه‌های قدیمی با نسخه‌های قدیمی مرورگر به شکل متفاوتی نمایش داده شوند. تصاویر با فضای رنگی sRGB به همان حالت sRGB نگه داشته می‌شوند تا از تغییرات رنگی جلوگیری شود.", + "image_preview_description": "تصویر با اندازه متوسط و بدون فراداده، مورد استفاده هنگام مشاهده یک دارایی و برای یادگیری ماشین", + "image_preview_quality_description": "کیفیت پیش‌نمایش از ۱ تا ۱۰۰. هرچه بالاتر باشد، کیفیت بهتر است، اما فایل‌های بزرگ‌تری ایجاد می‌کند و ممکن است پاسخ‌گویی برنامه کاهش یابد. تنظیم مقدار پایین می‌تواند بر کیفیت یادگیری ماشین تأثیر بگذارد.", + "image_preview_title": "تنظیمات پیش‌نمایش", "image_quality": "کیفیت", + "image_resolution": "وضوح تصویر", + "image_resolution_description": "وضوح بالاتر می‌تواند جزئیات بیشتری را حفظ کند، اما تبدیل آن زمان بیشتری می‌برد، حجم فایل‌ها را افزایش می‌دهد و ممکن است پاسخ‌گویی برنامه را کاهش دهد.", "image_settings": "تنظیمات عکس", "image_settings_description": "مدیریت کیفیت و وضوح تصاویر تولید شده", "job_concurrency": "همزمانی {job}", @@ -408,7 +423,6 @@ "edit_people": "ویرایش افراد", "edit_title": "ویرایش عنوان", "edit_user": "ویرایش کاربر", - "edited": "ویرایش شد", "editor": "ویرایشگر", "email": "ایمیل", "empty_trash": "خالی کردن سطل زباله", diff --git a/i18n/fi.json b/i18n/fi.json index b3f1542906..ac2007fa3a 100644 --- a/i18n/fi.json +++ b/i18n/fi.json @@ -28,10 +28,12 @@ "add_to_album": "Lisää albumiin", "add_to_album_bottom_sheet_added": "Lisätty albumiin {album}", "add_to_album_bottom_sheet_already_exists": "Kohde on jo albumissa {album}", + "add_to_album_bottom_sheet_some_local_assets": "Joitakin osia paikallisesta sisällöstä ei pystytty lisäämään albumiin", "add_to_album_toggle": "Vaihda albumin {album} valintaa", "add_to_albums": "Lisää albumeihin", "add_to_albums_count": "Lisää albumeihin ({count})", "add_to_shared_album": "Lisää jaettuun albumiin", + "add_upload_to_stack": "Lisää kuvapinoon", "add_url": "Lisää URL", "added_to_archive": "Lisätty arkistoon", "added_to_favorites": "Lisätty suosikkeihin", @@ -39,7 +41,7 @@ "admin": { "add_exclusion_pattern_description": "Lisää mallit, jonka mukaan jätetään tiedostoja pois. Jokerimerkit *, ** ja ? ovat tuettuna. Jättääksesi pois kaikki tiedostot mistä tahansa löytyvästä kansiosta \"Raw\" käytä \"**/Raw/**\". Jättääksesi pois kaikki \". tif\" päätteiset tiedot, käytä \"**/*.tif\". Jättääksesi pois tarkan tiedostopolun, käytä \"/path/to/ignore/**\".", "admin_user": "Ylläpitäjä", - "asset_offline_description": "Ulkoista kirjaston resurssia ei enää löydy levyltä, ja se on siirretty roskakoriin. Jos tiedosto siirrettiin kirjaston sisällä, tarkista aikajanaltasi uusi vastaava resurssi. Palautaaksesi tämän resurssin, varmista, että alla oleva tiedostopolku on Immichin käytettävissä ja skannaa kirjasto uudelleen.", + "asset_offline_description": "Ulkoista kirjaston resurssia ei enää löydy levyltä, ja se on siirretty roskakoriin. Jos tiedosto siirrettiin kirjaston sisällä, tarkista aikajanaltasi uusi vastaava resurssi. Palauttaaksesi tämän resurssin, varmista, että alla oleva tiedostopolku on Immichin käytettävissä ja skannaa kirjasto uudelleen.", "authentication_settings": "Autentikointiasetukset", "authentication_settings_description": "Hallitse salasana-, OAuth- ja muut autentikoinnin asetukset", "authentication_settings_disable_all": "Haluatko varmasti poistaa kaikki kirjautumistavat käytöstä? Kirjautuminen on tämän jälkeen mahdotonta.", @@ -123,6 +125,13 @@ "logging_enable_description": "Ota lokikirjaus käyttöön", "logging_level_description": "Kun käytössä, mitä lokituksen tasoa käytetään.", "logging_settings": "Lokit", + "machine_learning_availability_checks": "Saatavuustarkastukset", + "machine_learning_availability_checks_description": "Automaattisesti havaitse ja suosi vapaita koneoppimisen palvelimia", + "machine_learning_availability_checks_enabled": "Laita päälle saatavuus tarkistukset", + "machine_learning_availability_checks_interval": "Tarkastusväli", + "machine_learning_availability_checks_interval_description": "Aikaväli millisekunneissa saavutettavuus tarkistuksille", + "machine_learning_availability_checks_timeout": "Pyynnön aikakatkaisu", + "machine_learning_availability_checks_timeout_description": "Aikakatkaisu aika millisekunneissa saatavuus tarkistuksille", "machine_learning_clip_model": "CLIP-malli", "machine_learning_clip_model_description": "Käytettävän CLIP-mallin nimi toimivien mallien listasta. Huomaa että sinun täytyy suorittaa \"Älykäs etsintä\"-työ uudelleen vaihdettuasi käytettävää mallia.", "machine_learning_duplicate_detection": "Kaksoiskappaleiden tunnistus", @@ -142,9 +151,21 @@ "machine_learning_max_recognition_distance": "Suurin kasvojen eroavaisuus", "machine_learning_max_recognition_distance_description": "Kahden kasvon suurin eroavaisuus, milloin ne vielä mielletään samaksi henkilöksi, välillä 0-2. Arvoa alentamalla voidaan ehkäistä kahden saman näköisen henkilön mieltäminen samaksi henkilöksi, kun taas korottamalla voidaan ehkäistä saman henkilön mieltäminen kahdeksi erilliseksi henkilöksi. Huomaa että on helpompaa yhdistää kaksi, kuin erottaa, joten suosi mahdollisimman matalaa arvoa.", "machine_learning_min_detection_score": "Tunnistuksen vähimmäistulos", - "machine_learning_min_detection_score_description": "Pienin kasvojen tunnistamisessa saatu vahvuusarvo välillä 0-1. Matalammalla arvolla havaitaan enemmän kascoja, mutta voi lisätä virhearvioiden määrää.", + "machine_learning_min_detection_score_description": "Pienin kasvojen tunnistamisessa saatu vahvuusarvo välillä 0-1. Matalammalla arvolla havaitaan enemmän kasvoja, mutta voi lisätä virhearvioiden määrää.", "machine_learning_min_recognized_faces": "Tunnistettujen kasvojen vähimmäismäärä", "machine_learning_min_recognized_faces_description": "Luotavan käyttäjän kasvojen vähimmäismäärä. Arvoa nostamalla kasvojentunnistuksen tarkkuus paranee, mutta todennäköisyys sille, että kasvoja ei osata yhdistää henkilöön kasvaa.", + "machine_learning_ocr": "Tekstintunnistus (OCR)", + "machine_learning_ocr_description": "Käytä koneoppimista tekstin tunnistamiseen kuvista", + "machine_learning_ocr_enabled": "Aktivoi OCR", + "machine_learning_ocr_enabled_description": "Jos asetus on pois päältä, kuvia ei prosessoida tekstin tunnistamiseksi.", + "machine_learning_ocr_max_resolution": "Maksimiresoluutio", + "machine_learning_ocr_max_resolution_description": "Tätä suuremmat esikatselukuvat tullaan pienentämään samassa kuvasuhteessa. Suuremmat arvot ovat tarkempia, mutta kestävät pidempään prosessoida ja käyttävät enemmän muistia.", + "machine_learning_ocr_min_detection_score": "Pienin paikannuksen pistemäärä", + "machine_learning_ocr_min_detection_score_description": "Pienin arvo tekstin paikannukselle varmuudelle välillä 0-1. Pienemmät arvot paikantavat enemmän tekstiä, mutta saattavat johtaa useampaan väärään positiiviseen.", + "machine_learning_ocr_min_recognition_score": "Pienin tunnistuksen pistemäärä", + "machine_learning_ocr_min_score_recognition_description": "Pienin arvo tekstin tunnistuksen varmuudelle välillä 0-1. Pienemmät arvot tunnistavat enemmän tekstiä, mutta saattavat johtaa useampaan väärään positiiviseen.", + "machine_learning_ocr_model": "OCR-malli", + "machine_learning_ocr_model_description": "Palvelinmallit ovat tarkempia kuin mobiilimallit, mutta prosessointi kestää pidempään ja käyttää enemmän muistia.", "machine_learning_settings": "Koneoppimisen asetukset", "machine_learning_settings_description": "Koneoppimisen ominaisuudet ja asetukset", "machine_learning_smart_search": "Älykäs etsintä", @@ -176,7 +197,7 @@ "metadata_settings": "Metatietoasetukset", "metadata_settings_description": "Hallitse metatietoja", "migration_job": "Migraatio", - "migration_job_description": "Migroi aineiston pikkukuvat ja kasvot uusimpaan kansiorakenteeseen", + "migration_job_description": "Migratoi aineiston pikkukuvat ja kasvot uusimpaan kansiorakenteeseen", "nightly_tasks_cluster_faces_setting_description": "Aja kasvojen tunnistus uusiin tunnistettuihin kasvoihin", "nightly_tasks_cluster_new_faces_setting": "Kokoa uudet kasvot", "nightly_tasks_database_cleanup_setting": "Tietokannan puhdistuksen tehtävät", @@ -196,12 +217,14 @@ "note_apply_storage_label_previous_assets": "Huom: Asettaaksesi nimikkeen aiemmin ladatulle aineistolle, aja", "note_cannot_be_changed_later": "Huom: Tätä ei voi enää myöhemmin vaihtaa!", "notification_email_from_address": "Lähettäjän osoite", - "notification_email_from_address_description": "Lähettäjän sähköpostiosoite. Esimerkiksi \"Immich-kuvapalvelin \". Varmista, että käytetystä osoiteesta on lupa lähettää sähköposteja.", + "notification_email_from_address_description": "Lähettäjän sähköpostiosoite. Esimerkiksi \"Immich-kuvapalvelin \". Varmista, että käytetystä osoitteesta on lupa lähettää sähköposteja.", "notification_email_host_description": "Sähköpostipalvelin (esim. smtp.immich.app)", "notification_email_ignore_certificate_errors": "Älä huomioi varmennevirheitä", "notification_email_ignore_certificate_errors_description": "Älä huomioi TLS-varmenteiden validointivirheitä (ei suositeltu)", "notification_email_password_description": "Sähköpostipalvelimen salasana", "notification_email_port_description": "Sähköpostipalvelimen portti (esim. 25, 465, tai 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "Käytä SMTPS:ää (SMTP over TLS)", "notification_email_sent_test_email_button": "Lähetä testaussähköposti ja tallenna", "notification_email_setting_description": "Sähköposti-ilmoitusten asetukset", "notification_email_test_email": "Lähetä testisähköposti", @@ -218,9 +241,9 @@ "oauth_button_text": "Painikkeen teksti", "oauth_client_secret_description": "Vaaditaan, jos OAuth-palveluntarjoaja ei tue PKCE:tä (Proof Key for Code Exchange)", "oauth_enable_description": "Kirjaudu käyttäen OAuthia", - "oauth_mobile_redirect_uri": "Mobiilin uudellenohjaus-URI", + "oauth_mobile_redirect_uri": "Mobiilin uudelleenohjaus-URI", "oauth_mobile_redirect_uri_override": "Ohita mobiilin uudelleenohjaus-URI", - "oauth_mobile_redirect_uri_override_description": "Ota käyttöön kun OAuth tarjoaja ei salli mobiili URI:a, kuten ''{callback}''", + "oauth_mobile_redirect_uri_override_description": "Ota käyttöön kun OAuth-tarjoaja ei salli mobiili-URI:a, kuten ''{callback}''", "oauth_role_claim": "Roolin vaatimus", "oauth_role_claim_description": "Salli pääkäyttäjän pääsyoikeus automaattisesti tämän vaatimuksen perusteella. Vaatimus voi sisältää, joko 'käyttäjän' tai 'pääkäyttäjän'.", "oauth_settings": "OAuth", @@ -234,6 +257,7 @@ "oauth_storage_quota_default_description": "Käytettävä kiintiön määrä gigatavuissa, kun väittämää ei ole annettu.", "oauth_timeout": "Pyynnön aikakatkaisu", "oauth_timeout_description": "Pyyntöjen aikakatkaisu millisekunteina", + "ocr_job_description": "Käytä koneoppimista tunnistamaan tekstiä kuvista", "password_enable_description": "Kirjaudu käyttäen sähköpostiosoitetta ja salasanaa", "password_settings": "Kirjaudu salasanalla", "password_settings_description": "Hallitse salasanakirjautumisen asetuksia", @@ -271,7 +295,7 @@ "storage_template_migration_info": "Tallennusmalli muuntaa kaikki tiedostopäätteet pieniksi kirjaimiksi. Mallipohjan muutokset koskevat vain uusia resursseja. Jos haluat käyttää mallipohjaa takautuvasti aiemmin ladattuihin resursseihin, suorita {job}.", "storage_template_migration_job": "Tallennustilan mallin muutostyö", "storage_template_more_details": "Saadaksesi lisätietoa tästä ominaisuudesta, katso Tallennustilan Mallit sekä mihin se vaikuttaa", - "storage_template_onboarding_description_v2": "Päälle kytkettynä, toiminto järjestestelee tiedostot automaattisesti käyttäjän määrittämän mallin mukaisesti. Lisätietoja dokumentaatiosta..", + "storage_template_onboarding_description_v2": "Päälle kytkettynä toiminto järjestelee tiedostot automaattisesti käyttäjän määrittämän mallin mukaisesti. Lisätietoja dokumentaatiosta..", "storage_template_path_length": "Arvioitu tiedostopolun pituusrajoitus: {length, number}/{limit, number}", "storage_template_settings": "Tallennustilan malli", "storage_template_settings_description": "Hallitse palvelimelle ladatun aineiston kansiorakennetta ja tiedostonimiä", @@ -294,17 +318,17 @@ "thumbnail_generation_job": "Luo pikkukuvat", "thumbnail_generation_job_description": "Generoi isot, pienet sekä sumeat pikkukuvat jokaisesta aineistosta, kuten myös henkilöistä", "transcoding_acceleration_api": "Kiihdytysrajapinta", - "transcoding_acceleration_api_description": "Rajapinta, jolla keskustellaan laittesi kanssa nopeuttaaksemme koodausta. Tämä asetus on paras mahdollinen: Mikäli ongelmia ilmenee, palataan käyttämään ohjelmistopohjaista koodausta. VP9 voi toimia tai ei, riippuen laitteistosi kokoonpanosta.", + "transcoding_acceleration_api_description": "Rajapinta, jolla keskustellaan laitteesi kanssa nopeuttaaksemme koodausta. Tämä asetus on paras mahdollinen: Mikäli ongelmia ilmenee, palataan käyttämään ohjelmistopohjaista koodausta. VP9 voi toimia tai ei, riippuen laitteistosi kokoonpanosta.", "transcoding_acceleration_nvenc": "NVENC (vaatii NVIDIA:n grafiikkasuorittimen)", "transcoding_acceleration_qsv": "Quick Sync (Vaatii vähintään gen7 Intel prosessorin)", "transcoding_acceleration_rkmpp": "RKMPP (vain Rockchip SOCt)", "transcoding_acceleration_vaapi": "VAAPI", "transcoding_accepted_audio_codecs": "Sallitut äänikoodekit", - "transcoding_accepted_audio_codecs_description": "Valitse mitä äänikoodekkeja ei tarvitse muuntaa. Käytetään vain tiettyjen koodauskäytäntöjen kanssa.", - "transcoding_accepted_containers": "Hyväksytyt kontit", - "transcoding_accepted_containers_description": "Valitse, mitä formaatteja ei tarvitse kääntää MP4- muotoon. Käytössä vain tietyille muunnos säännöille.", + "transcoding_accepted_audio_codecs_description": "Valitse, mitä äänikoodekkeja ei tarvitse muuntaa. Käytetään vain tiettyjen koodauskäytäntöjen kanssa.", + "transcoding_accepted_containers": "Sallitut säiliömuodot", + "transcoding_accepted_containers_description": "Valitse, mitä säiliömuotoja ei tarvitse muuntaa MP4-muotoon. Käytetään vain tiettyjen koodauskäytäntöjen kanssa.", "transcoding_accepted_video_codecs": "Sallitut videokoodekit", - "transcoding_accepted_video_codecs_description": "Valitse mitä videokoodekkeja ei tarvitse muuntaa. Käytetään vain tiettyjen koodauskäytäntöjen kanssa.", + "transcoding_accepted_video_codecs_description": "Valitse, mitä videokoodekkeja ei tarvitse muuntaa. Käytetään vain tiettyjen koodauskäytäntöjen kanssa.", "transcoding_advanced_options_description": "Asetukset, joita useimpien käyttäjien ei tulisi muuttaa", "transcoding_audio_codec": "Äänikoodekki", "transcoding_audio_codec_description": "Opus on paras laadultaan, mutta ei välttämättä ole yhteensopiva vanhempien laitteiden tai sovellusten kanssa.", @@ -324,14 +348,14 @@ "transcoding_max_b_frames": "B-kehysten enimmäismäärä", "transcoding_max_b_frames_description": "Korkeampi arvo parantaa pakkausta, mutta hidastaa enkoodausta. Ei välttämättä ole yhteensopiva vanhempien laitteiden kanssa. 0 poistaa B-kehykset käytöstä, -1 määrittää arvon automaattisesti.", "transcoding_max_bitrate": "Suurin bittinopeus", - "transcoding_max_bitrate_description": "Suurimman sallitun bittinopeuden asettaminen tekee tiedostojen koosta ennustettavampaa vaikka laatu voi hieman heiketä. 720p videossa tyypilliset arvot ovat 2600 kbit/s VP9:lle ja HEVC:lle, tai 4500 kbit/s H.254:lle. Jos 0, ei käytössä.", + "transcoding_max_bitrate_description": "Suurimman sallitun bittinopeuden asettaminen tekee tiedostojen koosta ennustettavampaa vaikka laatu voi hieman heiketä. 720p videossa tyypilliset arvot ovat 2600 kbit/s VP9:lle ja HEVC:lle, tai 4500 kbit/s H.254:lle. Jos 0, ei käytössä. Jos yksikköä ei ole annettu, oletus on k (kbit/s). Eli 5000, 5000k ja 5M ovat yhtä suuria.", "transcoding_max_keyframe_interval": "Suurin avainkehysten väli", "transcoding_max_keyframe_interval_description": "Asettaa avainkehysten välin maksimiarvon. Alempi arvo huonontaa pakkauksen tehoa, mutta parantaa hakuaikoja ja voi parantaa laatua nopealiikkeisissä kohtauksissa. 0 asettaa arvon automaattisesti.", "transcoding_optimal_description": "Videot, joiden resoluutio on korkeampi kuin kohteen, tai ei hyväksytyssä formaatissa", "transcoding_policy": "Transkoodauskäytäntö", "transcoding_policy_description": "Aseta milloin video transkoodataan", "transcoding_preferred_hardware_device": "Ensisijainen laite", - "transcoding_preferred_hardware_device_description": "On voimassa vain VAAPI ja QSV -määritteille. Asettaa laitteistokoodauksessa käytetyn DRI noodin.", + "transcoding_preferred_hardware_device_description": "On voimassa vain VAAPI- ja QSV-määritteille. Asettaa laitteistokoodauksessa käytetyn DRI-noodin.", "transcoding_preset_preset": "Esiasetus (-asetus)", "transcoding_preset_preset_description": "Pakkausnopeus. Hitaampi tuottaa pienempiä tiedostoja ja parantaa laatua, kun kohdistetaan tiettyyn bittinopeuteen. VP9 ei huomioi korkeampaa kuin 'faster'.", "transcoding_reference_frames": "Kehysviitteet", @@ -342,13 +366,13 @@ "transcoding_target_resolution": "Kohderesoluutio", "transcoding_target_resolution_description": "Korkeampi resoluutio on tarkempi, mutta kestää kauemmin enkoodata, vie enemmän tilaa ja voi hidastaa sovelluksen responsiivisuutta.", "transcoding_temporal_aq": "Väliaikainen AQ", - "transcoding_temporal_aq_description": "Vaikuttaa vain NVENC:lle. Parantaa laatua kohtauksissa, joissa on paljon yksityiskohtia ja vähän liikettä. Ei välttämättä ole yhteensopiva vanhempien laitteiden kanssa.", + "transcoding_temporal_aq_description": "Vaikuttaa vain NVENC:lle. Aikaperusteinen adaptiivinen kvantisointi parantaa laatua kohtauksissa, joissa on paljon yksityiskohtia ja vähän liikettä. Ei välttämättä ole yhteensopiva vanhempien laitteiden kanssa.", "transcoding_threads": "Säikeet", "transcoding_threads_description": "Korkeampi arvo nopeuttaa enkoodausta, mutta vie tilaa palvelimen muilta tehtäviltä. Tämä arvo ei tulisi olla suurempi mitä suorittimen ytimien määrä. Suurin mahdollinen käyttö, mikäli arvo on 0.", "transcoding_tone_mapping": "Sävykartoitus", "transcoding_tone_mapping_description": "Pyrkii säilömään HDR-kuvien ulkonäön, kun muunnetaan peruskuvaksi. Jokaisella algoritmilla on omat heikkoutensa värien, yksityiskohtien tai kirkkauksien kesken. Hable säilöö yksityiskohdat, Mobius värit ja Reinhard kirkkaudet.", "transcoding_transcode_policy": "Transkoodauskäytäntö", - "transcoding_transcode_policy_description": "Käytäntö miten video tulisi transkoodata. HDR videot transkoodataan aina, paitsi jos transkoodaus on poistettu käytöstä.", + "transcoding_transcode_policy_description": "Käytäntö, miten video tulisi transkoodata. HDR-videot transkoodataan aina, paitsi jos transkoodaus on poistettu käytöstä.", "transcoding_two_pass_encoding": "Two-pass enkoodaus", "transcoding_two_pass_encoding_setting_description": "Transkoodaa kahdessa vaiheessa tuottaaksesi paremmin koodattuja videoita. Kun maksimibittinopeus on käytössä (vaaditaan H.264- ja HEVC-koodaukselle), tämä tila käyttää bittinopeusaluetta, joka perustuu maksimibittinopeuteen ja ohittaa CRF. VP9 osalta CRF:ää voidaan käyttää, jos maksimibittinopeus on poistettu käytöstä.", "transcoding_video_codec": "Videokoodekki", @@ -393,11 +417,11 @@ "advanced_settings_prefer_remote_subtitle": "Jotkut laitteet ovat erittäin hitaita lataamaan esikatselukuvia paikallisista kohteista. Aktivoi tämä asetus käyttääksesi etäkuvia.", "advanced_settings_prefer_remote_title": "Suosi etäkuvia", "advanced_settings_proxy_headers_subtitle": "Määritä välityspalvelimen otsikot(proxy headers), jotka Immichin tulisi lähettää jokaisen verkkopyynnön mukana", - "advanced_settings_proxy_headers_title": "Välityspalvelimen otsikot", + "advanced_settings_proxy_headers_title": "Mukautetut välityspalvelimen otsikot [KOKEELLINEN]", "advanced_settings_readonly_mode_subtitle": "Aktivoi vain luku -tilan, jolloin valokuvia voi ainoastaan selata. Toiminnot kuten useiden kuvien valitseminen, jakaminen, siirtäminen toistolaitteelle ja poistaminen ovat pois käytöstä. Laita vain luku -tila päälle tai pois päältä päävalikon käyttäjäkuvakkeesta", "advanced_settings_readonly_mode_title": "Vain luku -tila", "advanced_settings_self_signed_ssl_subtitle": "Ohita SSL sertifikaattivarmennus palvelimen päätepisteellä. Vaaditaan self-signed -sertifikaateissa.", - "advanced_settings_self_signed_ssl_title": "Salli self-signed SSL -sertifikaatit", + "advanced_settings_self_signed_ssl_title": "Salli self-signed SSL -sertifikaatit [KOKEELLINEN]", "advanced_settings_sync_remote_deletions_subtitle": "Poista tai palauta kohde automaattisesti tällä laitteella, kun kyseinen toiminto suoritetaan verkossa", "advanced_settings_sync_remote_deletions_title": "Synkronoi etäpoistot [KOKEELLINEN]", "advanced_settings_tile_subtitle": "Edistyneen käyttäjän asetukset", @@ -423,11 +447,12 @@ "album_remove_user_confirmation": "Oletko varma että haluat poistaa {user}?", "album_search_not_found": "Haullasi ei löytynyt yhtään albumia", "album_share_no_users": "Näyttää että olet jakanut tämän albumin kaikkien kanssa, tai sinulla ei ole käyttäjiä joille jakaa.", + "album_summary": "Albumi tiivistelmä", "album_updated": "Albumi päivitetty", "album_updated_setting_description": "Saa sähköpostia kun jaetussa albumissa on uutta sisältöä", "album_user_left": "Poistuttiin albumista {album}", "album_user_removed": "{user} poistettu", - "album_viewer_appbar_delete_confirm": "Haluatko varmast poistaa tämän albumin tililtäsi?", + "album_viewer_appbar_delete_confirm": "Haluatko varmasti poistaa tämän albumin tililtäsi?", "album_viewer_appbar_share_err_delete": "Albumin poistaminen epäonnistui", "album_viewer_appbar_share_err_leave": "Albumista poistuminen epäonnistui", "album_viewer_appbar_share_err_remove": "Ongelmia kohteiden poistamisessa albumista", @@ -456,10 +481,14 @@ "api_key_description": "Tämä arvo näytetään vain kerran. Varmista, että olet kopioinut sen ennen kuin suljet ikkunan.", "api_key_empty": "API-avaimesi ei pitäisi olla tyhjä", "api_keys": "API-avaimet", + "app_architecture_variant": "Variantti (Arkkitehtuuri)", "app_bar_signout_dialog_content": "Haluatko varmasti kirjautua ulos?", "app_bar_signout_dialog_ok": "Kyllä", "app_bar_signout_dialog_title": "Kirjaudu ulos", + "app_download_links": "Sovelluksen latauslinkit", "app_settings": "Sovellusasetukset", + "app_stores": "Sovelluskaupat", + "app_update_available": "Sovellukseen on saatavilla päivitys", "appears_in": "Esiintyy albumeissa", "apply_count": "Aseta {count, number}", "archive": "Arkisto", @@ -473,7 +502,7 @@ "archived_count": "{count, plural, other {Arkistoitu #}}", "are_these_the_same_person": "Ovatko he sama henkilö?", "are_you_sure_to_do_this": "Haluatko varmasti tehdä tämän?", - "asset_action_delete_err_read_only": "Vain luku-tilassa olevia kohteita ei voitu poistaa, ohitetaan", + "asset_action_delete_err_read_only": "Vain luku -tilassa olevia kohteita ei voitu poistaa, ohitetaan", "asset_action_share_err_offline": "Verkottomassa tilassa olevia kohteita ei voitu noutaa, ohitetaan", "asset_added_to_album": "Lisätty albumiin", "asset_adding_to_album": "Lisätään albumiin…", @@ -494,6 +523,8 @@ "asset_restored_successfully": "Kohde palautettu onnistuneesti", "asset_skipped": "Ohitettu", "asset_skipped_in_trash": "Roskakorissa", + "asset_trashed": "Kohde poistettu", + "asset_troubleshoot": "Sisällön vian paikannus", "asset_uploaded": "Lähetetty", "asset_uploading": "Ladataan…", "asset_viewer_settings_subtitle": "Galleriakatseluohjelman asetusten hallinta", @@ -501,7 +532,7 @@ "assets": "Kohteet", "assets_added_count": "Lisätty {count, plural, one {# kohde} other {# kohdetta}}", "assets_added_to_album_count": "Albumiin lisätty {count, plural, one {# kohde} other {# kohdetta}}", - "assets_added_to_albums_count": "Lisätty {assetTotal, plural, one {# aineisto} other {# aaineistoa}} {albumTotal, plural, one {# albumiin} other {# albumeihin}}", + "assets_added_to_albums_count": "Lisätty {assetTotal, plural, one {# kohde} other {# kohdetta}} {albumTotal, plural, one {# albumiin} other {# albumiin}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {Kohdetta} other {Kohdetta}} ei voida lisätä albumiin", "assets_cannot_be_added_to_albums": "{count, plural, one {Aineisto} other {Aineistoa}} ei voi lisätä mihinkään albumiin", "assets_count": "{count, plural, one {# media} other {# mediaa}}", @@ -527,8 +558,10 @@ "autoplay_slideshow": "Toista diaesitys automaattisesti", "back": "Takaisin", "back_close_deselect": "Palaa, sulje tai poista valinnat", + "background_backup_running_error": "Tausta varmuuskopiointi on aktiivinen, ei voida aloittaa manuaalista varmuuskopiointia", "background_location_permission": "Taustasijainnin käyttöoikeus", "background_location_permission_content": "Jotta sovellus voi vaihtaa verkkoa taustalla toimiessaan, Immichillä on *aina* oltava pääsy tarkkaan sijaintiin, jotta se voi lukea Wi-Fi-verkon nimen", + "background_options": "Tausta valinnat", "backup": "Varmuuskopiointi", "backup_album_selection_page_albums_device": "Laitteen albumit ({count})", "backup_album_selection_page_albums_tap": "Napauta sisällyttääksesi, kaksoisnapauta jättääksesi pois", @@ -536,8 +569,10 @@ "backup_album_selection_page_select_albums": "Valitse albumit", "backup_album_selection_page_selection_info": "Valintatiedot", "backup_album_selection_page_total_assets": "Ainulaatuisia kohteita yhteensä", + "backup_albums_sync": "Varmuuskopioitujen albumeiden synkronointi", "backup_all": "Kaikki", "backup_background_service_backup_failed_message": "Kohteiden varmuuskopiointi epäonnistui. Yritetään uudelleen…", + "backup_background_service_complete_notification": "Kohteiden varmuuskopiointi valmis", "backup_background_service_connection_failed_message": "Palvelimeen ei saatu yhteyttä. Yritetään uudelleen…", "backup_background_service_current_upload_notification": "Lähetetään {filename}", "backup_background_service_default_notification": "Tarkistetaan uusia kohteita…", @@ -585,7 +620,8 @@ "backup_controller_page_turn_on": "Varmuuskopiointi päälle", "backup_controller_page_uploading_file_info": "Tiedostojen lähetystiedot", "backup_err_only_album": "Vähintään yhden albumin tulee olla valittuna", - "backup_info_card_assets": "kohteet", + "backup_error_sync_failed": "Synkronointi epäonnistui. Varmuuskopion käsittely ei onnistu.", + "backup_info_card_assets": "kohdetta", "backup_manual_cancelled": "Peruutettu", "backup_manual_in_progress": "Lähetys palvelimelle on jo käynnissä. Kokeile myöhemmin uudelleen", "backup_manual_success": "Onnistui", @@ -620,7 +656,7 @@ "cache_settings_statistics_thumbnail": "Esikatselukuvat", "cache_settings_statistics_title": "Välimuistin käyttö", "cache_settings_subtitle": "Hallitse Immich-mobiilisovelluksen välimuistin käyttöä", - "cache_settings_tile_subtitle": "Hallitse paikallista tallenustilaa", + "cache_settings_tile_subtitle": "Hallitse paikallista tallennustilaa", "cache_settings_tile_title": "Paikallinen tallennustila", "cache_settings_title": "Välimuistin asetukset", "camera": "Kamera", @@ -646,12 +682,16 @@ "change_password_description": "Tämä on joko ensimmäinen kertasi kun kirjaudut järjestelmään, tai salasanasi on pyydetty vaihtamaan. Määritä uusi salasana alle.", "change_password_form_confirm_password": "Vahvista salasana", "change_password_form_description": "Hei {name},\n\nTämä on joko ensimmäinen kerta, kun kirjaudut järjestelmään, tai sinulta on pyydetty salasanan vaihtoa. Ole hyvä ja syötä uusi salasana alle.", + "change_password_form_log_out": "Kirjaudu ulos kaikilta muilta laitteilta", + "change_password_form_log_out_description": "On suositeltavaa kirjautua ulos kaikilta muilta laitteilta", "change_password_form_new_password": "Uusi salasana", "change_password_form_password_mismatch": "Salasanat eivät täsmää", "change_password_form_reenter_new_password": "Uusi salasana uudelleen", "change_pin_code": "Vaihda PIN-koodi", "change_your_password": "Vaihda salasanasi", "changed_visibility_successfully": "Näkyvyys vaihdettu", + "charging": "Ladataan laitetta", + "charging_requirement_mobile_backup": "Varmuuskopiointi taustalla vaatii laitteen latautumista", "check_corrupt_asset_backup": "Vioittuneiden varmuuskopioiden tarkistaminen", "check_corrupt_asset_backup_button": "Suorita tarkistus", "check_corrupt_asset_backup_description": "Suorita tämä tarkistus vain Wi-Fi-yhteyden kautta ja vasta, kun kaikki kohteet on varmuuskopioitu. Toimenpide voi kestää muutamia minuutteja.", @@ -671,7 +711,7 @@ "client_cert_invalid_msg": "Virheellinen varmennetiedosto tai väärä salasana", "client_cert_remove_msg": "Asiakassertifikaatti on poistettu", "client_cert_subtitle": "Vain PKCS12 (.p12, .pfx) -muotoa tuetaan. Varmenteen tuonti/poisto on käytettävissä vain ennen sisäänkirjautumista", - "client_cert_title": "SSL-asiakassertifikaatti", + "client_cert_title": "SSL-asiakassertifikaatti [KOKEELLINEN]", "clockwise": "Myötäpäivään", "close": "Sulje", "collapse": "Supista", @@ -683,7 +723,6 @@ "comments_and_likes": "Kommentit ja tykkäykset", "comments_are_disabled": "Kommentointi ei käytössä", "common_create_new_album": "Luo uusi albumi", - "common_server_error": "Tarkista internet-yhteytesi. Varmista että palvelin on saavutettavissa ja sovellus-/palvelinversiot ovat yhteensopivia.", "completed": "Valmis", "confirm": "Vahvista", "confirm_admin_password": "Vahvista ylläpitäjän salasana", @@ -722,6 +761,7 @@ "create": "Luo", "create_album": "Luo albumi", "create_album_page_untitled": "Nimetön", + "create_api_key": "Luo API-avain", "create_library": "Luo uusi kirjasto", "create_link": "Luo linkki", "create_link_to_share": "Luo linkki jaettavaksi", @@ -738,6 +778,7 @@ "create_user": "Luo käyttäjä", "created": "Luotu", "created_at": "Luotu", + "creating_linked_albums": "Luodaan linkattuja albumeita...", "crop": "Rajaa", "curated_object_page_title": "Asiat", "current_device": "Nykyinen laite", @@ -750,6 +791,7 @@ "daily_title_text_date_year": "E, dd MMM, yyyy", "dark": "Tumma", "dark_theme": "Vaihda tumma teema", + "date": "Päivämäärä", "date_after": "Päivämäärän jälkeen", "date_and_time": "Päivämäärä ja aika", "date_before": "Päivä ennen", @@ -762,7 +804,7 @@ "deduplication_criteria_1": "Kuvan koko tavuina", "deduplication_criteria_2": "EXIF-datan määrä", "deduplication_info": "Deduplikaatiotieto", - "deduplication_info_description": "Jotta voimme automaattisesti esivalita aineistot ja poistaa duplikaatit suurina erinä, tarkastelemme:", + "deduplication_info_description": "Jotta voimme automaattisesti esivalita aineistot ja poistaa kaksoiskappaleet suurina erinä, tarkastelemme:", "default_locale": "Oletuskieliasetus", "default_locale_description": "Muotoile päivämäärät ja numerot selaimesi kielen mukaan", "delete": "Poista", @@ -837,7 +879,7 @@ "downloading_media": "Median lataaminen", "drop_files_to_upload": "Pudota tiedostot mihin tahansa ladataksesi ne", "duplicates": "Kaksoiskappaleet", - "duplicates_description": "Selvitä jokaisen kohdalla mitkä (jos yksikään) ovat kaksoiskappaleita", + "duplicates_description": "Selvitä jokaisen kohdalla mitkä (jos mitkään) ovat kaksoiskappaleita", "duration": "Kesto", "edit": "Muokkaa", "edit_album": "Muokkaa albumia", @@ -864,7 +906,6 @@ "edit_tag": "Muokkaa tunnistetta", "edit_title": "Muokkaa otsikkoa", "edit_user": "Muokkaa käyttäjää", - "edited": "Muokattu", "editor": "Muokkaaja", "editor_close_without_save_prompt": "Muutoksia ei tallenneta", "editor_close_without_save_title": "Suljetaanko editori?", @@ -887,7 +928,9 @@ "error": "Virhe", "error_change_sort_album": "Albumin lajittelujärjestyksen muuttaminen epäonnistui", "error_delete_face": "Virhe kasvojen poistamisessa kohteesta", + "error_getting_places": "Ongelma paikkojen haussa", "error_loading_image": "Kuvan lataus ei onnistunut", + "error_loading_partners": "Ongelma partnerin haussa: {error}", "error_saving_image": "Virhe: {error}", "error_tag_face_bounding_box": "Kasvojen merkitseminen epäonnistui – rajausruudun koordinaatteja ei löydy", "error_title": "Virhe - Jotain meni pieleen", @@ -1018,6 +1061,7 @@ "exif_bottom_sheet_description_error": "Kuvauksen muuttaminen epäonnistui", "exif_bottom_sheet_details": "TIEDOT", "exif_bottom_sheet_location": "SIJAINTI", + "exif_bottom_sheet_no_description": "Ei kuvausta", "exif_bottom_sheet_people": "IHMISET", "exif_bottom_sheet_person_add_person": "Lisää nimi", "exit_slideshow": "Poistu diaesityksestä", @@ -1047,14 +1091,16 @@ "failed_to_load_folder": "Kansion lataaminen epäonnistui", "favorite": "Suosikki", "favorite_action_prompt": "{count} lisätty suosikkeihin", - "favorite_or_unfavorite_photo": "Suosikki- tai ei-suosikkikuva", + "favorite_or_unfavorite_photo": "Lisää tai poista kuva suosikeista", "favorites": "Suosikit", "favorites_page_no_favorites": "Suosikkikohteita ei löytynyt", "feature_photo_updated": "Kansikuva ladattu", "features": "Ominaisuudet", + "features_in_development": "Kehityksessä olevat ominaisuudet", "features_setting_description": "Hallitse sovelluksen ominaisuuksia", "file_name": "Tiedoston nimi", "file_name_or_extension": "Tiedostonimi tai tiedostopääte", + "file_size": "Tiedostokoko", "filename": "Tiedostonimi", "filetype": "Tiedostotyyppi", "filter": "Suodatin", @@ -1079,6 +1125,8 @@ "go_back": "Palaa", "go_to_folder": "Mene kansioon", "go_to_search": "Siirry hakuun", + "gps": "GPS", + "gps_missing": "Ei GPS:ää", "grant_permission": "Myönnä lupa", "group_albums_by": "Ryhmitä albumi...", "group_country": "Ryhmitä maan mukaan", @@ -1096,7 +1144,6 @@ "header_settings_field_validator_msg": "Arvo ei voi olla tyhjä", "header_settings_header_name_input": "Otsikon nimi", "header_settings_header_value_input": "Otsikon arvo", - "headers_settings_tile_subtitle": "Määritä välityspalvelimen otsikot, jotka sovelluksen tulisi lähettää jokaisen verkkopyynnön mukana", "headers_settings_tile_title": "Mukautettu proxy headers", "hi_user": "Hei {name} ({email})", "hide_all_people": "Piilota kaikki henkilöt", @@ -1108,18 +1155,18 @@ "home_page_add_to_album_conflicts": "Lisätty {added} kohdetta albumiin {album}. {failed} kohdetta on jo albumissa.", "home_page_add_to_album_err_local": "Paikallisten kohteiden lisääminen albumeihin ei ole mahdollista, ohitetaan", "home_page_add_to_album_success": "Lisätty {added} kohdetta albumiin {album}.", - "home_page_album_err_partner": "Kumppanin kohteita ei voi vielä lisätä albumiin. Hypätään yli", + "home_page_album_err_partner": "Kumppanin kohteita ei voi vielä lisätä albumiin, ohitetaan", "home_page_archive_err_local": "Paikallisten kohteiden arkistointi ei ole mahdollista, ohitetaan", - "home_page_archive_err_partner": "Kumppanin kohteita ei voi arkistoida. Hypätään yli", + "home_page_archive_err_partner": "Kumppanin kohteita ei voi arkistoida, ohitetaan", "home_page_building_timeline": "Rakennetaan aikajanaa", - "home_page_delete_err_partner": "Kumppanin kohteita ei voi poistaa.Hypätään yli", + "home_page_delete_err_partner": "Kumppanin kohteita ei voi poistaa, ohitetaan", "home_page_delete_remote_err_local": "Paikallisia kohteita etäkohdevalintojen joukossa, ohitetaan", "home_page_favorite_err_local": "Paikallisten kohteiden lisääminen suosikkeihin ei ole mahdollista, ohitetaan", - "home_page_favorite_err_partner": "Kumppanin kohteita ei voi vielä merkitä suosikiksi. Hypätään yli", + "home_page_favorite_err_partner": "Kumppanin kohteita ei voi vielä merkitä suosikiksi, ohitetaan", "home_page_first_time_notice": "Jos käytät sovellusta ensimmäistä kertaa, muista valita varmuuskopioitavat albumi(t), jotta aikajanalla voi olla kuvia ja videoita", "home_page_locked_error_local": "Paikallisten kohteiden siirto lukittuun kansioon ei onnistu, ohitetaan", "home_page_locked_error_partner": "Kumppanin kohteita ei voi siirtää lukittuun kansioon, ohitetaan", - "home_page_share_err_local": "Paikallisia kohteita ei voitu jakaa linkkien avulla. Hypätään yli", + "home_page_share_err_local": "Paikallisia kohteita ei voitu jakaa linkkien avulla, ohitetaan", "home_page_upload_err_limit": "Voit lähettää palvelimelle enintään 30 kohdetta kerrallaan, ohitetaan", "host": "Isäntä", "hour": "Tunti", @@ -1147,7 +1194,7 @@ "immich_web_interface": "Immich-verkkokäyttöliittymä", "import_from_json": "Tuo JSON-tiedostosta", "import_path": "Tuontipolku", - "in_albums": "{count, plural, one {# Albumissa} other {# albumissa}}", + "in_albums": "{count, plural, one {# albumissa} other {# albumissa}}", "in_archive": "Arkistossa", "include_archived": "Sisällytä arkistoidut", "include_shared_albums": "Sisällytä jaetut albumit", @@ -1156,10 +1203,10 @@ "individual_shares": "Yksittäiset jaot", "info": "Lisätietoja", "interval": { - "day_at_onepm": "Joka päivä klo 13:00", + "day_at_onepm": "Joka päivä klo 13.00", "hours": "Joka {hours, plural, one {tunti} other {{hours, number} tuntia}}", "night_at_midnight": "Joka yö keskiyöllä", - "night_at_twoam": "Joka yö klo 02:00" + "night_at_twoam": "Joka yö klo 2.00" }, "invalid_date": "Virheellinen päivämäärä", "invalid_date_format": "Virheellinen päivämäärämuoto", @@ -1214,8 +1261,10 @@ "local": "Paikallinen", "local_asset_cast_failed": "Kohdetta, joka ei ole ladattuna palvelimelle, ei voida striimata", "local_assets": "Paikalliset kohteet", + "local_media_summary": "Paikallisen median yhteenveto", "local_network": "Lähiverkko", "local_network_sheet_info": "Sovellus muodostaa yhteyden palvelimeen tämän URL-osoitteen kautta, kun käytetään määritettyä Wi-Fi-verkkoa", + "location": "Sijainti", "location_permission": "Sijainnin käyttöoikeus", "location_permission_content": "Automaattisen vaihtotoiminnon käyttämiseksi Immich tarvitsee tarkan sijainnin käyttöoikeuden, jotta se voi lukea nykyisen Wi-Fi-verkon nimen", "location_picker_choose_on_map": "Valitse kartalta", @@ -1225,6 +1274,7 @@ "location_picker_longitude_hint": "Syötä pituusaste", "lock": "Lukitse", "locked_folder": "Lukittu kansio", + "log_detail_title": "Lokin yksityiskohtaisuus", "log_out": "Kirjaudu ulos", "log_out_all_devices": "Kirjaudu ulos kaikilta laitteilta", "logged_in_as": "Kirjautunut käyttäjänä {user}", @@ -1255,6 +1305,7 @@ "login_password_changed_success": "Salasan päivitetty onnistuneesti", "logout_all_device_confirmation": "Haluatko varmasti kirjautua ulos kaikilta laitteilta?", "logout_this_device_confirmation": "Haluatko varmasti kirjautua ulos näiltä laitteilta?", + "logs": "Loki", "longitude": "Pituusaste", "look": "Tyyli", "loop_videos": "Toista videot uudelleen", @@ -1262,6 +1313,7 @@ "main_branch_warning": "Käytät kehitysversiota; suosittelemme vahvasti käyttämään julkaisuversiota!", "main_menu": "Päävalikko", "make": "Valmistaja", + "manage_geolocation": "Muokkaa sijaintia", "manage_shared_links": "Hallitse jaettuja linkkejä", "manage_sharing_with_partners": "Hallitse jakamista kumppaneille", "manage_the_app_settings": "Hallitse sovelluksen asetuksia", @@ -1296,6 +1348,7 @@ "mark_as_read": "Merkitse luetuksi", "marked_all_as_read": "Merkitty kaikki luetuiksi", "matches": "Osumia", + "matching_assets": "Vastaava sisältö", "media_type": "Median tyyppi", "memories": "Muistoja", "memories_all_caught_up": "Kaikki ajan tasalla", @@ -1312,10 +1365,12 @@ "merge_people_prompt": "Haluatko yhdistää nämä henkilöt? Tätä valintaa ei voi peruuttaa.", "merge_people_successfully": "Henkilöt yhdistetty", "merged_people_count": "{count, plural, one {# Henkilö} other {# henkilöä}} yhdistetty", - "minimize": "PIenennä", + "minimize": "Pienennä", "minute": "Minuutti", "minutes": "Minuutit", "missing": "Puuttuvat", + "mobile_app": "Mobiilisovellus", + "mobile_app_download_onboarding_note": "Lataa mobiilisovellus käyttämällä seuraavia vaihtoehtoja", "model": "Malli", "month": "Kuukauden mukaan", "monthly_title_text_date_format": "MMMM y", @@ -1329,23 +1384,28 @@ "moved_to_library": "Siirretty {count, plural, one {# kohde} other {# kohdetta}} kirjastoon", "moved_to_trash": "Siirretty roskakoriin", "multiselect_grid_edit_date_time_err_read_only": "Vain luku -tilassa olevien kohteiden päivämäärää ei voitu muokata, ohitetaan", - "multiselect_grid_edit_gps_err_read_only": "Vain luku-tilassa olevien kohteiden sijantitietoja ei voitu muokata, ohitetaan", + "multiselect_grid_edit_gps_err_read_only": "Vain luku -tilassa olevien kohteiden sijantitietoja ei voitu muokata, ohitetaan", "mute_memories": "Mykistä muistot", "my_albums": "Omat albumit", "name": "Nimi", "name_or_nickname": "Nimi tai lempinimi", + "navigate": "Navigoi", + "navigate_to_time": "Navigoi aikaan", "network_requirement_photos_upload": "Käytä mobiiliverkkoa kuvien varmuuskopioimiseksi", "network_requirement_videos_upload": "Käytä mobiiliverkkoa videoiden varmuuskopioimiseksi", + "network_requirements": "Verkkovaatimukset", "network_requirements_updated": "Verkkovaatimukset muuttuivat, nollataan varmuuskopiointijono", "networking_settings": "Verkko", "networking_subtitle": "Hallitse palvelinasetuksia", "never": "ei koskaan", "new_album": "Uusi Albumi", "new_api_key": "Uusi API-avain", + "new_date_range": "Uusi aikaväli", "new_password": "Uusi salasana", "new_person": "Uusi henkilö", "new_pin_code": "Uusi PIN-koodi", "new_pin_code_subtitle": "Tämä on ensimmäinen kerta, kun käytät lukittua kansiota. Luo PIN-koodi päästäksesi tähän sisältöön turvallisesti", + "new_timeline": "Uusi aikajana", "new_user_created": "Uusi käyttäjä lisätty", "new_version_available": "UUSI VERSIO SAATAVILLA", "newest_first": "Uusin ensin", @@ -1356,23 +1416,28 @@ "no_albums_with_name_yet": "Näyttää siltä, ettei sinulla ole yhtään tämän nimistä albumia.", "no_albums_yet": "Näyttää siltä, ettei sinulla ole vielä yhtään albumia.", "no_archived_assets_message": "Arkistoi kuvia ja videoita piilottaaksesi ne kuvat näkymästä", - "no_assets_message": "NAPAUTA LATAAKSESI ENSIMMÄISEN KUVASI", + "no_assets_message": "NAPAUTA LADATAKSESI ENSIMMÄINEN KUVASI", "no_assets_to_show": "Ei näytettäviä kohteita", "no_cast_devices_found": "Cast-laitteita ei löytynyt", + "no_checksum_local": "Ei tarkistussummaa - paikallista sisältöä ei voida hakea", + "no_checksum_remote": "Ei tarkistussummaa - etänä olevaa sisältöä ei voida hakea", "no_duplicates_found": "Kaksoiskappaleita ei löytynyt.", "no_exif_info_available": "EXIF-tietoa ei saatavilla", "no_explore_results_message": "Lataa lisää kuvia tutkiaksesi kokoelmaasi.", "no_favorites_message": "Lisää suosikkeja löytääksesi nopeasti parhaat kuvasi ja videosi", "no_libraries_message": "Luo ulkoinen kirjasto nähdäksesi valokuvasi ja videot", + "no_local_assets_found": "Paikallista sisältöä ei löytynyt tällä tarkistussummalla", "no_locked_photos_message": "Kuvat ja videot lukitussa kansiossa ovat piilotettuja, eivätkä ne näy selatessasi tai etsiessäsi kirjastoasi.", "no_name": "Ei nimeä", "no_notifications": "Ei ilmoituksia", "no_people_found": "Ei vastaavia henkilöitä", "no_places": "Ei paikkoja", + "no_remote_assets_found": "Etänä olevaa sisältöä ei löytynyt tällä tarkistussummalla", "no_results": "Ei tuloksia", "no_results_description": "Kokeile synonyymiä tai yleisempää avainsanaa", "no_shared_albums_message": "Luo albumi, jotta voit jakaa kuvia ja videoita toisille", "no_uploads_in_progress": "Ei käynnissä olevia latauksia", + "not_available": "N/A", "not_in_any_album": "Ei yhdessäkään albumissa", "not_selected": "Ei valittu", "note_apply_storage_label_to_previously_uploaded assets": "Huom: Jotta voit soveltaa tallennustunnistetta aiemmin ladattuihin kohteisiin, suorita", @@ -1386,6 +1451,9 @@ "notifications": "Ilmoitukset", "notifications_setting_description": "Hallitse ilmoituksia", "oauth": "OAuth", + "obtainium_configurator": "Obtainium-määritystyökalu", + "obtainium_configurator_instructions": "Käytä Obtainiumia asentaaksesi ja päivittääksesi Android-sovelluksen suoraan Immichin GitHubin julkaisukanavasta. Luo API-avain ja valitse variantti luodaksesi Obtainium-määrityslinkin", + "ocr": "OCR (Tekstintunnistus)", "official_immich_resources": "Viralliset Immich-resurssit", "offline": "Offline", "offset": "Ero", @@ -1407,6 +1475,8 @@ "open_the_search_filters": "Avaa hakusuodattimet", "options": "Vaihtoehdot", "or": "tai", + "organize_into_albums": "Järjestä albumeihin", + "organize_into_albums_description": "Siirrä olemassa olevat kuvat albumeihin käyttäen nykyisiä synkronointiasetuksia", "organize_your_library": "Järjestele kirjastosi", "original": "alkuperäinen", "other": "Muut", @@ -1452,7 +1522,7 @@ "permanent_deletion_warning_setting_description": "Näytä varoitus, kun poistat kohteita pysyvästi", "permanently_delete": "Poista pysyvästi", "permanently_delete_assets_count": "Poista pysyvästi {count, plural, one {kohde} other {kohteita}}", - "permanently_delete_assets_prompt": "Haluatko varmasti poistaa pysyvästi {count, plural, one {tämän kohteen?} other {nämä # kohteet?}} Tämä poistaa myös {count, plural, one {sen} other {ne}} kaikista albumeista.", + "permanently_delete_assets_prompt": "Haluatko varmasti poistaa pysyvästi {count, plural, one {tämän kohteen?} other {nämä # kohteet?}} Tämä poistaa {count, plural, one {sen} other {ne}} myös kaikista albumeista.", "permanently_deleted_asset": "Media poistettu pysyvästi", "permanently_deleted_assets_count": "{count, plural, one {# media} other {# mediaa}} poistettu pysyvästi", "permission": "Käyttöoikeus", @@ -1488,10 +1558,14 @@ "play_memories": "Toista muistot", "play_motion_photo": "Toista Liikekuva", "play_or_pause_video": "Toista tai keskeytä video", + "play_original_video": "Toista alkuperäinen video", + "play_original_video_setting_description": "Suosi alkuperäisten videoiden toistoa transkoodattujen videoiden sijaan. Jos alkuperäinen tiedosto ei ole yhteensopiva, se ei välttämättä toistu oikein.", + "play_transcoded_video": "Toista transkoodattu video", "please_auth_to_access": "Ole hyvä ja kirjaudu sisään", "port": "Portti", "preferences_settings_subtitle": "Hallitse sovelluksen asetuksia", "preferences_settings_title": "Asetukset", + "preparing": "Valmistellaan", "preset": "Asetus", "preview": "Esikatselu", "previous": "Edellinen", @@ -1504,12 +1578,9 @@ "privacy": "Tietosuoja", "profile": "Profiili", "profile_drawer_app_logs": "Lokit", - "profile_drawer_client_out_of_date_major": "Sovelluksen mobiiliversio on vanhentunut. Päivitä viimeisimpään merkittävään versioon.", - "profile_drawer_client_out_of_date_minor": "Sovelluksen mobiiliversio on vanhentunut. Päivitä viimeisimpään versioon.", "profile_drawer_client_server_up_to_date": "Asiakasohjelma ja palvelin ovat ajan tasalla", "profile_drawer_github": "GitHub", - "profile_drawer_server_out_of_date_major": "Palvelimen ohjelmistoversio on vanhentunut. Päivitä viimeisimpään merkittävään versioon.", - "profile_drawer_server_out_of_date_minor": "Palvelimen ohjelmistoversio on vanhentunut. Päivitä viimeisimpään versioon.", + "profile_drawer_readonly_mode": "Muokkaus on estetty. Paina käyttäjäkuvaketta pitkään palataksesi muokkaustilaan.", "profile_image_of_user": "Käyttäjän {user} profiilikuva", "profile_picture_set": "Profiilikuva asetettu.", "public_album": "Julkinen albumi", @@ -1546,6 +1617,7 @@ "purchase_server_description_2": "Tukijan tila", "purchase_server_title": "Palvelin", "purchase_settings_server_activated": "Palvelimen tuoteavainta hallinnoi ylläpitäjä", + "query_asset_id": "Kysy sisällön ID:tä", "queue_status": "Jonossa {count}/{total}", "rating": "Tähtiarvostelu", "rating_clear": "Tyhjennä arvostelu", @@ -1553,6 +1625,9 @@ "rating_description": "Näytä EXIF-arvosana lisätietopaneelissa", "reaction_options": "Reaktioasetukset", "read_changelog": "Lue muutosloki", + "readonly_mode_disabled": "Muokkaustila päällä", + "readonly_mode_enabled": "Muokkaustila pois päältä", + "ready_for_upload": "Valmis lähetystä varten", "reassign": "Määritä uudelleen", "reassigned_assets_to_existing_person": "Uudelleen määritetty {count, plural, one {# kohde} other {# kohdetta}} {name, select, null {olemassa olevalle henkilölle} other {{name}}}", "reassigned_assets_to_new_person": "Määritetty {count, plural, one {# media} other {# mediaa}} uudelle henkilölle", @@ -1577,6 +1652,7 @@ "regenerating_thumbnails": "Regeneroidaan pikkukuvia", "remote": "Etä", "remote_assets": "Etäkohteet", + "remote_media_summary": "Yhteenveto etänä olevasta mediasta", "remove": "Poista", "remove_assets_album_confirmation": "Haluatko varmasti poistaa {count, plural, one {# median} other {# mediaa}} albumista?", "remove_assets_shared_link_confirmation": "Haluatko varmasti poistaa {count, plural, one {# median} other {# mediaa}} tästä jakolinkistä?", @@ -1621,6 +1697,7 @@ "reset_sqlite_confirmation": "Haluatko varmasti nollata SQLite tietokannan? Sinun tulee kirjautua sovelluksesta ulos ja takaisin sisään uudelleensynkronoidaksesi datan", "reset_sqlite_success": "SQLite Tietokanta nollattu onnistuneesti", "reset_to_default": "Palauta oletusasetukset", + "resolution": "Resoluutio", "resolve_duplicates": "Ratkaise kaksoiskappaleet", "resolved_all_duplicates": "Kaikki kaksoiskappaleet selvitetty", "restore": "Palauta", @@ -1629,6 +1706,7 @@ "restore_user": "Palauta käyttäjä", "restored_asset": "Palautettu media", "resume": "Jatka", + "resume_paused_jobs": "Jatka {count, plural, one {# paused job} other {# paused jobs}}", "retry_upload": "Yritä latausta uudelleen", "review_duplicates": "Tarkastele kaksoiskappaleita", "review_large_files": "Tarkista suuret tiedostot", @@ -1654,6 +1732,9 @@ "search_by_description_example": "Vaelluspäivä Sapassa", "search_by_filename": "Hae tiedostonimen tai -päätteen mukaan", "search_by_filename_example": "esim. IMG_1234.JPG tai PNG", + "search_by_ocr": "Etsi tekstintunnistuksella (OCR)", + "search_by_ocr_example": "Latte", + "search_camera_lens_model": "Etsi linssin mallia...", "search_camera_make": "Etsi kameramerkkiä...", "search_camera_model": "Etsi kameramallia...", "search_city": "Etsi kaupunkia...", @@ -1670,6 +1751,7 @@ "search_filter_location_title": "Valitse sijainti", "search_filter_media_type": "Mediatyyppi", "search_filter_media_type_title": "Valitse mediatyyppi", + "search_filter_ocr": "Hae tekstintunnistuksella (OCR)", "search_filter_people_title": "Valitse ihmiset", "search_for": "Hae", "search_for_existing_person": "Etsi olemassa olevaa henkilöä", @@ -1693,7 +1775,7 @@ "search_places": "Etsi paikkoja", "search_rating": "Hae luokituksen mukaan...", "search_result_page_new_search_hint": "Uusi haku", - "search_settings": "Hakuasetukset", + "search_settings": "Etsi asetuksia", "search_state": "Etsi maakuntaa...", "search_suggestion_list_smart_search_hint_1": "Älykäs haku on oletuksena käytössä. Käytä metatietojen etsimiseen syntaksia ", "search_suggestion_list_smart_search_hint_2": "m:hakusana", @@ -1705,7 +1787,7 @@ "second": "Toinen", "see_all_people": "Näytä kaikki henkilöt", "select": "Valitse", - "select_album_cover": "Valitse albmin kansi", + "select_album_cover": "Valitse albumin kansi", "select_all": "Valitse kaikki", "select_all_duplicates": "Valitse kaikki kaksoiskappaleet", "select_all_in": "Valitse kaikki {group}", @@ -1722,6 +1804,7 @@ "select_user_for_sharing_page_err_album": "Albumin luonti epäonnistui", "selected": "Valittu", "selected_count": "{count, plural, other {# valittu}}", + "selected_gps_coordinates": "Valitut GPS-koordinaatit", "send_message": "Lähetä viesti", "send_welcome_email": "Lähetä tervetuloviesti", "server_endpoint": "Palvelinosoite", @@ -1731,6 +1814,7 @@ "server_online": "Palvelin Online-tilassa", "server_privacy": "Palvelimen tietosuoja", "server_stats": "Palvelimen tilastot", + "server_update_available": "Palvelimeen on saatavilla päivitys", "server_version": "Palvelimen versio", "set": "Aseta", "set_as_album_cover": "Aseta albumin kanneksi", @@ -1759,11 +1843,13 @@ "setting_notifications_subtitle": "Ilmoitusasetusten määrittely", "setting_notifications_total_progress_subtitle": "Lähetyksen yleinen edistyminen (kohteita lähetetty/yhteensä)", "setting_notifications_total_progress_title": "Näytä taustavarmuuskopioinnin kokonaisedistyminen", + "setting_video_viewer_auto_play_subtitle": "Aloita videoiden toistaminen automaattisesti kun ne avataan", + "setting_video_viewer_auto_play_title": "Toista videoita automaattisesti", "setting_video_viewer_looping_title": "Silmukkatoisto", "setting_video_viewer_original_video_subtitle": "Kun toistat videota palvelimelta, toista alkuperäinen, vaikka transkoodattu versio olisi saatavilla. Tämä voi johtaa puskurointiin. Paikalliset videot toistetaan aina alkuperäislaadulla.", "setting_video_viewer_original_video_title": "Pakota alkuperäinen video", "settings": "Asetukset", - "settings_require_restart": "Käynnistä Immich uudelleen ottaaksesti tämän asetuksen käyttöön", + "settings_require_restart": "Käynnistä Immich uudelleen ottaaksesi tämä asetus käyttöön", "settings_saved": "Asetukset tallennettu", "setup_pin_code": "Määritä PIN-koodi", "share": "Jaa", @@ -1829,7 +1915,7 @@ "sharing_sidebar_description": "Näytä jakamislinkki sivupalkissa", "sharing_silver_appbar_create_shared_album": "Luo jaettu albumi", "sharing_silver_appbar_share_partner": "Jaa kumppanille", - "shift_to_permanent_delete": "Paina ⇧ poistaaksesi median pysyvästi", + "shift_to_permanent_delete": "Paina ⇧ poistaaksesi media pysyvästi", "show_album_options": "Näytä albumin asetukset", "show_albums": "Näytä albumit", "show_all_people": "Näytä kaikki henkilöt", @@ -1850,6 +1936,7 @@ "show_slideshow_transition": "Näytä diaesitys siirtymä", "show_supporter_badge": "Kannattajan merkki", "show_supporter_badge_description": "Näytä kannattajan merkki", + "show_text_search_menu": "Näytä tekstihakuvalikko", "shuffle": "Sekoita", "sidebar": "Sivupalkki", "sidebar_display_description": "Näytä linkki näkymään sivupalkissa", @@ -1880,6 +1967,7 @@ "stacktrace": "Vianetsintätiedot", "start": "Aloita", "start_date": "Alkupäivä", + "start_date_before_end_date": "Aloituspäivämäärän pitää olla ennen lopetuspäivämäärää", "state": "Maakunta", "status": "Tila", "stop_casting": "Lopeta suoratoisto", @@ -1904,6 +1992,8 @@ "sync_albums_manual_subtitle": "Synkronoi kaikki ladatut videot ja valokuvat valittuihin varmuuskopioalbumeihin", "sync_local": "Synkronoi paikallinen", "sync_remote": "Synkronoi etä", + "sync_status": "Synkronoinnin status", + "sync_status_subtitle": "Näytä ja hallinnoi synkronointijärjestelmää", "sync_upload_album_setting_subtitle": "Luo ja lataa valokuvasi ja videosi valittuihin albumeihin Immichissä", "tag": "Tunniste", "tag_assets": "Lisää tunnisteita", @@ -1934,6 +2024,7 @@ "theme_setting_three_stage_loading_title": "Ota kolmivaiheinen lataus käyttöön", "they_will_be_merged_together": "Nämä tullaan yhdistämään", "third_party_resources": "Kolmannen osapuolen resurssit", + "time": "Aika", "time_based_memories": "Aikaan perustuvat muistot", "timeline": "Aikajana", "timezone": "Aikavyöhyke", @@ -1941,7 +2032,9 @@ "to_change_password": "Vaihda salasana", "to_favorite": "Aseta suosikiksi", "to_login": "Kirjaudu sisään", + "to_multi_select": "usean valitsemiseksi", "to_parent": "Siirry vanhempaan", + "to_select": "valitsemiseksi", "to_trash": "Roskakoriin", "toggle_settings": "Määritä asetukset", "total": "Yhteensä", @@ -1949,7 +2042,7 @@ "trash": "Roskakori", "trash_action_prompt": "{count} siirretty roskakoriin", "trash_all": "Vie kaikki roskakoriin", - "trash_count": "Roskakori {count, number}", + "trash_count": "Vie {count, number} roskakoriin", "trash_delete_asset": "Poista / vie roskakoriin", "trash_emptied": "Roskakori tyhjennetty", "trash_no_results_message": "Roskakorissa olevat kuvat ja videot näytetään täällä.", @@ -1961,8 +2054,10 @@ "trash_page_select_assets_btn": "Valitse kohteet", "trash_page_title": "Roskakori ({count})", "trashed_items_will_be_permanently_deleted_after": "Roskakorin kohteet poistetaan pysyvästi {days, plural, one {# päivän} other {# päivän}} päästä.", + "troubleshoot": "Vianetsintä", "type": "Tyyppi", "unable_to_change_pin_code": "PIN-koodin vaihtaminen epäonnistui", + "unable_to_check_version": "Sovelluksen tai palvelimen versiota ei voitu tarkistaa", "unable_to_setup_pin_code": "PIN-koodin määrittäminen epäonnistui", "unarchive": "Palauta arkistosta", "unarchive_action_prompt": "{count} poistettu arkistosta", @@ -1991,6 +2086,7 @@ "unstacked_assets_count": "Poistettu pinosta {count, plural, one {# kohde} other {# kohdetta}}", "untagged": "Ilman tunnistetta", "up_next": "Seuraavaksi", + "update_location_action_prompt": "Päivitä {count} kohteen sijaintia:", "updated_at": "Päivitetty", "updated_password": "Salasana päivitetty", "upload": "Siirrä palvelimelle", @@ -2057,6 +2153,7 @@ "view_next_asset": "Näytä seuraava", "view_previous_asset": "Näytä edellinen", "view_qr_code": "Näytä QR-koodi", + "view_similar_photos": "Näytä samankaltaiset kuvat", "view_stack": "Näytä pinona", "view_user": "Näytä käyttäjä", "viewer_remove_from_stack": "Poista pinosta", @@ -2075,5 +2172,6 @@ "yes": "Kyllä", "you_dont_have_any_shared_links": "Sinulla ei ole jaettuja linkkejä", "your_wifi_name": "Wi-Fi-verkkosi nimi", - "zoom_image": "Zoomaa kuvaa" + "zoom_image": "Zoomaa kuvaa", + "zoom_to_bounds": "Zoomaa reunoihin" } diff --git a/i18n/fil.json b/i18n/fil.json index 12e6086064..23257ce2fd 100644 --- a/i18n/fil.json +++ b/i18n/fil.json @@ -25,6 +25,8 @@ "add_to_album": "Idagdag sa album", "add_to_album_bottom_sheet_added": "Naidagdag sa {album}", "add_to_album_bottom_sheet_already_exists": "Nasa {album} na", + "add_to_albums": "Idagdag sa mga album", + "add_to_albums_count": "Idagdag sa mga album ({count})", "add_to_shared_album": "Idagdag sa shared album", "add_url": "Magdagdag ng URL", "added_to_archive": "Naidagdag sa archive", @@ -67,16 +69,18 @@ }, "album_user_left": "Umalis sa {album}", "all_albums": "Lahat ng albums", + "all_people": "Lahat ng tao", + "all_videos": "Lahat ng video", "api_key_description": "Isang beses lamang na ipapakita itong value. Siguraduhin na ikopya itong value bago iclose ang window na ito.", "are_these_the_same_person": "Itong tao na ito ay parehas?", "asset_adding_to_album": "Dinadagdag sa album...", "asset_filename_is_offline": "Offline ang asset {filename}", "asset_uploading": "Ina-upload...", + "create_album_page_untitled": "Walang pamagat", "documentation": "Dokumentasyion", "done": "Tapos na", "download": "I-download", "edit": "I-edit", - "edited": "Inedit", "editor_close_without_save_title": "Isara ang editor?", "explore": "I-explore", "export": "I-export", diff --git a/i18n/fr.json b/i18n/fr.json index c0a76c69fe..789afffc38 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -28,10 +28,12 @@ "add_to_album": "Ajouter à l'album", "add_to_album_bottom_sheet_added": "Ajouté à {album}", "add_to_album_bottom_sheet_already_exists": "Déjà dans {album}", + "add_to_album_bottom_sheet_some_local_assets": "Certains médias n'ont pas pu être ajoutés à l'album", "add_to_album_toggle": "Basculer la sélection pour {album}", "add_to_albums": "Ajouter aux albums", "add_to_albums_count": "Ajouter aux albums ({count})", "add_to_shared_album": "Ajouter à l'album partagé", + "add_upload_to_stack": "Ajouter les éléments téléversés à la pile", "add_url": "Ajouter l'URL", "added_to_archive": "Ajouté à l'archive", "added_to_favorites": "Ajouté aux favoris", @@ -118,7 +120,7 @@ "library_settings_description": "Gestion des paramètres des bibliothèques externes", "library_tasks_description": "Scanner les bibliothèques externes pour les nouveaux et/ou les éléments modifiés", "library_watching_enable_description": "Surveiller les modifications de fichiers dans les bibliothèques externes", - "library_watching_settings": "Surveillance de bibliothèque (EXPÉRIMENTAL)", + "library_watching_settings": "Surveillance de bibliothèque [EXPÉRIMENTAL]", "library_watching_settings_description": "Surveiller automatiquement les fichiers modifiés", "logging_enable_description": "Activer la journalisation", "logging_level_description": "Niveau de journalisation lorsque cette option est activée.", @@ -152,6 +154,18 @@ "machine_learning_min_detection_score_description": "Score de confiance minimal pour qu'un visage soit détecté, allant de 0 à 1. Des valeurs plus basses détecteront plus de visages mais peuvent entraîner des faux positifs.", "machine_learning_min_recognized_faces": "Nombre minimal de visages reconnus", "machine_learning_min_recognized_faces_description": "Nombre minimal de visages reconnus pour qu'une personne soit créée. Augmenter cette valeur rend la reconnaissance faciale plus précise au détriment d'augmenter la chance qu'un visage ne soit pas attribué à une personne.", + "machine_learning_ocr": "OCR", + "machine_learning_ocr_description": "Utiliser l'apprentissage automatique pour reconnaître le texte dans les images", + "machine_learning_ocr_enabled": "Activer la reconnaissance de caractères", + "machine_learning_ocr_enabled_description": "Si désactivé, la reconnaissance de texte ne s'appliquera pas aux images.", + "machine_learning_ocr_max_resolution": "Résolution maximale", + "machine_learning_ocr_max_resolution_description": "Les prévisualisations au-dessus de cette résolution seront retaillées en conservant leur ratio. Des valeurs plus grandes sont plus précises, mais sont plus lentes et utilisent plus de mémoire.", + "machine_learning_ocr_min_detection_score": "Score minimum de détection", + "machine_learning_ocr_min_detection_score_description": "Score de confiance minimum pour la détection du textew entre 0 et 1. Des valeurs faibles permettront de reconnaître davantage de texte mais peuvent entraîner des faux positifs.", + "machine_learning_ocr_min_recognition_score": "Score de reconnaissance minimum", + "machine_learning_ocr_min_score_recognition_description": "Score de confiance minimum pour la reconnaissance du texte, entre 0 et 1. Des valeurs faible permettront de reconnaître davantage de texte, mais peuvent entraîner des faux positifs.", + "machine_learning_ocr_model": "Modèle de Reconnaissance Optique de Caractères", + "machine_learning_ocr_model_description": "Les modèles du serveur sont plus précis que les modèles mobiles, mais ils sont plus lents et utilisent plus de mémoire.", "machine_learning_settings": "Paramètres d'apprentissage automatique", "machine_learning_settings_description": "Gérer les fonctionnalités et les paramètres d'apprentissage automatique", "machine_learning_smart_search": "Recherche intelligente", @@ -209,6 +223,8 @@ "notification_email_ignore_certificate_errors_description": "Ignorer les erreurs de validation du certificat TLS (non recommandé)", "notification_email_password_description": "Mot de passe à utiliser lors de l'authentification avec le serveur de messagerie", "notification_email_port_description": "Port du serveur de messagerie (par exemple 25, 465 ou 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "Utilise SMTPS (SMTP via TLS)", "notification_email_sent_test_email_button": "Envoyer un courriel de test et enregistrer", "notification_email_setting_description": "Paramètres pour l'envoi de notifications par courriel", "notification_email_test_email": "Envoyer un courriel de test", @@ -241,6 +257,7 @@ "oauth_storage_quota_default_description": "Quota en Gio à utiliser lorsqu'aucune valeur n'est précisée.", "oauth_timeout": "Expiration de la durée de la requête", "oauth_timeout_description": "Délai d'expiration des requêtes en millisecondes", + "ocr_job_description": "Utiliser un modèle d'apprentissage automatique pour reconnaitre le texte dans les images", "password_enable_description": "Connexion avec courriel et mot de passe", "password_settings": "Connexion par mot de passe", "password_settings_description": "Gérer les paramètres de connexion par mot de passe", @@ -303,7 +320,7 @@ "transcoding_acceleration_api": "API d'accélération", "transcoding_acceleration_api_description": "Il s'agit de l'API qui interagira avec votre appareil pour accélérer le transcodage. Ce paramètre fait au mieux : il basculera vers le transcodage logiciel en cas d'échec. Le codec vidéo VP9 peut fonctionner ou non selon votre matériel.", "transcoding_acceleration_nvenc": "NVENC (nécessite un GPU NVIDIA)", - "transcoding_acceleration_qsv": "Quick Sync (nécessite un processeur Intel de 7ème génération ou plus)", + "transcoding_acceleration_qsv": "Quick Sync (nécessite un processeur Intel de 7ème génération ou supérieur)", "transcoding_acceleration_rkmpp": "RKMPP (uniquement sur les SOCs Rockchip)", "transcoding_acceleration_vaapi": "VAAPI", "transcoding_accepted_audio_codecs": "Codecs audio acceptés", @@ -331,7 +348,7 @@ "transcoding_max_b_frames": "Nombre maximum de trames B", "transcoding_max_b_frames_description": "Des valeurs plus élevées améliorent l'efficacité de la compression, mais ralentissent l'encodage. Elles peuvent ne pas être compatibles avec l'accélération matérielle sur les anciens appareils. Une valeur de 0 désactive les trames B, tandis qu'une valeur de -1 définit automatiquement ce paramètre.", "transcoding_max_bitrate": "Débit binaire maximal", - "transcoding_max_bitrate_description": "Définir un débit binaire maximal peut résulter en des fichiers de taille plus prédictible, au prix d'une légère perte en qualité. En 720p, les valeurs sont 2600 kbit/s pour du VP9 ou du HEVC ou 4500 kbit/s pour du H.264. Désactivé si le débit binaire est à 0.", + "transcoding_max_bitrate_description": "Définir un débit binaire maximal peut rendre la taille des fichiers plus prévisible, au prix d’une légère perte de qualité. En 720p, les valeurs typiques sont de 2600 kbit/s pour du VP9 ou du HEVC, ou de 4500 kbit/s pour du H.264. Désactivé si le débit binaire est fixé à 0. Lorsqu’aucune unité n’est spécifiée, k (pour kbit/s) est supposée ; ainsi, 5000, 5000k et 5M (pour Mbit/s) sont équivalents.", "transcoding_max_keyframe_interval": "Intervalle maximal entre les images clés", "transcoding_max_keyframe_interval_description": "Définit la distance maximale de trames entre les images clés. Les valeurs plus basses diminuent l'efficacité de la compression, mais améliorent les temps de recherche et peuvent améliorer la qualité dans les scènes avec des mouvements rapides. Une valeur de 0 définit automatiquement ce paramètre.", "transcoding_optimal_description": "Les vidéos dont la résolution est supérieure à celle attendue ou celles qui ne sont pas dans un format accepté", @@ -349,7 +366,7 @@ "transcoding_target_resolution": "Résolution cible", "transcoding_target_resolution_description": "Des résolutions plus élevées peuvent préserver plus de détails, mais prennent plus de temps à encoder, ont de plus grandes tailles de fichiers, et peuvent réduire la réactivité de l'application.", "transcoding_temporal_aq": "Quantification adaptative temporelle (temporal AQ)", - "transcoding_temporal_aq_description": "S'applique uniquement à NVENC. Améliore la qualité des scènes riches en détails et à faible mouvement. Peut ne pas être compatible avec les anciens appareils.", + "transcoding_temporal_aq_description": "S'applique uniquement à NVENC. La quantification adaptative temporelle améliore la qualité des scènes riches en détails et à faible mouvement. Peut ne pas être compatible avec les anciens appareils.", "transcoding_threads": "Processus", "transcoding_threads_description": "Une valeur plus élevée entraîne un encodage plus rapide, mais laisse moins de place au serveur pour traiter d'autres tâches pendant son activité. Cette valeur ne doit pas être supérieure au nombre de cœurs de CPU. Une valeur égale à 0 maximise l'utilisation.", "transcoding_tone_mapping": "Mappage tonal", @@ -400,11 +417,11 @@ "advanced_settings_prefer_remote_subtitle": "Certains appareils sont très lents à charger des miniatures à partir de ressources locales. Activez ce paramètre pour charger des images externes à la place.", "advanced_settings_prefer_remote_title": "Préférer les images externes", "advanced_settings_proxy_headers_subtitle": "Ajoutez des en-têtes personnalisés à chaque requête réseau", - "advanced_settings_proxy_headers_title": "En-têtes de proxy", + "advanced_settings_proxy_headers_title": "En-têtes de proxy personnalisés [EXPÉRIMENTAL]", "advanced_settings_readonly_mode_subtitle": "Active le mode lecture seule, où les photos peuvent seulement être visualisées, et les actions comme les sélections multiples, le partage, la diffusion, la suppression sont désactivées. Activer/désactiver la lecture seule via l'image de l'utilisateur depuis l'écran d'accueil", "advanced_settings_readonly_mode_title": "Mode lecture seule", "advanced_settings_self_signed_ssl_subtitle": "Permet d'ignorer la vérification du certificat SSL pour le point d'accès du serveur. Requis pour les certificats auto-signés.", - "advanced_settings_self_signed_ssl_title": "Autoriser les certificats SSL auto-signés", + "advanced_settings_self_signed_ssl_title": "Autoriser les certificats SSL auto-signés [EXPÉRIMENTAL]", "advanced_settings_sync_remote_deletions_subtitle": "Supprimer ou restaurer automatiquement un média sur cet appareil lorsqu'une action a été faite sur le web", "advanced_settings_sync_remote_deletions_title": "Synchroniser les suppressions depuis le serveur [EXPÉRIMENTAL]", "advanced_settings_tile_subtitle": "Paramètres d'utilisateur avancés", @@ -464,10 +481,14 @@ "api_key_description": "Cette valeur ne sera affichée qu'une seule fois. Assurez-vous de la copier avant de fermer la fenêtre.", "api_key_empty": "Le nom de votre clé API ne doit pas être vide", "api_keys": "Clés d'API", + "app_architecture_variant": "Variante (Architecture)", "app_bar_signout_dialog_content": "Êtes-vous sûr(e) de vouloir vous déconnecter ?", "app_bar_signout_dialog_ok": "Oui", "app_bar_signout_dialog_title": "Se déconnecter", + "app_download_links": "Liens de téléchargement de l'appli", "app_settings": "Paramètres de l'application", + "app_stores": "Magasins d'applications", + "app_update_available": "Une mise à jour est disponible", "appears_in": "Apparaît dans", "apply_count": "Appliquer ({count, number})", "archive": "Archive", @@ -551,6 +572,7 @@ "backup_albums_sync": "Sauvegarde de la synchronisation des albums", "backup_all": "Tout", "backup_background_service_backup_failed_message": "Échec de la sauvegarde des médias. Nouvelle tentative…", + "backup_background_service_complete_notification": "Sauvegarde du média terminée", "backup_background_service_connection_failed_message": "Impossible de se connecter au serveur. Nouvelle tentative…", "backup_background_service_current_upload_notification": "Envoi de {filename}", "backup_background_service_default_notification": "Recherche de nouveaux médias…", @@ -598,7 +620,8 @@ "backup_controller_page_turn_on": "Activer la sauvegarde au premier plan", "backup_controller_page_uploading_file_info": "Envoi des informations du fichier", "backup_err_only_album": "Impossible de retirer le seul album", - "backup_info_card_assets": "éléments", + "backup_error_sync_failed": "Échec de synchronisation.", + "backup_info_card_assets": "médias", "backup_manual_cancelled": "Annulé", "backup_manual_in_progress": "Envoi déjà en cours. Réessayez plus tard", "backup_manual_success": "Succès", @@ -659,6 +682,8 @@ "change_password_description": "C'est la première fois que vous vous connectez ou une demande a été faite pour changer votre mot de passe. Veuillez entrer le nouveau mot de passe ci-dessous.", "change_password_form_confirm_password": "Confirmez le mot de passe", "change_password_form_description": "Bonjour {name},\n\nC'est la première fois que vous vous connectez au système ou vous avez demandé de changer votre mot de passe. Veuillez saisir le nouveau mot de passe ci-dessous.", + "change_password_form_log_out": "Déconnecter tous les autres appareils", + "change_password_form_log_out_description": "Il est recommandé de déconnecter tous les autres appareils", "change_password_form_new_password": "Nouveau mot de passe", "change_password_form_password_mismatch": "Les mots de passe ne correspondent pas", "change_password_form_reenter_new_password": "Saisissez à nouveau le nouveau mot de passe", @@ -686,7 +711,7 @@ "client_cert_invalid_msg": "Fichier de certificat invalide ou mot de passe incorrect", "client_cert_remove_msg": "Certificat supprimé", "client_cert_subtitle": "Prend en charge uniquement le format PKCS12 (.p12, .pfx). L'importation/suppression de certificats n'est possible qu'avant la connexion", - "client_cert_title": "Certificat SSL", + "client_cert_title": "Certificat SSL [EXPÉRIMENTAL]", "clockwise": "Sens horaire", "close": "Fermer", "collapse": "Réduire", @@ -698,7 +723,6 @@ "comments_and_likes": "Commentaires et \"J'aime\"", "comments_are_disabled": "Les commentaires sont désactivés", "common_create_new_album": "Créer un nouvel album", - "common_server_error": "Veuillez vérifier votre connexion réseau, vous assurer que le serveur est accessible et que les versions de l'application et du serveur sont compatibles.", "completed": "Complété", "confirm": "Confirmez", "confirm_admin_password": "Confirmez le mot de passe Admin", @@ -737,6 +761,7 @@ "create": "Créer", "create_album": "Créer un album", "create_album_page_untitled": "Sans titre", + "create_api_key": "Créer une clé d'API", "create_library": "Créer une bibliothèque", "create_link": "Créer le lien", "create_link_to_share": "Créer un lien pour partager", @@ -766,6 +791,7 @@ "daily_title_text_date_year": "E, dd MMM, yyyy", "dark": "Sombre", "dark_theme": "Activer le thème sombre", + "date": "Date", "date_after": "Date après", "date_and_time": "Date et heure", "date_before": "Date avant", @@ -880,7 +906,6 @@ "edit_tag": "Modifier l'étiquette", "edit_title": "Modifier le titre", "edit_user": "Modifier l'utilisateur", - "edited": "Modifié", "editor": "Editeur", "editor_close_without_save_prompt": "Les changements ne seront pas enregistrés", "editor_close_without_save_title": "Fermer l'éditeur ?", @@ -892,7 +917,7 @@ "empty_trash": "Vider la corbeille", "empty_trash_confirmation": "Êtes-vous sûr de vouloir vider la corbeille ? Cela supprimera définitivement de Immich tous les médias qu'elle contient.\nVous ne pouvez pas annuler cette action !", "enable": "Active", - "enable_backup": "Activer la sauvegarde", + "enable_backup": "Sauvegarde", "enable_biometric_auth_description": "Entrez votre code PIN pour activer l'authentification biométrique", "enabled": "Activé", "end_date": "Date de fin", @@ -1036,6 +1061,7 @@ "exif_bottom_sheet_description_error": "Erreur de mise à jour de la description", "exif_bottom_sheet_details": "DÉTAILS", "exif_bottom_sheet_location": "LOCALISATION", + "exif_bottom_sheet_no_description": "Aucune description", "exif_bottom_sheet_people": "PERSONNES", "exif_bottom_sheet_person_add_person": "Ajouter un nom", "exit_slideshow": "Quitter le diaporama", @@ -1074,6 +1100,7 @@ "features_setting_description": "Gérer les fonctionnalités de l'application", "file_name": "Nom du fichier", "file_name_or_extension": "Nom du fichier ou extension", + "file_size": "Taille du fichier", "filename": "Nom du fichier", "filetype": "Type de fichier", "filter": "Filtres", @@ -1117,7 +1144,6 @@ "header_settings_field_validator_msg": "Cette valeur ne peut pas être vide", "header_settings_header_name_input": "Nom de l'en-tête", "header_settings_header_value_input": "Valeur de l'en-tête", - "headers_settings_tile_subtitle": "Définir les en-têtes de proxy que l'application doit envoyer avec chaque requête réseau", "headers_settings_tile_title": "En-têtes de proxy personnalisés", "hi_user": "Bonjour {name} ({email})", "hide_all_people": "Cacher toutes les personnes", @@ -1238,6 +1264,7 @@ "local_media_summary": "Résumé du média local", "local_network": "Réseau local", "local_network_sheet_info": "L'application va se connecter au serveur via cette URL quand l'appareil est connecté à ce réseau Wi-Fi", + "location": "Localisation", "location_permission": "Autorisation de localisation", "location_permission_content": "Afin de pouvoir changer d'adresse automatiquement, Immich doit avoir accès à la localisation précise, afin d'accéder au nom du réseau wifi utilisé", "location_picker_choose_on_map": "Sélectionner sur la carte", @@ -1342,6 +1369,8 @@ "minute": "Minute", "minutes": "Minutes", "missing": "Manquant", + "mobile_app": "Appli mobile", + "mobile_app_download_onboarding_note": "Téléchargez l'application mobile compagnon via les options suivantes", "model": "Modèle", "month": "Mois", "monthly_title_text_date_format": "MMMM y", @@ -1360,6 +1389,8 @@ "my_albums": "Mes albums", "name": "Nom", "name_or_nickname": "Nom ou surnom", + "navigate": "Naviguer vers", + "navigate_to_time": "Naviguer vers Date/Heure", "network_requirement_photos_upload": "Utiliser les données mobile pour sauvegarder les photos", "network_requirement_videos_upload": "Utiliser les données mobile pour sauvegarder les vidéos", "network_requirements": "Prérequis réseau", @@ -1369,6 +1400,7 @@ "never": "Jamais", "new_album": "Nouvel Album", "new_api_key": "Nouvelle clé API", + "new_date_range": "Nouvelle plage de date", "new_password": "Nouveau mot de passe", "new_person": "Nouvelle personne", "new_pin_code": "Nouveau code PIN", @@ -1419,6 +1451,9 @@ "notifications": "Notifications", "notifications_setting_description": "Gérer les notifications", "oauth": "OAuth", + "obtainium_configurator": "Configuration pour Obtainium", + "obtainium_configurator_instructions": "Utilisez Obtainium pour installer et mettre à jour l'application Android directement depuis la version d'Immich sur Github. Créer une clé d'API et sélectionner une variante pour créer votre lien de configuration pour Obtainium", + "ocr": "Reconnaissance Optique de Caractères", "official_immich_resources": "Ressources Immich officielles", "offline": "Hors ligne", "offset": "Décalage", @@ -1523,6 +1558,9 @@ "play_memories": "Lancer les souvenirs", "play_motion_photo": "Jouer la photo animée", "play_or_pause_video": "Lancer ou mettre en pause la vidéo", + "play_original_video": "Lire la vidéo originale", + "play_original_video_setting_description": "Préférer la lecture des vidéos originales plutôt que les vidéos transcodées. Si le média original n'est pas compatible, il pourrait ne pas être lu correctement.", + "play_transcoded_video": "Lire la vidéo transcodée", "please_auth_to_access": "Merci de vous authentifier pour accéder", "port": "Port", "preferences_settings_subtitle": "Gérer les préférences de l'application", @@ -1540,13 +1578,9 @@ "privacy": "Vie privée", "profile": "Profil", "profile_drawer_app_logs": "Journaux", - "profile_drawer_client_out_of_date_major": "L'application mobile est obsolète. Veuillez effectuer la mise à jour vers la dernière version majeure.", - "profile_drawer_client_out_of_date_minor": "L'application mobile est obsolète. Veuillez effectuer la mise à jour vers la dernière version mineure.", "profile_drawer_client_server_up_to_date": "Le client et le serveur sont à jour", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Mode lecture seule activé. Faites un appui long sur l'image de l'utilisateur pour quitter.", - "profile_drawer_server_out_of_date_major": "Le serveur est obsolète. Veuillez mettre à jour vers la dernière version majeure.", - "profile_drawer_server_out_of_date_minor": "Le serveur est obsolète. Veuillez mettre à jour vers la dernière version mineure.", "profile_image_of_user": "Image de profil de {user}", "profile_picture_set": "Photo de profil définie.", "public_album": "Album public", @@ -1663,6 +1697,7 @@ "reset_sqlite_confirmation": "Êtes-vous certain de vouloir réinitialiser la base de données SQLite ? Vous devrez vous déconnecter puis vous reconnecter à nouveau pour resynchroniser les données", "reset_sqlite_success": "La base de données SQLite à été réinitialisé avec succès", "reset_to_default": "Rétablir les valeurs par défaut", + "resolution": "Résolution", "resolve_duplicates": "Résoudre les doublons", "resolved_all_duplicates": "Résolution de tous les doublons", "restore": "Restaurer", @@ -1681,6 +1716,7 @@ "running": "En cours", "save": "Sauvegarder", "save_to_gallery": "Enregistrer", + "saved": "Sauvegardé", "saved_api_key": "Clé API sauvegardée", "saved_profile": "Profil enregistré", "saved_settings": "Paramètres enregistrés", @@ -1697,6 +1733,9 @@ "search_by_description_example": "Randonnée à Sapa", "search_by_filename": "Rechercher par nom du fichier ou extension", "search_by_filename_example": "Exemple : IMG_1234.JPG ou PNG", + "search_by_ocr": "Recherche par OCR", + "search_by_ocr_example": "café latte", + "search_camera_lens_model": "Chercher par modèle d'objectif...", "search_camera_make": "Rechercher par marque d'appareil photo...", "search_camera_model": "Rechercher par modèle d'appareil photo...", "search_city": "Rechercher par ville...", @@ -1713,6 +1752,7 @@ "search_filter_location_title": "Sélectionner une localisation", "search_filter_media_type": "Type de média", "search_filter_media_type_title": "Sélectionner type de média", + "search_filter_ocr": "Recherche par OCR", "search_filter_people_title": "Sélectionner une personne", "search_for": "Chercher", "search_for_existing_person": "Rechercher une personne existante", @@ -1770,11 +1810,12 @@ "send_welcome_email": "Envoyer un courriel de bienvenue", "server_endpoint": "Adresse du serveur", "server_info_box_app_version": "Version de l'application", - "server_info_box_server_url": "URL du serveur", + "server_info_box_server_url": "Server URL", "server_offline": "Serveur hors ligne", "server_online": "Serveur en ligne", "server_privacy": "Vie privée pour le serveur", "server_stats": "Statistiques du serveur", + "server_update_available": "Une mise à jour du serveur est disponible", "server_version": "Version du serveur", "set": "Définir", "set_as_album_cover": "Définir comme couverture d'album", @@ -1803,6 +1844,8 @@ "setting_notifications_subtitle": "Ajustez vos préférences de notification", "setting_notifications_total_progress_subtitle": "Progression globale de l'envoi (effectué/total des médias)", "setting_notifications_total_progress_title": "Afficher la progression totale de la sauvegarde en arrière-plan", + "setting_video_viewer_auto_play_subtitle": "Lancer automatiquement la lecture des vidéos lorsqu’elles sont ouvertes", + "setting_video_viewer_auto_play_title": "Lecture automatique des vidéos", "setting_video_viewer_looping_title": "Boucle", "setting_video_viewer_original_video_subtitle": "Lors de la diffusion d'une vidéo depuis le serveur, lisez l'original même si un transcodage est disponible. Cela peut entraîner de la mise en mémoire tampon. Les vidéos disponibles localement sont lues en qualité d'origine, quel que soit ce paramètre.", "setting_video_viewer_original_video_title": "Forcer la vidéo originale", @@ -1895,7 +1938,7 @@ "show_supporter_badge": "Badge de contributeur", "show_supporter_badge_description": "Afficher le badge de contributeur", "show_text_search_menu": "Afficher le menu de recherche de texte", - "shuffle": "Mélanger", + "shuffle": "Aléatoire", "sidebar": "Barre latérale", "sidebar_display_description": "Afficher un lien vers la vue dans la barre latérale", "sign_out": "Déconnexion", @@ -1982,6 +2025,7 @@ "theme_setting_three_stage_loading_title": "Activer le chargement en trois étapes", "they_will_be_merged_together": "Elles seront fusionnées ensemble", "third_party_resources": "Ressources tierces", + "time": "Horaire", "time_based_memories": "Souvenirs basés sur la date", "timeline": "Vue chronologique", "timezone": "Fuseau horaire", @@ -2014,6 +2058,7 @@ "troubleshoot": "Dépannage", "type": "Type", "unable_to_change_pin_code": "Impossible de changer le code PIN", + "unable_to_check_version": "Impossible de vérifier la version de l'application ou du serveur", "unable_to_setup_pin_code": "Impossible de définir le code PIN", "unarchive": "Désarchiver", "unarchive_action_prompt": "{count} supprimé(s) de l'archive", diff --git a/i18n/gl.json b/i18n/gl.json index b6a59fadc7..c2e955960d 100644 --- a/i18n/gl.json +++ b/i18n/gl.json @@ -11,14 +11,14 @@ "activity_changed": "A actividade está {enabled, select, true {activada} other {desactivada}}", "add": "Engadir", "add_a_description": "Engadir unha descrición", - "add_a_location": "Engadir unha ubicación", + "add_a_location": "Engadir unha localización", "add_a_name": "Engadir un nome", "add_a_title": "Engadir un título", - "add_birthday": "Engadir cumpleanos", - "add_endpoint": "Engadir endpoint", + "add_birthday": "Engadir cumpreanos", + "add_endpoint": "Engadir punto final", "add_exclusion_pattern": "Engadir patrón de exclusión", "add_import_path": "Engadir ruta de importación", - "add_location": "Engadir ubicación", + "add_location": "Engadir localización", "add_more_users": "Engadir máis usuarios", "add_partner": "Engadir compañeiro/a", "add_path": "Engadir ruta", @@ -28,69 +28,71 @@ "add_to_album": "Engadir ao álbum", "add_to_album_bottom_sheet_added": "Engadido a {album}", "add_to_album_bottom_sheet_already_exists": "Xa está en {album}", - "add_to_album_toggle": "Alternar selección para o {album}", - "add_to_albums": "Engadir en álbums", + "add_to_album_bottom_sheet_some_local_assets": "Algunhas imaxes ou ficheiros locais non se puideron engadir ao álbum", + "add_to_album_toggle": "Alternar selección para {album}", + "add_to_albums": "Engadir a álbums", "add_to_albums_count": "Engadir a {count} álbums", "add_to_shared_album": "Engadir ao álbum compartido", + "add_upload_to_stack": "Engade cargar á pila", "add_url": "Engadir URL", "added_to_archive": "Engadido ao arquivo", "added_to_favorites": "Engadido a favoritos", - "added_to_favorites_count": "Engadido {count, number} a favoritos", + "added_to_favorites_count": "Engadíronse {count, number} a favoritos", "admin": { - "add_exclusion_pattern_description": "Engadir patróns de exclusión. Admítense caracteres comodín usando *, ** e ?. Para ignorar todos os ficheiros en calquera directorio chamado \"Raw\", emprega \"**/Raw/**\". Para ignorar todos os ficheiros que rematen en \".tif\", usa \"**/*.tif\". Para ignorar unha ruta absoluta, emprega \"/ruta/a/ignorar/**\".", + "add_exclusion_pattern_description": "Engadir patróns de exclusión. Admítense caracteres comodín usando *, ** e ?. Para ignorar todos os ficheiros en calquera directorio chamado \"Raw\", empregue \"**/Raw/**\". Para ignorar todos os ficheiros que rematen en \".tif\", use \"**/*.tif\". Para ignorar unha ruta absoluta, empregue \"/ruta/a/ignorar/**\".", "admin_user": "Usuario administrador", - "asset_offline_description": "Este activo da biblioteca externa xa non se atopa no disco e moveuse ao lixo. Se o ficheiro se moveu dentro da biblioteca, comproba a túa liña de tempo para o novo activo correspondente. Para restaurar este activo, asegúrate de que Immich poida acceder á ruta do ficheiro a continuación e escanee a biblioteca.", + "asset_offline_description": "Este activo da biblioteca externa xa non se atopa no disco e moveuse ao lixo. Se o ficheiro se moveu dentro da biblioteca, comprobe a súa liña de tempo para o novo activo correspondente. Para restaurar este activo, asegúrese de que Immich poida acceder á ruta do ficheiro a continuación e escanee a biblioteca.", "authentication_settings": "Configuración de autenticación", "authentication_settings_description": "Xestionar contrasinal, OAuth e outras configuracións de autenticación", - "authentication_settings_disable_all": "Estás seguro de que queres desactivar todos os métodos de inicio de sesión? O inicio de sesión desactivarase completamente.", + "authentication_settings_disable_all": "Está seguro de que quere desactivar todos os métodos de inicio de sesión? O inicio de sesión desactivarase completamente.", "authentication_settings_reenable": "Para reactivalo, use un Comando de servidor.", "background_task_job": "Tarefas en segundo plano", - "backup_database": "Crear un vertedoiro de base de datos", - "backup_database_enable_description": "Activar o vertedoiro de copias de seguridade da base de datos", - "backup_keep_last_amount": "Cantidade de copias de seguridade anteriores a conservar", - "backup_onboarding_1_description": "Copia no exterior na nube ou noutra localización física.", - "backup_onboarding_2_description": "Copias locais en diferentes dispositivos. Isto inclue os arquivos principais e as copias de esos arquivos localmente.", - "backup_onboarding_3_description": "copias totais da tua información, incluindo os arquivos orixinais. Isto inclue 1 copia externa e 2 copias locais.", - "backup_onboarding_description": "Unha estratexia de copia 3-2-1 é recomendada para protexer os teus datos. Deberías gardar copias das túas fotos/videos subidas así como da base de datos de Immich como unha solución de seguridade.", - "backup_onboarding_footer": "Pra máis información sobre copias de seguridade de Immich, por favor use a seguinte ligazón de documentación.", - "backup_onboarding_parts_title": "Unha copia de seguridade 3-2-1 inclue:", + "backup_database": "Crear unha copia da base de datos", + "backup_database_enable_description": "Activar a copia de seguridade da base de datos", + "backup_keep_last_amount": "Número de copias anteriores a conservar", + "backup_onboarding_1_description": "Copia externa na nube ou noutra localización física.", + "backup_onboarding_2_description": "Copias locais en diferentes dispositivos. Isto inclúe os arquivos principais e as copias deses arquivos localmente.", + "backup_onboarding_3_description": "Copias totais da súa información, incluíndo os arquivos orixinais. Isto inclúe 1 copia externa e 2 copias locais.", + "backup_onboarding_description": "Unha estratexia de copia de seguridade 3-2-1 é recomendada para protexer os seus datos. Debería gardar copias das súas fotos/vídeos subidos así como da base de datos de Immich como unha solución de seguridade.", + "backup_onboarding_footer": "Para máis información sobre copias de seguridade de Immich, por favor use a seguinte ligazón á documentación.", + "backup_onboarding_parts_title": "Unha copia de seguridade 3-2-1 inclúe:", "backup_onboarding_title": "Copia de seguridade", - "backup_settings": "Configuración da copia de seguridade", - "backup_settings_description": "Xestionar a configuración do volcado da base de datos", + "backup_settings": "Configuración da copia da base de datos", + "backup_settings_description": "Xestionar a configuración da copia da base de datos.", "cleared_jobs": "Traballos borrados para: {job}", "config_set_by_file": "A configuración establécese actualmente mediante un ficheiro de configuración", - "confirm_delete_library": "Estás seguro de que queres eliminar a biblioteca {library}?", - "confirm_delete_library_assets": "Estás seguro de que queres eliminar esta biblioteca? Isto eliminará {count, plural, one {# activo contido} other {todos os # activos contidos}} de Immich e non se pode desfacer. Os ficheiros permanecerán no disco.", + "confirm_delete_library": "Está seguro de que quere eliminar a biblioteca {library}?", + "confirm_delete_library_assets": "Está seguro de que quere eliminar esta biblioteca? Isto eliminará {count, plural, one {# activo contido} other {todos os # activos contidos}} de Immich e non se pode desfacer. Os ficheiros permanecerán no disco.", "confirm_email_below": "Para confirmar, escriba \"{email}\" a continuación", - "confirm_reprocess_all_faces": "Estás seguro de que queres reprocesar todas as caras? Isto tamén borrará as persoas nomeadas.", - "confirm_user_password_reset": "Estás seguro de que queres restablecer o contrasinal de {user}?", - "confirm_user_pin_code_reset": "Estás seguro de que queres restablecer o PIN de {user}?", + "confirm_reprocess_all_faces": "Está seguro de que quere reprocesar todas as caras? Isto tamén borrará as persoas nomeadas.", + "confirm_user_password_reset": "Está seguro de que quere restablecer o contrasinal de {user}?", + "confirm_user_pin_code_reset": "Está seguro de que quere restablecer o PIN de {user}?", "create_job": "Crear traballo", "cron_expression": "Expresión Cron", "cron_expression_description": "Estableza o intervalo de escaneo usando o formato cron. Para obter máis información, consulte por exemplo Crontab Guru", "cron_expression_presets": "Preaxustes de expresión Cron", "disable_login": "Desactivar inicio de sesión", "duplicate_detection_job_description": "Executar aprendizaxe automática nos activos para detectar imaxes similares. Depende da Busca Intelixente", - "exclusion_pattern_description": "Os patróns de exclusión permítenche ignorar ficheiros e cartafoles ao escanear a túa biblioteca. Isto é útil se tes cartafoles que conteñen ficheiros que non queres importar, como ficheiros RAW.", + "exclusion_pattern_description": "Os patróns de exclusión permítenlle ignorar ficheiros e cartafoles ao escanear a súa biblioteca. Isto é útil se ten cartafoles que conteñen ficheiros que non quere importar, como ficheiros RAW.", "external_library_management": "Xestión da biblioteca externa", "face_detection": "Detección de caras", "face_detection_description": "Detectar as caras nos activos usando aprendizaxe automática. Para vídeos, só se considera a miniatura. \"Actualizar\" (re)procesa todos os activos. \"Restablecer\" ademais borra todos os datos de caras actuais. \"Faltantes\" pon en cola os activos que aínda non foron procesados. As caras detectadas poranse en cola para o Recoñecemento Facial despois de completar a Detección de Caras, agrupándoas en persoas existentes ou novas.", "facial_recognition_job_description": "Agrupar caras detectadas en persoas. Este paso execútase despois de completar a Detección de Caras. \"Restablecer\" (re)agrupa todas as caras. \"Faltantes\" pon en cola as caras que non teñen unha persoa asignada.", "failed_job_command": "O comando {command} fallou para o traballo: {job}", - "force_delete_user_warning": "AVISO: Isto eliminará inmediatamente o usuario e todos os activos. Isto non se pode desfacer e os ficheiros non se poden recuperar.", + "force_delete_user_warning": "AVISO: Isto eliminará inmediatamente o usuario e todos os seus activos. Esta acción non se pode desfacer e os ficheiros non se poderán recuperar.", "image_format": "Formato", "image_format_description": "WebP produce ficheiros máis pequenos que JPEG, pero é máis lento de codificar.", "image_fullsize_description": "Imaxe a tamaño completo con metadatos eliminados, usada ao facer zoom", "image_fullsize_enabled": "Activar a xeración de imaxes a tamaño completo", "image_fullsize_enabled_description": "Xerar imaxe a tamaño completo para formatos non compatibles coa web. Cando \"Preferir vista previa incrustada\" está activado, as vistas previas incrustadas utilízanse directamente sen conversión. Non afecta a formatos compatibles coa web como JPEG.", - "image_fullsize_quality_description": "Calidade da imaxe a tamaño completo de 1 a 100. Máis alto é mellor, pero produce ficheiros máis grandes.", + "image_fullsize_quality_description": "Calidade da imaxe a tamaño completo de 1 a 100. Canto máis alto, mellor, pero produce ficheiros máis grandes.", "image_fullsize_title": "Configuración da imaxe a tamaño completo", "image_prefer_embedded_preview": "Preferir vista previa incrustada", - "image_prefer_embedded_preview_setting_description": "Usar vistas previas incrustadas en fotos RAW como entrada para o procesamento de imaxes e cando estean dispoñibles. Isto pode producir cores máis precisas para algunhas imaxes, pero a calidade da vista previa depende da cámara e a imaxe pode ter máis artefactos de compresión.", - "image_prefer_wide_gamut": "Preferir gama ampla", + "image_prefer_embedded_preview_setting_description": "Usar vistas previas incrustadas en fotos RAW como entrada para o procesamento de imaxes cando estean dispoñibles. Isto pode producir cores máis precisas para algunhas imaxes, pero a calidade da vista previa depende da cámara e a imaxe pode ter máis artefactos de compresión.", + "image_prefer_wide_gamut": "Preferir gama de cores ampla", "image_prefer_wide_gamut_setting_description": "Usar Display P3 para as miniaturas. Isto preserva mellor a viveza das imaxes con espazos de cor amplos, pero as imaxes poden aparecer de forma diferente en dispositivos antigos cunha versión de navegador antiga. As imaxes sRGB mantéñense como sRGB para evitar cambios de cor.", "image_preview_description": "Imaxe de tamaño medio con metadatos eliminados, usada ao ver un único activo e para aprendizaxe automática", - "image_preview_quality_description": "Calidade da vista previa de 1 a 100. Máis alto é mellor, pero produce ficheiros máis grandes e pode reducir a capacidade de resposta da aplicación. Establecer un valor baixo pode afectar á calidade da aprendizaxe automática.", + "image_preview_quality_description": "Calidade da vista previa de 1 a 100. Canto máis alto, mellor, pero produce ficheiros máis grandes e pode reducir a capacidade de resposta da aplicación. Establecer un valor baixo pode afectar á calidade da aprendizaxe automática.", "image_preview_title": "Configuración da vista previa", "image_quality": "Calidade", "image_resolution": "Resolución", @@ -98,11 +100,11 @@ "image_settings": "Configuración da imaxe", "image_settings_description": "Xestionar a calidade e resolución das imaxes xeradas", "image_thumbnail_description": "Miniatura pequena con metadatos eliminados, usada ao ver grupos de fotos como a liña de tempo principal", - "image_thumbnail_quality_description": "Calidade da miniatura de 1 a 100. Máis alto é mellor, pero produce ficheiros máis grandes e pode reducir a capacidade de resposta da aplicación.", + "image_thumbnail_quality_description": "Calidade da miniatura de 1 a 100. Canto máis alto, mellor, pero produce ficheiros máis grandes e pode reducir a capacidade de resposta da aplicación.", "image_thumbnail_title": "Configuración da miniatura", "job_concurrency": "concorrencia de {job}", "job_created": "Traballo creado", - "job_not_concurrency_safe": "Este traballo non é seguro para concorrencia.", + "job_not_concurrency_safe": "Este traballo non é seguro para execución concorrente.", "job_settings": "Configuración de traballos", "job_settings_description": "Xestionar a concorrencia de traballos", "job_status": "Estado do traballo", @@ -117,31 +119,31 @@ "library_settings": "Biblioteca externa", "library_settings_description": "Xestionar a configuración da biblioteca externa", "library_tasks_description": "Escanear bibliotecas externas en busca de activos novos e/ou modificados", - "library_watching_enable_description": "Vixiar bibliotecas externas para cambios nos ficheiros", + "library_watching_enable_description": "Vixiar bibliotecas externas para detectar cambios nos ficheiros", "library_watching_settings": "Vixilancia da biblioteca (EXPERIMENTAL)", "library_watching_settings_description": "Vixiar automaticamente os ficheiros modificados", "logging_enable_description": "Activar rexistro", "logging_level_description": "Cando estea activado, que nivel de rexistro usar.", "logging_settings": "Rexistro", "machine_learning_availability_checks": "Comprobacións de dispoñibilidade", - "machine_learning_availability_checks_description": "Detectar automáticamente e preferir servidores de aprendizaxe profunda dispoñibles", + "machine_learning_availability_checks_description": "Detectar automaticamente e preferir servidores de aprendizaxe automática dispoñibles", "machine_learning_availability_checks_enabled": "Activar comprobacións de dispoñibilidade", "machine_learning_availability_checks_interval": "Intervalo de comprobación", "machine_learning_availability_checks_interval_description": "Intervalo en milisegundos entre comprobacións de dispoñibilidade", "machine_learning_availability_checks_timeout": "Tempo de espera da solicitude", - "machine_learning_availability_checks_timeout_description": "Tempo de espera en milisegundos para as comprobación de dispoñibilidade", + "machine_learning_availability_checks_timeout_description": "Tempo de espera en milisegundos para as comprobacións de dispoñibilidade", "machine_learning_clip_model": "Modelo CLIP", - "machine_learning_clip_model_description": "O nome dun modelo CLIP listado aquí. Ten en conta que debe volver executar o traballo 'Busca Intelixente' para todas as imaxes ao cambiar un modelo.", + "machine_learning_clip_model_description": "O nome dun modelo CLIP listado aquí. Teña en conta que debe volver executar o traballo 'Busca Intelixente' para todas as imaxes ao cambiar un modelo.", "machine_learning_duplicate_detection": "Detección de duplicados", "machine_learning_duplicate_detection_enabled": "Activar detección de duplicados", - "machine_learning_duplicate_detection_enabled_description": "Se está desactivado, os activos exactamente idénticos aínda se eliminarán duplicados.", + "machine_learning_duplicate_detection_enabled_description": "Se está desactivado, os activos exactamente idénticos aínda se eliminarán.", "machine_learning_duplicate_detection_setting_description": "Usar incrustacións CLIP para atopar posibles duplicados", "machine_learning_enabled": "Activar aprendizaxe automática", - "machine_learning_enabled_description": "Se está desactivado, todas as funcións de ML desactivaranse independentemente da configuración a continuación.", + "machine_learning_enabled_description": "Se está desactivado, todas as funcións de aprendizaxe automática desactivaranse independentemente da configuración a continuación.", "machine_learning_facial_recognition": "Recoñecemento facial", "machine_learning_facial_recognition_description": "Detectar, recoñecer e agrupar caras en imaxes", "machine_learning_facial_recognition_model": "Modelo de recoñecemento facial", - "machine_learning_facial_recognition_model_description": "Os modelos están listados en orde descendente de tamaño. Os modelos máis grandes son máis lentos e usan máis memoria, pero producen mellores resultados. Teña en conta que debes volver executar o traballo de Detección de Caras para todas as imaxes ao cambiar un modelo.", + "machine_learning_facial_recognition_model_description": "Os modelos están listados en orde descendente de tamaño. Os modelos máis grandes son máis lentos e usan máis memoria, pero producen mellores resultados. Teña en conta que debe volver executar o traballo de Detección de Caras para todas as imaxes ao cambiar un modelo.", "machine_learning_facial_recognition_setting": "Activar recoñecemento facial", "machine_learning_facial_recognition_setting_description": "Se está desactivado, as imaxes non se codificarán para o recoñecemento facial e non encherán a sección Persoas na páxina Explorar.", "machine_learning_max_detection_distance": "Distancia máxima de detección", @@ -188,31 +190,34 @@ "nightly_tasks_cluster_new_faces_setting": "Agrupar novas caras", "nightly_tasks_database_cleanup_setting": "Tarefas de limpeza da base de datos", "nightly_tasks_database_cleanup_setting_description": "Limpar información vella e obsoleta da base de datos", - "nightly_tasks_generate_memories_setting": "Xerar memorias", - "nightly_tasks_generate_memories_setting_description": "Crear novas memorias dende os recursos", + "nightly_tasks_generate_memories_setting": "Xerar recordos", + "nightly_tasks_generate_memories_setting_description": "Crear novos recordos a partir dos activos", "nightly_tasks_missing_thumbnails_setting": "Xerar as miniaturas que faltan", - "nightly_tasks_missing_thumbnails_setting_description": "Encolar arquivos sin miniaturas para a xeración das miniaturas", + "nightly_tasks_missing_thumbnails_setting_description": "Poñer en cola os arquivos sen miniaturas para a súa xeración", "nightly_tasks_settings": "Configuración das tarefas nocturnas", "nightly_tasks_settings_description": "Administrar as tarefas nocturnas", - "nightly_tasks_start_time_setting": "Tempo de inicio", - "nightly_tasks_start_time_setting_description": "O tempo no que o servidor comeza a executar as tarefas nocturnas", - "nightly_tasks_sync_quota_usage_setting": "Sincronizar uso de cuota", + "nightly_tasks_start_time_setting": "Hora de inicio", + "nightly_tasks_start_time_setting_description": "A hora na que o servidor comeza a executar as tarefas nocturnas", + "nightly_tasks_sync_quota_usage_setting": "Sincronizar uso de cota", + "nightly_tasks_sync_quota_usage_setting_description": "Actualizar a cota de almacenamento do usuario, en base ao uso actual", "no_paths_added": "Non se engadiron rutas", - "no_pattern_added": "Non se engadiu ningún padrón", + "no_pattern_added": "Non se engadiu ningún patrón", "note_apply_storage_label_previous_assets": "Nota: Para aplicar a Etiqueta de Almacenamento a activos cargados previamente, execute o", "note_cannot_be_changed_later": "NOTA: Isto non se pode cambiar máis tarde!", "notification_email_from_address": "Enderezo do remitente", - "notification_email_from_address_description": "Enderezo de correo electrónico do remitente, por exemplo: \"Servidor de Fotos Immich \"", + "notification_email_from_address_description": "Enderezo de correo do remitente, por exemplo: \"Servidor de Fotos Immich noreply@exemplo.com\". Asegúrese de empregar un enderezo dende o que teña permiso para enviar correos.", "notification_email_host_description": "Host do servidor de correo electrónico (p. ex. smtp.immich.app)", "notification_email_ignore_certificate_errors": "Ignorar erros de certificado", "notification_email_ignore_certificate_errors_description": "Ignorar erros de validación do certificado TLS (non recomendado)", "notification_email_password_description": "Contrasinal a usar ao autenticarse co servidor de correo electrónico", "notification_email_port_description": "Porto do servidor de correo electrónico (p. ex. 25, 465 ou 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "Usar SMTPS (SMTP sobre TLS)", "notification_email_sent_test_email_button": "Enviar correo de proba e gardar", "notification_email_setting_description": "Configuración para enviar notificacións por correo electrónico", "notification_email_test_email": "Enviar correo de proba", - "notification_email_test_email_failed": "Erro ao enviar correo de proba, comproba os teus valores", - "notification_email_test_email_sent": "Enviouse un correo electrónico de proba a {email}. Por favor, comproba a túa caixa de entrada.", + "notification_email_test_email_failed": "Erro ao enviar correo de proba, comprobe os seus valores", + "notification_email_test_email_sent": "Enviouse un correo electrónico de proba a {email}. Por favor, comprobe a súa caixa de entrada.", "notification_email_username_description": "Nome de usuario a usar ao autenticarse co servidor de correo electrónico", "notification_enable_email_notifications": "Activar notificacións por correo electrónico", "notification_settings": "Configuración de Notificacións", @@ -222,10 +227,13 @@ "oauth_auto_register": "Rexistro automático", "oauth_auto_register_description": "Rexistrar automaticamente novos usuarios despois de iniciar sesión con OAuth", "oauth_button_text": "Texto do botón", + "oauth_client_secret_description": "Requirido se o provedor OAuth non admite PKCE (Proof Key for Code Exchange)", "oauth_enable_description": "Iniciar sesión con OAuth", "oauth_mobile_redirect_uri": "URI de redirección móbil", "oauth_mobile_redirect_uri_override": "Substitución de URI de redirección móbil", "oauth_mobile_redirect_uri_override_description": "Activar cando o provedor OAuth non permite un URI móbil, como ''{callback}''", + "oauth_role_claim": "Declaración de rol", + "oauth_role_claim_description": "Conceder acceso de administrador automaticamente segundo a presenza desta declaración. A declaración pode ter os valores 'user' ou 'admin'.", "oauth_settings": "OAuth", "oauth_settings_description": "Xestionar a configuración de inicio de sesión OAuth", "oauth_settings_more_details": "Para máis detalles sobre esta función, consulte a documentación.", @@ -234,7 +242,9 @@ "oauth_storage_quota_claim": "Declaración de cota de almacenamento", "oauth_storage_quota_claim_description": "Establecer automaticamente a cota de almacenamento do usuario ao valor desta declaración.", "oauth_storage_quota_default": "Cota de almacenamento predeterminada (GiB)", - "oauth_storage_quota_default_description": "Cota en GiB a usar cando non se proporciona ningunha declaración (Introduza 0 para cota ilimitada).", + "oauth_storage_quota_default_description": "Cota en GiB a empregar cando non se proporciona ningunha declaración.", + "oauth_timeout": "Tempo máximo de espera da solicitude", + "oauth_timeout_description": "Tempo máximo de espera para as solicitudes en milisegundos", "password_enable_description": "Iniciar sesión con correo electrónico e contrasinal", "password_settings": "Inicio de sesión con contrasinal", "password_settings_description": "Xestionar a configuración de inicio de sesión con contrasinal", @@ -243,17 +253,17 @@ "quota_size_gib": "Tamaño da cota (GiB)", "refreshing_all_libraries": "Actualizando todas as bibliotecas", "registration": "Rexistro do administrador", - "registration_description": "Dado que ti es o primeiro usuario no sistema, asignarásete como Administrador e serás responsable das tarefas administrativas, e os usuarios adicionais serán creados por ti.", + "registration_description": "Dado que vostede é o primeiro usuario no sistema, asignaráselle como Administrador e será responsable das tarefas administrativas. Os usuarios adicionais serán creados por vostede.", "require_password_change_on_login": "Requirir que o usuario cambie o contrasinal no primeiro inicio de sesión", "reset_settings_to_default": "Restablecer a configuración aos valores predeterminados", - "reset_settings_to_recent_saved": "Restablecer a configuración á configuración gardada recentemente", + "reset_settings_to_recent_saved": "Restablecer á configuración gardada recentemente", "scanning_library": "Escaneando biblioteca", "search_jobs": "Buscar traballos…", "send_welcome_email": "Enviar correo electrónico de benvida", "server_external_domain_settings": "Dominio externo", "server_external_domain_settings_description": "Dominio para ligazóns públicas compartidas, incluíndo http(s)://", "server_public_users": "Usuarios públicos", - "server_public_users_description": "Todos os usuarios (nome e correo electrónico) listanse ao engadir un usuario a álbums compartidos. Cando está desactivado, a lista de usuarios só estará dispoñible para os usuarios administradores.", + "server_public_users_description": "Todos os usuarios (nome e correo electrónico) lístanse ao engadir un usuario a álbums compartidos. Cando está desactivado, a lista de usuarios só estará dispoñible para os usuarios administradores.", "server_settings": "Configuración do servidor", "server_settings_description": "Xestionar a configuración do servidor", "server_welcome_message": "Mensaxe de benvida", @@ -266,19 +276,20 @@ "storage_template_date_time_sample": "Tempo de mostra {date}", "storage_template_enable_description": "Activar o motor de modelos de almacenamento", "storage_template_hash_verification_enabled": "Verificación de hash activada", - "storage_template_hash_verification_enabled_description": "Activa a verificación de hash, non desactives isto a menos que esteas seguro das implicacións", + "storage_template_hash_verification_enabled_description": "Activa a verificación de hash. Non desactive isto a menos que estea seguro das implicacións", "storage_template_migration": "Migración do modelo de almacenamento", "storage_template_migration_description": "Aplicar o {template} actual aos activos cargados previamente", "storage_template_migration_info": "O modelo de almacenamento converterá todas as extensións a minúsculas. Os cambios no modelo só se aplicarán aos activos novos. Para aplicar retroactivamente o modelo aos activos cargados previamente, execute o {job}.", "storage_template_migration_job": "Traballo de Migración do Modelo de Almacenamento", "storage_template_more_details": "Para máis detalles sobre esta función, consulte o Modelo de Almacenamento e as súas implicacións", + "storage_template_onboarding_description_v2": "Cando está activada, esta función organizará automaticamente os ficheiros segundo un modelo definido polo usuario. Para máis información, consulte a documentación.", "storage_template_path_length": "Límite aproximado da lonxitude da ruta: {length, number}/{limit, number}", "storage_template_settings": "Modelo de Almacenamento", "storage_template_settings_description": "Xestionar a estrutura de cartafoles e o nome de ficheiro do activo cargado", "storage_template_user_label": "{label} é a Etiqueta de Almacenamento do usuario", "system_settings": "Configuración do Sistema", "tag_cleanup_job": "Limpeza de etiquetas", - "template_email_available_tags": "Podes usar as seguintes variables no teu modelo: {tags}", + "template_email_available_tags": "Pode usar as seguintes variables no seu modelo: {tags}", "template_email_if_empty": "Se o modelo está baleiro, usarase o correo electrónico predeterminado.", "template_email_invite_album": "Modelo de Invitación a Álbum", "template_email_preview": "Vista previa", @@ -286,15 +297,15 @@ "template_email_update_album": "Modelo de Actualización de Álbum", "template_email_welcome": "Modelo de correo electrónico de benvida", "template_settings": "Modelos de Notificación", - "template_settings_description": "Xestionar modelos personalizados para notificacións.", + "template_settings_description": "Xestionar modelos personalizados para notificacións", "theme_custom_css_settings": "CSS Personalizado", - "theme_custom_css_settings_description": "As Follas de Estilo en Cascada permiten personalizar o deseño de Immich.", + "theme_custom_css_settings_description": "As Follas de Estilo en Cascada (CSS) permiten personalizar o deseño de Immich.", "theme_settings": "Configuración do Tema", "theme_settings_description": "Xestionar a personalización da interface web de Immich", "thumbnail_generation_job": "Xerar Miniaturas", "thumbnail_generation_job_description": "Xerar miniaturas grandes, pequenas e borrosas para cada activo, así como miniaturas para cada persoa", "transcoding_acceleration_api": "API de aceleración", - "transcoding_acceleration_api_description": "A API que interactuará co teu dispositivo para acelerar a transcodificación. Esta configuración é de 'mellor esforzo': recurrirá á transcodificación por software en caso de fallo. VP9 pode funcionar ou non dependendo do teu hardware.", + "transcoding_acceleration_api_description": "A API que interactuará co seu dispositivo para acelerar a transcodificación. Esta configuración é de 'mellor esforzo': recurrirá á transcodificación por software en caso de fallo. VP9 pode funcionar ou non dependendo do seu hardware.", "transcoding_acceleration_nvenc": "NVENC (require GPU NVIDIA)", "transcoding_acceleration_qsv": "Quick Sync (require CPU Intel de 7ª xeración ou posterior)", "transcoding_acceleration_rkmpp": "RKMPP (só en SOCs Rockchip)", @@ -309,22 +320,22 @@ "transcoding_audio_codec": "Códec de audio", "transcoding_audio_codec_description": "Opus é a opción de maior calidade, pero ten menor compatibilidade con dispositivos ou software antigos.", "transcoding_bitrate_description": "Vídeos cun bitrate superior ao máximo ou que non estean nun formato aceptado", - "transcoding_codecs_learn_more": "Para saber máis sobre a terminoloxía usada aquí, consulte a documentación de FFmpeg para códec H.264, códec HEVC e códec VP9.", + "transcoding_codecs_learn_more": "Para saber máis sobre a terminoloxía usada aquí, consulte a documentación de FFmpeg para o códec H.264, o códec HEVC e o códec VP9.", "transcoding_constant_quality_mode": "Modo de calidade constante", "transcoding_constant_quality_mode_description": "ICQ é mellor que CQP, pero algúns dispositivos de aceleración por hardware non admiten este modo. Establecer esta opción preferirá o modo especificado ao usar codificación baseada na calidade. Ignorado por NVENC xa que non admite ICQ.", "transcoding_constant_rate_factor": "Factor de taxa constante (-crf)", - "transcoding_constant_rate_factor_description": "Nivel de calidade do vídeo. Valores típicos son 23 para H.264, 28 para HEVC, 31 para VP9 e 35 para AV1. Máis baixo é mellor, pero produce ficheiros máis grandes.", - "transcoding_disabled_description": "Non transcodificar ningún vídeo, pode romper a reprodución nalgúns clientes", + "transcoding_constant_rate_factor_description": "Nivel de calidade do vídeo. Valores típicos son 23 para H.264, 28 para HEVC, 31 para VP9 e 35 para AV1. Canto máis baixo, mellor, pero produce ficheiros máis grandes.", + "transcoding_disabled_description": "Non transcodificar ningún vídeo; pode romper a reprodución nalgúns clientes", "transcoding_encoding_options": "Opcións de Codificación", "transcoding_encoding_options_description": "Establecer códecs, resolución, calidade e outras opcións para os vídeos codificados", "transcoding_hardware_acceleration": "Aceleración por Hardware", - "transcoding_hardware_acceleration_description": "Experimental; moito máis rápido, pero terá menor calidade co mesmo bitrate", + "transcoding_hardware_acceleration_description": "Experimental: transcodificación máis rápida, pero pode reducir a calidade ao mesmo bitrate", "transcoding_hardware_decoding": "Decodificación por hardware", "transcoding_hardware_decoding_setting_description": "Activa a aceleración de extremo a extremo en lugar de só acelerar a codificación. Pode non funcionar en todos os vídeos.", "transcoding_max_b_frames": "Máximo de B-frames", "transcoding_max_b_frames_description": "Valores máis altos melloran a eficiencia da compresión, pero ralentizan a codificación. Pode non ser compatible coa aceleración por hardware en dispositivos máis antigos. 0 desactiva os B-frames, mentres que -1 establece este valor automaticamente.", "transcoding_max_bitrate": "Bitrate máximo", - "transcoding_max_bitrate_description": "Establecer un bitrate máximo pode facer que os tamaños dos ficheiros sexan máis predicibles a un custo menor para a calidade. A 720p, os valores típicos son 2600 kbit/s para VP9 ou HEVC, ou 4500 kbit/s para H.264. Desactivado se se establece en 0.", + "transcoding_max_bitrate_description": "Establecer unha taxa de bits máxima pode facer que os tamaños dos ficheiros sexan máis predicibles a un custo menor na calidade. En 720p, os valores típicos son 2600 kbit/s para VP9 ou HEVC, ou 4500 kbit/s para H.264. Desactivado se se establece en 0. Cando non se especifica unha unidade, asúmese k (para kbit/s); polo tanto, 5000, 5000k e 5M (para Mbit/s) son equivalentes.", "transcoding_max_keyframe_interval": "Intervalo máximo de fotogramas clave", "transcoding_max_keyframe_interval_description": "Establece a distancia máxima de fotogramas entre fotogramas clave. Valores máis baixos empeoran a eficiencia da compresión, pero melloran os tempos de busca e poden mellorar a calidade en escenas con movemento rápido. 0 establece este valor automaticamente.", "transcoding_optimal_description": "Vídeos cunha resolución superior á obxectivo ou que non estean nun formato aceptado", @@ -342,7 +353,7 @@ "transcoding_target_resolution": "Resolución obxectivo", "transcoding_target_resolution_description": "Resolucións máis altas poden preservar máis detalles pero tardan máis en codificarse, teñen tamaños de ficheiro máis grandes e poden reducir a capacidade de resposta da aplicación.", "transcoding_temporal_aq": "AQ Temporal", - "transcoding_temporal_aq_description": "Aplícase só a NVENC. Aumenta a calidade de escenas de alto detalle e baixo movemento. Pode non ser compatible con dispositivos máis antigos.", + "transcoding_temporal_aq_description": "Aplícase só a NVENC. A Cuantización Adaptativa Temporal aumenta a calidade das escenas con moito detalle e pouco movemento. Pode que non sexa compatible con dispositivos máis antigos.", "transcoding_threads": "Fíos", "transcoding_threads_description": "Valores máis altos levan a unha codificación máis rápida, pero deixan menos marxe para que o servidor procese outras tarefas mentres está activo. Este valor non debería ser maior que o número de núcleos da CPU. Maximiza a utilización se se establece en 0.", "transcoding_tone_mapping": "Mapeo de tons", @@ -350,7 +361,7 @@ "transcoding_transcode_policy": "Política de transcodificación", "transcoding_transcode_policy_description": "Política para cando un vídeo debe ser transcodificado. Os vídeos HDR sempre serán transcodificados (excepto se a transcodificación está desactivada).", "transcoding_two_pass_encoding": "Codificación en dous pasos", - "transcoding_two_pass_encoding_setting_description": "Transcodificar en dous pasos para producir vídeos codificados mellor. Cando o bitrate máximo está activado (requirido para que funcione con H.264 e HEVC), este modo usa un rango de bitrate baseado no bitrate máximo e ignora CRF. Para VP9, pódese usar CRF se o bitrate máximo está desactivado.", + "transcoding_two_pass_encoding_setting_description": "Transcodificar en dous pasos para producir vídeos codificados de mellor calidade. Cando o bitrate máximo está activado (requirido para que funcione con H.264 e HEVC), este modo usa un rango de bitrate baseado no bitrate máximo e ignora CRF. Para VP9, pódese usar CRF se o bitrate máximo está desactivado.", "transcoding_video_codec": "Códec de vídeo", "transcoding_video_codec_description": "VP9 ten alta eficiencia e compatibilidade web, pero tarda máis en transcodificarse. HEVC ten un rendemento similar, pero ten menor compatibilidade web. H.264 é amplamente compatible e rápido de transcodificar, pero produce ficheiros moito máis grandes. AV1 é o códec máis eficiente pero carece de soporte en dispositivos máis antigos.", "trash_enabled_description": "Activar funcións do Lixo", @@ -358,6 +369,9 @@ "trash_number_of_days_description": "Número de días para manter os activos no lixo antes de eliminalos permanentemente", "trash_settings": "Configuración do Lixo", "trash_settings_description": "Xestionar a configuración do lixo", + "unlink_all_oauth_accounts": "Desvincular todas as contas OAuth", + "unlink_all_oauth_accounts_description": "Lembre desvincular todas as contas OAuth antes de migrar a un novo provedor.", + "unlink_all_oauth_accounts_prompt": "Está seguro de que quere desvincular todas as contas OAuth? Isto reiniciará o ID de OAuth de cada usuario e non se poderá desfacer.", "user_cleanup_job": "Limpeza de usuarios", "user_delete_delay": "A conta e os activos de {user} programaranse para a súa eliminación permanente en {delay, plural, one {# día} other {# días}}.", "user_delete_delay_settings": "Atraso na eliminación", @@ -384,13 +398,15 @@ "admin_password": "Contrasinal do administrador", "administration": "Administración", "advanced": "Avanzado", - "advanced_settings_enable_alternate_media_filter_subtitle": "Usa esta opción para filtrar medios durante a sincronización baseándose en criterios alternativos. Só proba isto se tes problemas coa aplicación detectando todos os álbums.", + "advanced_settings_enable_alternate_media_filter_subtitle": "Use esta opción para filtrar medios durante a sincronización baseándose en criterios alternativos. Só probe isto se ten problemas coa aplicación para detectar todos os álbums.", "advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTAL] Usar filtro alternativo de sincronización de álbums do dispositivo", "advanced_settings_log_level_title": "Nivel de rexistro: {level}", - "advanced_settings_prefer_remote_subtitle": "Algúns dispositivos son extremadamente lentos para cargar miniaturas de activos no dispositivo. Active esta configuración para cargar imaxes remotas no seu lugar.", + "advanced_settings_prefer_remote_subtitle": "Algúns dispositivos tardan moito en cargar as miniaturas de ficheiros locais. Active esta opción para cargar imaxes remotas no seu lugar.", "advanced_settings_prefer_remote_title": "Preferir imaxes remotas", "advanced_settings_proxy_headers_subtitle": "Definir cabeceiras de proxy que Immich debería enviar con cada solicitude de rede", "advanced_settings_proxy_headers_title": "Cabeceiras de Proxy", + "advanced_settings_readonly_mode_subtitle": "Activa o modo de só lectura, no que as fotos só se poden visualizar; opcións como seleccionar varias imaxes, compartir, enviar a outros dispositivos ou eliminar están deshabilitadas. Active/desactive o modo de só lectura a través do avatar do usuario na pantalla principal", + "advanced_settings_readonly_mode_title": "Modo de só lectura", "advanced_settings_self_signed_ssl_subtitle": "Omite a verificación do certificado SSL para o punto final do servidor. Requirido para certificados autofirmados.", "advanced_settings_self_signed_ssl_title": "Permitir certificados SSL autofirmados", "advanced_settings_sync_remote_deletions_subtitle": "Eliminar ou restaurar automaticamente un activo neste dispositivo cando esa acción se realiza na web", @@ -398,32 +414,35 @@ "advanced_settings_tile_subtitle": "Configuración de usuario avanzado", "advanced_settings_troubleshooting_subtitle": "Activar funcións adicionais para a resolución de problemas", "advanced_settings_troubleshooting_title": "Resolución de problemas", - "age_months": "Idade {months, plural, one {# mes} other {# meses}}", - "age_year_months": "Idade 1 ano, {months, plural, one {# mes} other {# meses}}", - "age_years": "{years, plural, other {Idade #}}", + "age_months": "Idade: {months, plural, one {# mes} other {# meses}}", + "age_year_months": "Idade: 1 ano e {months, plural, one {# mes} other {# meses}}", + "age_years": "Idade: {years, plural, one {# ano} other {# anos}}", "album_added": "Álbum engadido", - "album_added_notification_setting_description": "Recibir unha notificación por correo electrónico cando sexas engadido a un álbum compartido", + "album_added_notification_setting_description": "Recibir unha notificación por correo electrónico cando sexa engadido a un álbum compartido", "album_cover_updated": "Portada do álbum actualizada", - "album_delete_confirmation": "Estás seguro de que queres eliminar o álbum {album}?", + "album_delete_confirmation": "Está seguro de que quere eliminar o álbum {album}?", "album_delete_confirmation_description": "Se este álbum está compartido, outros usuarios non poderán acceder a el.", + "album_deleted": "Álbum eliminado", "album_info_card_backup_album_excluded": "EXCLUÍDO", "album_info_card_backup_album_included": "INCLUÍDO", "album_info_updated": "Información do álbum actualizada", "album_leave": "Saír do álbum?", - "album_leave_confirmation": "Estás seguro de que queres saír de {album}?", + "album_leave_confirmation": "Está seguro de que quere saír de {album}?", "album_name": "Nome do Álbum", "album_options": "Opcións do álbum", "album_remove_user": "Eliminar usuario?", - "album_remove_user_confirmation": "Estás seguro de que queres eliminar a {user}?", - "album_share_no_users": "Parece que compartiches este álbum con todos os usuarios ou non tes ningún usuario co que compartir.", + "album_remove_user_confirmation": "Está seguro de que quere eliminar a {user}?", + "album_search_not_found": "Non se atoparon álbums que coincidan coa súa busca", + "album_share_no_users": "Parece que compartiu este álbum con todos os usuarios ou non ten ningún usuario co que compartir.", + "album_summary": "Resumo do álbum", "album_updated": "Álbum actualizado", "album_updated_setting_description": "Recibir unha notificación por correo electrónico cando un álbum compartido teña novos activos", "album_user_left": "Saíu de {album}", - "album_user_removed": "Eliminado {user}", - "album_viewer_appbar_delete_confirm": "Estás seguro de que queres eliminar este álbum da túa conta?", + "album_user_removed": "Eliminouse a {user}", + "album_viewer_appbar_delete_confirm": "Está seguro de que quere eliminar este álbum da súa conta?", "album_viewer_appbar_share_err_delete": "Erro ao eliminar o álbum", "album_viewer_appbar_share_err_leave": "Erro ao saír do álbum", - "album_viewer_appbar_share_err_remove": "Hai problemas ao eliminar activos do álbum", + "album_viewer_appbar_share_err_remove": "Houbo problemas ao eliminar activos do álbum", "album_viewer_appbar_share_err_title": "Erro ao cambiar o título do álbum", "album_viewer_appbar_share_leave": "Saír do álbum", "album_viewer_appbar_share_to": "Compartir con", @@ -431,6 +450,10 @@ "album_with_link_access": "Permitir que calquera persoa coa ligazón vexa fotos e persoas neste álbum.", "albums": "Álbums", "albums_count": "{count, plural, one {{count, number} Álbum} other {{count, number} Álbums}}", + "albums_default_sort_order": "Orde de clasificación predeterminada do álbum", + "albums_default_sort_order_description": "Orde inicial dos ficheiros ao crear novos álbums.", + "albums_feature_description": "Coleccións de ficheiros que se poden compartir con outros usuarios.", + "albums_on_device_count": "Álbums no dispositivo ({count})", "all": "Todo", "all_albums": "Todos os álbums", "all_people": "Todas as persoas", @@ -445,12 +468,16 @@ "api_key_description": "Este valor só se mostrará unha vez. Asegúrese de copialo antes de pechar a xanela.", "api_key_empty": "O nome da súa chave API non pode estar baleiro", "api_keys": "Chaves API", - "app_bar_signout_dialog_content": "Estás seguro de que queres pechar sesión?", + "app_architecture_variant": "Variante (Arquitectura)", + "app_bar_signout_dialog_content": "Está seguro de que quere pechar sesión?", "app_bar_signout_dialog_ok": "Si", "app_bar_signout_dialog_title": "Pechar sesión", + "app_download_links": "Ligazóns de Descarga da Aplicación", "app_settings": "Configuración da Aplicación", "appears_in": "Aparece en", + "apply_count": "Aplicar ({count, number})", "archive": "Arquivo", + "archive_action_prompt": "{count} engadido(s) ao Arquivo", "archive_or_unarchive_photo": "Arquivar ou desarquivar foto", "archive_page_no_archived_assets": "Non se atoparon activos arquivados", "archive_page_title": "Arquivo ({count})", @@ -459,14 +486,14 @@ "archived": "Arquivado", "archived_count": "{count, plural, other {Arquivados #}}", "are_these_the_same_person": "Son estas a mesma persoa?", - "are_you_sure_to_do_this": "Estás seguro de que queres facer isto?", + "are_you_sure_to_do_this": "Está seguro de que quere facer isto?", "asset_action_delete_err_read_only": "Non se poden eliminar activo(s) de só lectura, omitindo", "asset_action_share_err_offline": "Non se poden obter activo(s) fóra de liña, omitindo", "asset_added_to_album": "Engadido ao álbum", "asset_adding_to_album": "Engadindo ao álbum…", "asset_description_updated": "A descrición do activo actualizouse", "asset_filename_is_offline": "O activo {filename} está fóra de liña", - "asset_has_unassigned_faces": "O activo ten caras non asignadas", + "asset_has_unassigned_faces": "O activo ten caras sen asignar", "asset_hashing": "Calculando hash…", "asset_list_group_by_sub_title": "Agrupar por", "asset_list_layout_settings_dynamic_layout_title": "Deseño dinámico", @@ -477,59 +504,71 @@ "asset_list_settings_subtitle": "Configuración do deseño da grella de fotos", "asset_list_settings_title": "Grella de Fotos", "asset_offline": "Activo Fóra de Liña", - "asset_offline_description": "Este activo externo xa non se atopa no disco. Por favor, contacta co teu administrador de Immich para obter axuda.", + "asset_offline_description": "Este activo externo xa non se atopa no disco. Por favor, contacte co seu administrador de Immich para obter axuda.", "asset_restored_successfully": "Activo restaurado correctamente", "asset_skipped": "Omitido", "asset_skipped_in_trash": "No lixo", + "asset_trashed": "Ficheiro enviado ao lixo", + "asset_troubleshoot": "Solución de problemas do ficheiro", "asset_uploaded": "Subido", "asset_uploading": "Subindo…", - "asset_viewer_settings_subtitle": "Xestionar a túa configuración do visor da galería", + "asset_viewer_settings_subtitle": "Xestionar a súa configuración do visor da galería", "asset_viewer_settings_title": "Visor de Activos", "assets": "Activos", "assets_added_count": "Engadido {count, plural, one {# activo} other {# activos}}", "assets_added_to_album_count": "Engadido {count, plural, one {# activo} other {# activos}} ao álbum", + "assets_added_to_albums_count": "Engadido {assetTotal, plural, one {# ficheiro} other {# ficheiros}} a {albumTotal, plural, one {# álbum} other {# álbums}}", + "assets_cannot_be_added_to_album_count": "{count, plural, one {O ficheiro non se pode engadir} other {Os ficheiros non se poden engadir}} ao álbum", + "assets_cannot_be_added_to_albums": "{count, plural, one {O ficheiro non se pode engadir} other {Os ficheiros non se poden engadir}} a ningún dos álbums", "assets_count": "{count, plural, one {# activo} other {# activos}}", "assets_deleted_permanently": "{count} activo(s) eliminado(s) permanentemente", "assets_deleted_permanently_from_server": "{count} activo(s) eliminado(s) permanentemente do servidor Immich", + "assets_downloaded_failed": "{count, plural, one {Descargouse # ficheiro - fallou 1 ficheiro} other {Descargáronse # ficheiros - fallaron {error} ficheiros}}", + "assets_downloaded_successfully": "{count, plural, one {Descargouse # ficheiro correctamente} other {Descargáronse # ficheiros correctamente}}", "assets_moved_to_trash_count": "Movido {count, plural, one {# activo} other {# activos}} ao lixo", "assets_permanently_deleted_count": "Eliminados permanentemente {count, plural, one {# activo} other {# activos}}", "assets_removed_count": "Eliminados {count, plural, one {# activo} other {# activos}}", - "assets_removed_permanently_from_device": "{count} activo(s) eliminado(s) permanentemente do teu dispositivo", - "assets_restore_confirmation": "Estás seguro de que queres restaurar todos os seus activos no lixo? Non podes desfacer esta acción! Ten en conta que calquera activo fóra de liña non pode ser restaurado desta maneira.", + "assets_removed_permanently_from_device": "{count} activo(s) eliminado(s) permanentemente do seu dispositivo", + "assets_restore_confirmation": "Está seguro de que quere restaurar todos os activos no lixo? Non pode desfacer esta acción! Teña en conta que calquera activo fóra de liña non pode ser restaurado desta maneira.", "assets_restored_count": "Restaurados {count, plural, one {# activo} other {# activos}}", "assets_restored_successfully": "{count} activo(s) restaurado(s) correctamente", "assets_trashed": "{count} activo(s) movido(s) ao lixo", "assets_trashed_count": "Movido {count, plural, one {# activo} other {# activos}} ao lixo", "assets_trashed_from_server": "{count} activo(s) movido(s) ao lixo desde o servidor Immich", "assets_were_part_of_album_count": "{count, plural, one {O activo xa era} other {Os activos xa eran}} parte do álbum", + "assets_were_part_of_albums_count": "{count, plural, one {O ficheiro xa estaba} other {Os ficheiros xa estaban}} neses álbums", "authorized_devices": "Dispositivos Autorizados", "automatic_endpoint_switching_subtitle": "Conectar localmente a través da wifi designada cando estea dispoñible e usar conexións alternativas noutros lugares", "automatic_endpoint_switching_title": "Cambio automático de URL", + "autoplay_slideshow": "Reprodución automática da presentación", "back": "Atrás", "back_close_deselect": "Atrás, pechar ou deseleccionar", - "background_location_permission": "Permiso de ubicación en segundo plano", - "background_location_permission_content": "Para cambiar de rede cando se executa en segundo plano, Immich debe ter *sempre* acceso á ubicación precisa para que a aplicación poida ler o nome da rede wifi", + "background_backup_running_error": "A copia de seguridade en segundo plano está en curso, non se pode iniciar unha copia manual", + "background_location_permission": "Permiso de localización en segundo plano", + "background_location_permission_content": "Para cambiar de rede cando se executa en segundo plano, Immich debe ter *sempre* acceso á localización precisa para que a aplicación poida ler o nome da rede wifi", + "background_options": "Opcións en segundo plano", "backup": "Copia de Seguridade", "backup_album_selection_page_albums_device": "Álbums no dispositivo ({count})", "backup_album_selection_page_albums_tap": "Tocar para incluír, dobre toque para excluír", - "backup_album_selection_page_assets_scatter": "Os activos poden dispersarse por varios álbums. Polo tanto, os álbums poden incluírse ou excluírse durante o proceso de copia de seguridade.", + "backup_album_selection_page_assets_scatter": "Os activos poden estar dispersos por varios álbums. Polo tanto, os álbums poden incluírse ou excluírse durante o proceso de copia de seguridade.", "backup_album_selection_page_select_albums": "Seleccionar álbums", "backup_album_selection_page_selection_info": "Información da selección", "backup_album_selection_page_total_assets": "Total de activos únicos", + "backup_albums_sync": "Sincronización de álbums da copia de seguridade", "backup_all": "Todo", "backup_background_service_backup_failed_message": "Erro ao facer copia de seguridade dos activos. Reintentando…", "backup_background_service_connection_failed_message": "Erro ao conectar co servidor. Reintentando…", "backup_background_service_current_upload_notification": "Subindo {filename}", "backup_background_service_default_notification": "Comprobando novos activos…", "backup_background_service_error_title": "Erro na copia de seguridade", - "backup_background_service_in_progress_notification": "Facendo copia de seguridade dos teus activos…", + "backup_background_service_in_progress_notification": "Facendo copia de seguridade dos seus activos…", "backup_background_service_upload_failure_notification": "Erro ao subir {filename}", "backup_controller_page_albums": "Álbums da Copia de Seguridade", "backup_controller_page_background_app_refresh_disabled_content": "Active a actualización de aplicacións en segundo plano en Axustes > Xeral > Actualización en segundo plano para usar a copia de seguridade en segundo plano.", "backup_controller_page_background_app_refresh_disabled_title": "Actualización de aplicacións en segundo plano desactivada", "backup_controller_page_background_app_refresh_enable_button_text": "Ir a axustes", - "backup_controller_page_background_battery_info_link": "Móstrame como", - "backup_controller_page_background_battery_info_message": "Para a mellor experiencia de copia de seguridade en segundo plano, desactiva calquera optimización de batería que restrinxa a actividade en segundo plano para Immich.\n\nDado que isto é específico do dispositivo, busque a información requirida para o fabricante do teu dispositivo.", + "backup_controller_page_background_battery_info_link": "Móstreme como", + "backup_controller_page_background_battery_info_message": "Para a mellor experiencia de copia de seguridade en segundo plano, desactive calquera optimización de batería que restrinxa a actividade en segundo plano para Immich.\n\nDado que isto é específico do dispositivo, busque a información requirida para o fabricante do seu dispositivo.", "backup_controller_page_background_battery_info_ok": "Aceptar", "backup_controller_page_background_battery_info_title": "Optimizacións da batería", "backup_controller_page_background_charging": "Só mentres se carga", @@ -565,29 +604,35 @@ "backup_controller_page_turn_on": "Activar copia de seguridade en primeiro plano", "backup_controller_page_uploading_file_info": "Subindo información do ficheiro", "backup_err_only_album": "Non se pode eliminar o único álbum", + "backup_error_sync_failed": "A sincronización fallou. Non se pode procesar a copia de seguridade.", "backup_info_card_assets": "activos", "backup_manual_cancelled": "Cancelado", - "backup_manual_in_progress": "Subida xa en progreso. Intenta despois dun tempo", + "backup_manual_in_progress": "Subida xa en progreso. Inténteo despois dun tempo", "backup_manual_success": "Éxito", "backup_manual_title": "Estado da subida", + "backup_options": "Opcións de copia de seguridade", "backup_options_page_title": "Opcións da copia de seguridade", "backup_setting_subtitle": "Xestionar a configuración de carga en segundo plano e primeiro plano", + "backup_settings_subtitle": "Xestionar configuración de subidas", "backward": "Atrás", "biometric_auth_enabled": "Autenticación biométrica activada", + "biometric_locked_out": "Está bloqueado da autenticación biométrica", + "biometric_no_options": "Non hai opcións biométricas dispoñibles", + "biometric_not_available": "A autenticación biométrica non está dispoñible neste dispositivo", "birthdate_saved": "Data de nacemento gardada correctamente", "birthdate_set_description": "A data de nacemento úsase para calcular a idade desta persoa no momento dunha foto.", "blurred_background": "Fondo borroso", "bugs_and_feature_requests": "Erros e Solicitudes de Funcións", "build": "Compilación", "build_image": "Construír Imaxe", - "bulk_delete_duplicates_confirmation": "Estás seguro de que queres eliminar masivamente {count, plural, one {# activo duplicado} other {# activos duplicados}}? Isto conservará o activo máis grande de cada grupo e eliminará permanentemente todos os demais duplicados. Non pode desfacer esta acción!", - "bulk_keep_duplicates_confirmation": "Estás seguro de que queres conservar {count, plural, one {# activo duplicado} other {# activos duplicados}}? Isto resolverá todos os grupos duplicados sen eliminar nada.", - "bulk_trash_duplicates_confirmation": "Estás seguro de que queres mover masivamente ao lixo {count, plural, one {# activo duplicado} other {# activos duplicados}}? Isto conservará o activo máis grande de cada grupo e moverá ao lixo todos os demais duplicados.", - "buy": "Comprar Immich", + "bulk_delete_duplicates_confirmation": "Está seguro de que quere eliminar masivamente {count, plural, one {# activo duplicado} other {# activos duplicados}}? Isto conservará o activo máis grande de cada grupo e eliminará permanentemente todos os demais duplicados. Non pode desfacer esta acción!", + "bulk_keep_duplicates_confirmation": "Está seguro de que quere conservar {count, plural, one {# activo duplicado} other {# activos duplicados}}? Isto resolverá todos os grupos duplicados sen eliminar nada.", + "bulk_trash_duplicates_confirmation": "Está seguro de que quere mover masivamente ao lixo {count, plural, one {# activo duplicado} other {# activos duplicados}}? Isto conservará o activo máis grande de cada grupo e moverá ao lixo todos os demais duplicados.", + "buy": "Apoiar Immich", "cache_settings_clear_cache_button": "Borrar caché", "cache_settings_clear_cache_button_title": "Borra a caché da aplicación. Isto afectará significativamente o rendemento da aplicación ata que a caché se reconstruíu.", "cache_settings_duplicated_assets_clear_button": "BORRAR", - "cache_settings_duplicated_assets_subtitle": "Fotos e vídeos que están na lista negra da aplicación", + "cache_settings_duplicated_assets_subtitle": "Fotos e vídeos que a aplicación ten na lista de ignorados", "cache_settings_duplicated_assets_title": "Activos Duplicados ({count})", "cache_settings_statistics_album": "Miniaturas da biblioteca", "cache_settings_statistics_full": "Imaxes completas", @@ -604,24 +649,31 @@ "cancel": "Cancelar", "cancel_search": "Cancelar busca", "canceled": "Cancelado", + "canceling": "Cancelando", "cannot_merge_people": "Non se poden fusionar persoas", "cannot_undo_this_action": "Non pode desfacer esta acción!", "cannot_update_the_description": "Non se pode actualizar a descrición", + "cast": "Enviar a dispositivo", + "cast_description": "Configurar destinos dispoñibles para enviar a dispositivo", "change_date": "Cambiar data", + "change_description": "Cambiar descrición", "change_display_order": "Cambiar orde de visualización", "change_expiration_time": "Cambiar hora de caducidade", - "change_location": "Cambiar ubicación", + "change_location": "Cambiar localización", "change_name": "Cambiar nome", "change_name_successfully": "Nome cambiado correctamente", "change_password": "Cambiar Contrasinal", - "change_password_description": "Esta é a primeira vez que inicias sesión no sistema ou solicitouse un cambio do teu contrasinal. Introduza o novo contrasinal a continuación.", + "change_password_description": "Esta é a primeira vez que inicia sesión no sistema ou solicitouse un cambio do seu contrasinal. Introduza o novo contrasinal a continuación.", "change_password_form_confirm_password": "Confirmar Contrasinal", - "change_password_form_description": "Ola {name},\n\nEsta é a primeira vez que inicias sesión no sistema ou solicitouse un cambio do teu contrasinal. Introduza o novo contrasinal a continuación.", + "change_password_form_description": "Ola {name},\n\nEsta é a primeira vez que inicia sesión no sistema ou solicitouse un cambio do seu contrasinal. Introduza o novo contrasinal a continuación.", "change_password_form_new_password": "Novo Contrasinal", "change_password_form_password_mismatch": "Os contrasinais non coinciden", "change_password_form_reenter_new_password": "Reintroducir Novo Contrasinal", - "change_your_password": "Cambiar o teu contrasinal", + "change_pin_code": "Cambiar código PIN", + "change_your_password": "Cambiar o seu contrasinal", "changed_visibility_successfully": "Visibilidade cambiada correctamente", + "charging": "Cargando", + "charging_requirement_mobile_backup": "A copia de seguridade en segundo plano require que o dispositivo estea cargando", "check_corrupt_asset_backup": "Comprobar copias de seguridade de activos corruptos", "check_corrupt_asset_backup_button": "Realizar comprobación", "check_corrupt_asset_backup_description": "Execute esta comprobación só a través da wifi e unha vez que todos os activos teñan copia de seguridade. O procedemento pode tardar uns minutos.", @@ -631,6 +683,7 @@ "clear": "Limpar", "clear_all": "Limpar todo", "clear_all_recent_searches": "Limpar todas as buscas recentes", + "clear_file_cache": "Limpar caché de ficheiros", "clear_message": "Limpar mensaxe", "clear_value": "Limpar valor", "client_cert_dialog_msg_confirm": "Aceptar", @@ -652,21 +705,25 @@ "comments_and_likes": "Comentarios e Gústames", "comments_are_disabled": "Os comentarios están desactivados", "common_create_new_album": "Crear novo álbum", - "common_server_error": "Por favor, comprobe a túa conexión de rede, asegúrache de que o servidor sexa accesible e que as versións da aplicación/servidor sexan compatibles.", "completed": "Completado", "confirm": "Confirmar", "confirm_admin_password": "Confirmar Contrasinal do Administrador", - "confirm_delete_face": "Estás seguro de que queres eliminar a cara de {name} do activo?", - "confirm_delete_shared_link": "Estás seguro de que queres eliminar esta ligazón compartida?", - "confirm_keep_this_delete_others": "Todos os demais activos na pila eliminaranse excepto este activo. Estás seguro de que queres continuar?", + "confirm_delete_face": "Está seguro de que quere eliminar a cara de {name} do activo?", + "confirm_delete_shared_link": "Está seguro de que quere eliminar esta ligazón compartida?", + "confirm_keep_this_delete_others": "Todos os demais activos na pila eliminaranse excepto este activo. Está seguro de que quere continuar?", + "confirm_new_pin_code": "Confirmar novo código PIN", "confirm_password": "Confirmar contrasinal", + "confirm_tag_face": "Quere etiquetar esta cara como {name}?", + "confirm_tag_face_unnamed": "Quere etiquetar esta cara?", + "connected_device": "Dispositivo conectado", + "connected_to": "Conectado a", "contain": "Conter", "context": "Contexto", "continue": "Continuar", "control_bottom_app_bar_create_new_album": "Crear novo álbum", "control_bottom_app_bar_delete_from_immich": "Eliminar de Immich", "control_bottom_app_bar_delete_from_local": "Eliminar do dispositivo", - "control_bottom_app_bar_edit_location": "Editar ubicación", + "control_bottom_app_bar_edit_location": "Editar localización", "control_bottom_app_bar_edit_time": "Editar Data e Hora", "control_bottom_app_bar_share_link": "Compartir Ligazón", "control_bottom_app_bar_share_to": "Compartir Con", @@ -696,19 +753,25 @@ "create_new_user": "Crear novo usuario", "create_shared_album_page_share_add_assets": "ENGADIR ACTIVOS", "create_shared_album_page_share_select_photos": "Seleccionar Fotos", + "create_shared_link": "Crear ligazón compartida", "create_tag": "Crear etiqueta", "create_tag_description": "Crear unha nova etiqueta. Para etiquetas aniñadas, introduza a ruta completa da etiqueta incluíndo barras inclinadas.", "create_user": "Crear usuario", "created": "Creado", + "created_at": "Creado", + "creating_linked_albums": "Creando álbums vinculados...", "crop": "Recortar", "curated_object_page_title": "Cousas", "current_device": "Dispositivo actual", + "current_pin_code": "Código PIN actual", "current_server_address": "Enderezo do servidor actual", "custom_locale": "Configuración Rexional Personalizada", "custom_locale_description": "Formatar datas e números baseándose na lingua e a rexión", + "custom_url": "URL personalizada", "daily_title_text_date": "E, dd MMM", "daily_title_text_date_year": "E, dd MMM, yyyy", "dark": "Escuro", + "dark_theme": "Alternar tema escuro", "date_after": "Data posterior a", "date_and_time": "Data e Hora", "date_before": "Data anterior a", @@ -716,45 +779,54 @@ "date_of_birth_saved": "Data de nacemento gardada correctamente", "date_range": "Rango de datas", "day": "Día", - "deduplicate_all": "Eliminar Duplicados Todos", + "days": "Días", + "deduplicate_all": "Eliminar todos os duplicados", "deduplication_criteria_1": "Tamaño da imaxe en bytes", "deduplication_criteria_2": "Reconto de datos EXIF", "deduplication_info": "Información de Deduplicación", "deduplication_info_description": "Para preseleccionar automaticamente activos e eliminar duplicados masivamente, miramos:", "default_locale": "Configuración Rexional Predeterminada", - "default_locale_description": "Formatar datas e números baseándose na configuración rexional do teu navegador", + "default_locale_description": "Formatar datas e números baseándose na configuración rexional do seu navegador", "delete": "Eliminar", + "delete_action_confirmation_message": "Está seguro de que quere eliminar este ficheiro? Esta acción moverá o ficheiro ao lixo do servidor e preguntaralle se tamén quere eliminalo localmente", + "delete_action_prompt": "{count} eliminado(s)", "delete_album": "Eliminar álbum", - "delete_api_key_prompt": "Estás seguro de que queres eliminar esta chave API?", - "delete_dialog_alert": "Estes elementos eliminaranse permanentemente de Immich e do teu dispositivo", - "delete_dialog_alert_local": "Estes elementos eliminaranse permanentemente do teu dispositivo pero aínda estarán dispoñibles no servidor Immich", - "delete_dialog_alert_local_non_backed_up": "Algúns dos elementos non teñen copia de seguridade en Immich e eliminaranse permanentemente do teu dispositivo", + "delete_api_key_prompt": "Está seguro de que quere eliminar esta chave API?", + "delete_dialog_alert": "Estes elementos eliminaranse permanentemente de Immich e do seu dispositivo", + "delete_dialog_alert_local": "Estes elementos eliminaranse permanentemente do seu dispositivo pero aínda estarán dispoñibles no servidor Immich", + "delete_dialog_alert_local_non_backed_up": "Algúns dos elementos non teñen copia de seguridade en Immich e eliminaranse permanentemente do seu dispositivo", "delete_dialog_alert_remote": "Estes elementos eliminaranse permanentemente do servidor Immich", "delete_dialog_ok_force": "Eliminar Igualmente", "delete_dialog_title": "Eliminar Permanentemente", - "delete_duplicates_confirmation": "Estás seguro de que queres eliminar permanentemente estes duplicados?", + "delete_duplicates_confirmation": "Está seguro de que quere eliminar permanentemente estes duplicados?", "delete_face": "Eliminar cara", "delete_key": "Eliminar chave", "delete_library": "Eliminar Biblioteca", "delete_link": "Eliminar ligazón", + "delete_local_action_prompt": "{count} eliminado(s) localmente", "delete_local_dialog_ok_backed_up_only": "Eliminar Só con Copia de Seguridade", "delete_local_dialog_ok_force": "Eliminar Igualmente", "delete_others": "Eliminar outros", + "delete_permanently": "Eliminar permanentemente", + "delete_permanently_action_prompt": "{count} eliminado(s) permanentemente", "delete_shared_link": "Eliminar ligazón compartida", "delete_shared_link_dialog_title": "Eliminar Ligazón Compartida", "delete_tag": "Eliminar etiqueta", - "delete_tag_confirmation_prompt": "Estás seguro de que queres eliminar a etiqueta {tagName}?", + "delete_tag_confirmation_prompt": "Está seguro de que quere eliminar a etiqueta {tagName}?", "delete_user": "Eliminar usuario", "deleted_shared_link": "Ligazón compartida eliminada", "deletes_missing_assets": "Elimina activos que faltan no disco", "description": "Descrición", "description_input_hint_text": "Engadir descrición...", "description_input_submit_error": "Erro ao actualizar a descrición, comprobe o rexistro para máis detalles", + "deselect_all": "Deseleccionar todo", "details": "Detalles", "direction": "Dirección", "disabled": "Desactivado", "disallow_edits": "Non permitir edicións", + "discord": "Discord", "discover": "Descubrir", + "discovered_devices": "Dispositivos descubertos", "dismiss_all_errors": "Descartar todos os erros", "dismiss_error": "Descartar erro", "display_options": "Opcións de visualización", @@ -765,6 +837,7 @@ "documentation": "Documentación", "done": "Feito", "download": "Descargar", + "download_action_prompt": "Descargando {count} activo(s)", "download_canceled": "Descarga cancelada", "download_complete": "Descarga completada", "download_enqueue": "Descarga en cola", @@ -791,40 +864,55 @@ "edit": "Editar", "edit_album": "Editar álbum", "edit_avatar": "Editar avatar", + "edit_birthday": "Editar aniversario", "edit_date": "Editar data", "edit_date_and_time": "Editar data e hora", - "edit_exclusion_pattern": "Editar padrón de exclusión", + "edit_date_and_time_action_prompt": "{count} data(s) e hora(s) editada(s)", + "edit_date_and_time_by_offset": "Cambiar data por desfase", + "edit_date_and_time_by_offset_interval": "Nova franxa de datas: {from} - {to}", + "edit_description": "Editar descrición", + "edit_description_prompt": "Por favor, seleccione unha nova descrición:", + "edit_exclusion_pattern": "Editar patrón de exclusión", "edit_faces": "Editar caras", "edit_import_path": "Editar ruta de importación", "edit_import_paths": "Editar Rutas de Importación", "edit_key": "Editar chave", "edit_link": "Editar ligazón", - "edit_location": "Editar ubicación", - "edit_location_dialog_title": "Ubicación", + "edit_location": "Editar localización", + "edit_location_action_prompt": "{count} localización(s) editada(s)", + "edit_location_dialog_title": "Localización", "edit_name": "Editar nome", "edit_people": "Editar persoas", "edit_tag": "Editar etiqueta", "edit_title": "Editar Título", "edit_user": "Editar usuario", - "edited": "Editado", + "editor": "Editor", "editor_close_without_save_prompt": "Os cambios non se gardarán", "editor_close_without_save_title": "Pechar editor?", "editor_crop_tool_h2_aspect_ratios": "Proporcións de aspecto", "editor_crop_tool_h2_rotation": "Rotación", "email": "Correo electrónico", + "email_notifications": "Notificacións por correo electrónico", "empty_folder": "Este cartafol está baleiro", "empty_trash": "Baleirar lixo", - "empty_trash_confirmation": "Estás seguro de que queres baleirar o lixo? Isto eliminará permanentemente todos os activos no lixo de Immich. Non podes desfacer esta acción!", + "empty_trash_confirmation": "Está seguro de que quere baleirar o lixo? Isto eliminará permanentemente todos os activos no lixo de Immich. Non pode desfacer esta acción!", "enable": "Activar", + "enable_backup": "Activar copia de seguridade", + "enable_biometric_auth_description": "Introduza o seu código PIN para activar a autenticación biométrica", "enabled": "Activado", "end_date": "Data de fin", "enqueued": "En cola", "enter_wifi_name": "Introducir nome da wifi", + "enter_your_pin_code": "Introduza o seu código PIN", + "enter_your_pin_code_subtitle": "Introduza o seu código PIN para acceder ao cartafol bloqueado", "error": "Erro", "error_change_sort_album": "Erro ao cambiar a orde de clasificación do álbum", "error_delete_face": "Erro ao eliminar a cara do activo", + "error_getting_places": "Erro ao obter lugares", "error_loading_image": "Erro ao cargar a imaxe", + "error_loading_partners": "Erro cargando compañeiros/as: {error}", "error_saving_image": "Erro: {error}", + "error_tag_face_bounding_box": "Erro ao etiquetar cara - non se poden obter as coordenadas da caixa delimitadora", "error_title": "Erro - Algo saíu mal", "errors": { "cannot_navigate_next_asset": "Non se pode navegar ao seguinte activo", @@ -841,10 +929,10 @@ "error_adding_users_to_album": "Erro ao engadir usuarios ao álbum", "error_deleting_shared_user": "Erro ao eliminar o usuario compartido", "error_downloading": "Erro ao descargar {filename}", - "error_hiding_buy_button": "Erro ao ocultar o botón de compra", + "error_hiding_buy_button": "Erro ao ocultar o botón de apoio", "error_removing_assets_from_album": "Erro ao eliminar activos do álbum, comprobe a consola para máis detalles", "error_selecting_all_assets": "Erro ao seleccionar todos os activos", - "exclusion_pattern_already_exists": "Este padrón de exclusión xa existe.", + "exclusion_pattern_already_exists": "Este patrón de exclusión xa existe.", "failed_to_create_album": "Erro ao crear o álbum", "failed_to_create_shared_link": "Erro ao crear a ligazón compartida", "failed_to_edit_shared_link": "Erro ao editar a ligazón compartida", @@ -855,6 +943,7 @@ "failed_to_load_notifications": "Erro ao cargar as notificacións", "failed_to_load_people": "Erro ao cargar persoas", "failed_to_remove_product_key": "Erro ao eliminar a chave do produto", + "failed_to_reset_pin_code": "Erro ao restablecer o código PIN", "failed_to_stack_assets": "Erro ao apilar activos", "failed_to_unstack_assets": "Erro ao desapilar activos", "failed_to_update_notification_status": "Erro ao actualizar o estado das notificacións", @@ -863,10 +952,11 @@ "paths_validation_failed": "{paths, plural, one {# ruta fallou} other {# rutas fallaron}} na validación", "profile_picture_transparent_pixels": "As imaxes de perfil non poden ter píxeles transparentes. Por favor, faga zoom e/ou mova a imaxe.", "quota_higher_than_disk_size": "Estableceu unha cota superior ao tamaño do disco", + "something_went_wrong": "Algo fallou", "unable_to_add_album_users": "Non se puideron engadir usuarios ao álbum", "unable_to_add_assets_to_shared_link": "Non se puideron engadir activos á ligazón compartida", "unable_to_add_comment": "Non se puido engadir o comentario", - "unable_to_add_exclusion_pattern": "Non se puido engadir o padrón de exclusión", + "unable_to_add_exclusion_pattern": "Non se puido engadir o patrón de exclusión", "unable_to_add_import_path": "Non se puido engadir a ruta de importación", "unable_to_add_partners": "Non se puideron engadir compañeiros/as", "unable_to_add_remove_archive": "Non se puido {archived, select, true {eliminar activo do} other {engadir activo ao}} arquivo", @@ -874,13 +964,14 @@ "unable_to_archive_unarchive": "Non se puido {archived, select, true {arquivar} other {desarquivar}}", "unable_to_change_album_user_role": "Non se puido cambiar o rol do usuario do álbum", "unable_to_change_date": "Non se puido cambiar a data", + "unable_to_change_description": "Non se puido cambiar a descrición", "unable_to_change_favorite": "Non se puido cambiar o favorito do activo", - "unable_to_change_location": "Non se puido cambiar a ubicación", + "unable_to_change_location": "Non se puido cambiar a localización", "unable_to_change_password": "Non se puido cambiar o contrasinal", "unable_to_change_visibility": "Non se puido cambiar a visibilidade para {count, plural, one {# persoa} other {# persoas}}", "unable_to_complete_oauth_login": "Non se puido completar o inicio de sesión OAuth", "unable_to_connect": "Non se puido conectar", - "unable_to_copy_to_clipboard": "Non se puido copiar ao portapapeis, asegúrate de acceder á páxina a través de https", + "unable_to_copy_to_clipboard": "Non se puido copiar ao portapapeis, asegúrese de acceder á páxina a través de https", "unable_to_create_admin_account": "Non se puido crear a conta de administrador", "unable_to_create_api_key": "Non se puido crear unha nova Chave API", "unable_to_create_library": "Non se puido crear a biblioteca", @@ -888,12 +979,12 @@ "unable_to_delete_album": "Non se puido eliminar o álbum", "unable_to_delete_asset": "Non se puido eliminar o activo", "unable_to_delete_assets": "Erro ao eliminar activos", - "unable_to_delete_exclusion_pattern": "Non se puido eliminar o padrón de exclusión", + "unable_to_delete_exclusion_pattern": "Non se puido eliminar o patrón de exclusión", "unable_to_delete_import_path": "Non se puido eliminar a ruta de importación", "unable_to_delete_shared_link": "Non se puido eliminar a ligazón compartida", "unable_to_delete_user": "Non se puido eliminar o usuario", "unable_to_download_files": "Non se puideron descargar os ficheiros", - "unable_to_edit_exclusion_pattern": "Non se puido editar o padrón de exclusión", + "unable_to_edit_exclusion_pattern": "Non se puido editar o patrón de exclusión", "unable_to_edit_import_path": "Non se puido editar a ruta de importación", "unable_to_empty_trash": "Non se puido baleirar o lixo", "unable_to_enter_fullscreen": "Non se puido entrar en pantalla completa", @@ -917,6 +1008,7 @@ "unable_to_remove_partner": "Non se puido eliminar o/a compañeiro/a", "unable_to_remove_reaction": "Non se puido eliminar a reacción", "unable_to_reset_password": "Non se puido restablecer o contrasinal", + "unable_to_reset_pin_code": "Non é posible reiniciar o código PIN", "unable_to_resolve_duplicate": "Non se puido resolver o duplicado", "unable_to_restore_assets": "Non se puideron restaurar os activos", "unable_to_restore_trash": "Non se puido restaurar o lixo", @@ -938,22 +1030,26 @@ "unable_to_update_album_cover": "Non se puido actualizar a portada do álbum", "unable_to_update_album_info": "Non se puido actualizar a información do álbum", "unable_to_update_library": "Non se puido actualizar a biblioteca", - "unable_to_update_location": "Non se puido actualizar a ubicación", + "unable_to_update_location": "Non se puido actualizar a localización", "unable_to_update_settings": "Non se puido actualizar a configuración", "unable_to_update_timeline_display_status": "Non se puido actualizar o estado de visualización da liña de tempo", "unable_to_update_user": "Non se puido actualizar o usuario", "unable_to_upload_file": "Non se puido cargar o ficheiro" }, + "exif": "Exif", "exif_bottom_sheet_description": "Engadir Descrición...", + "exif_bottom_sheet_description_error": "Erro ao actualizar a descrición", "exif_bottom_sheet_details": "DETALLES", - "exif_bottom_sheet_location": "UBICACIÓN", + "exif_bottom_sheet_location": "LOCALIZACIÓN", + "exif_bottom_sheet_no_description": "Sen descrición", "exif_bottom_sheet_people": "PERSOAS", "exif_bottom_sheet_person_add_person": "Engadir nome", "exit_slideshow": "Saír da Presentación", "expand_all": "Expandir todo", "experimental_settings_new_asset_list_subtitle": "Traballo en progreso", "experimental_settings_new_asset_list_title": "Activar grella de fotos experimental", - "experimental_settings_subtitle": "Use baixo o teu propio risco!", + "experimental_settings_subtitle": "Use baixo o seu propio risco!", + "experimental_settings_title": "Experimental", "expire_after": "Caduca despois de", "expired": "Caducado", "expires_date": "Caduca o {date}", @@ -961,6 +1057,8 @@ "explorer": "Explorador", "export": "Exportar", "export_as_json": "Exportar como JSON", + "export_database": "Exportar a Base de Datos", + "export_database_description": "Exportar a base de datos SQLite", "extension": "Extensión", "external": "Externo", "external_libraries": "Bibliotecas Externas", @@ -968,14 +1066,17 @@ "external_network_sheet_info": "Cando non estea na rede wifi preferida, a aplicación conectarase ao servidor a través da primeira das seguintes URLs que poida alcanzar, comezando de arriba a abaixo", "face_unassigned": "Sen asignar", "failed": "Fallado", + "failed_to_authenticate": "Fallou a autenticación", "failed_to_load_assets": "Erro ao cargar activos", "failed_to_load_folder": "Erro ao cargar o cartafol", "favorite": "Favorito", + "favorite_action_prompt": "{count} engadido/a a Favoritos", "favorite_or_unfavorite_photo": "Marcar ou desmarcar como favorito", "favorites": "Favoritos", "favorites_page_no_favorites": "Non se atoparon activos favoritos", "feature_photo_updated": "Foto destacada actualizada", "features": "Funcións", + "features_in_development": "Funcionalidades en Desenvolvemento", "features_setting_description": "Xestionar as funcións da aplicación", "file_name": "Nome do ficheiro", "file_name_or_extension": "Nome do ficheiro ou extensión", @@ -984,20 +1085,27 @@ "filter": "Filtro", "filter_people": "Filtrar persoas", "filter_places": "Filtrar lugares", - "find_them_fast": "Atópaos rápido por nome coa busca", + "find_them_fast": "Atópeos rápido por nome coa busca", + "first": "Primeiro/a", "fix_incorrect_match": "Corrixir coincidencia incorrecta", "folder": "Cartafol", "folder_not_found": "Cartafol non atopado", "folders": "Cartafoles", "folders_feature_description": "Navegar pola vista de cartafoles para as fotos e vídeos no sistema de ficheiros", + "forgot_pin_code_question": "Esqueceu o seu PIN?", "forward": "Adiante", + "gcast_enabled": "Google Cast", + "gcast_enabled_description": "Esta funcionalidade carga recursos externos de Google para poder funcionar.", "general": "Xeral", + "geolocation_instruction_location": "Prema nun recurso con coordenadas GPS para usar a súa localización, ou seleccione unha localización directamente no mapa", "get_help": "Obter Axuda", - "get_wifiname_error": "Non se puido obter o nome da wifi. Asegúrate de que concedeu os permisos necesarios e está conectado a unha rede wifi", + "get_wifiname_error": "Non se puido obter o nome da wifi. Asegúrese de que concedeu os permisos necesarios e está conectado a unha rede wifi", "getting_started": "Primeiros Pasos", "go_back": "Volver", "go_to_folder": "Ir ao cartafol", "go_to_search": "Ir á busca", + "gps": "GPS", + "gps_missing": "Sen GPS", "grant_permission": "Conceder permiso", "group_albums_by": "Agrupar álbums por...", "group_country": "Agrupar por país", @@ -1008,16 +1116,18 @@ "haptic_feedback_switch": "Activar resposta háptica", "haptic_feedback_title": "Resposta Háptica", "has_quota": "Ten cota", + "hash_asset": "Facer hash do recurso", + "hashed_assets": "Recursos cun hash", + "hashing": "Aplicando hash", "header_settings_add_header_tip": "Engadir Cabeceira", "header_settings_field_validator_msg": "O valor non pode estar baleiro", "header_settings_header_name_input": "Nome da cabeceira", "header_settings_header_value_input": "Valor da cabeceira", - "headers_settings_tile_subtitle": "Definir cabeceiras de proxy que a aplicación debería enviar con cada solicitude de rede", "headers_settings_tile_title": "Cabeceiras de proxy personalizadas", "hi_user": "Ola {name} ({email})", "hide_all_people": "Ocultar todas as persoas", "hide_gallery": "Ocultar galería", - "hide_named_person": "Ocultar persoa {name}", + "hide_named_person": "Ocultar a persoa {name}", "hide_password": "Ocultar contrasinal", "hide_person": "Ocultar persoa", "hide_unnamed_people": "Ocultar persoas sen nome", @@ -1032,10 +1142,16 @@ "home_page_delete_remote_err_local": "Activos locais na selección de eliminación remota, omitindo", "home_page_favorite_err_local": "Non se poden marcar como favoritos activos locais aínda, omitindo", "home_page_favorite_err_partner": "Non se poden marcar como favoritos activos de compañeiro/a aínda, omitindo", - "home_page_first_time_notice": "Se esta é a primeira vez que usas a aplicación, asegúrate de elixir un álbum de copia de seguridade para que a liña de tempo poida encherse con fotos e vídeos nel", + "home_page_first_time_notice": "Se esta é a primeira vez que usa a aplicación, asegúrese de elixir un álbum de copia de seguridade para que a liña de tempo poida encherse con fotos e vídeos nel", + "home_page_locked_error_local": "Non é posíbel mover os recursos locais ao cartafol bloqueado, saltando", + "home_page_locked_error_partner": "Non é posíbel mover os recursos do/a colaborador/a ao cartafol bloqueado, saltando", "home_page_share_err_local": "Non se poden compartir activos locais mediante ligazón, omitindo", "home_page_upload_err_limit": "Só se pode cargar un máximo de 30 activos á vez, omitindo", + "host": "Servidor", "hour": "Hora", + "hours": "Horas", + "id": "ID", + "idle": "Inactivo/a", "ignore_icloud_photos": "Ignorar fotos de iCloud", "ignore_icloud_photos_description": "As fotos que están almacenadas en iCloud non se cargarán ao servidor Immich", "image": "Imaxe", @@ -1063,7 +1179,7 @@ "include_shared_albums": "Incluír álbums compartidos", "include_shared_partner_assets": "Incluír activos de compañeiro/a compartidos", "individual_share": "Compartir individual", - "individual_shares": "Compartires individuais", + "individual_shares": "Comparticións individuais", "info": "Información", "interval": { "day_at_onepm": "Todos os días ás 13:00", @@ -1075,6 +1191,12 @@ "invalid_date_format": "Formato de data inválido", "invite_people": "Invitar Persoas", "invite_to_album": "Invitar ao álbum", + "ios_debug_info_fetch_ran_at": "Obtívose ás {dateTime}", + "ios_debug_info_last_sync_at": "Última sincronización: {dateTime}", + "ios_debug_info_no_processes_queued": "Sen procesos en segundo plano en cola", + "ios_debug_info_no_sync_yet": "Aínda non se executou ningunha tarefa de sincronización en segundo plano", + "ios_debug_info_processes_queued": "{count, plural, one {{count} proceso en segundo plano en cola} other {{count} procesos en segundo plano en cola}}", + "ios_debug_info_processing_ran_at": "O procesamento executouse ás {dateTime}", "items_count": "{count, plural, one {# elemento} other {# elementos}}", "jobs": "Traballos", "keep": "Conservar", @@ -1083,10 +1205,17 @@ "kept_this_deleted_others": "Conservouse este activo e elimináronse {count, plural, one {# activo} other {# activos}}", "keyboard_shortcuts": "Atallos de teclado", "language": "Lingua", - "language_setting_description": "Seleccione a túa lingua preferida", + "language_no_results_subtitle": "Probe axustar o seu termo de busca", + "language_no_results_title": "Non se atopou ningunha lingua", + "language_search_hint": "Buscar linguas...", + "language_setting_description": "Seleccione a súa lingua preferida", + "large_files": "Ficheiros Grandes", + "last": "Último/a", "last_seen": "Visto por última vez", "latest_version": "Última Versión", + "latitude": "Latitude", "leave": "Saír", + "leave_album": "Deixar o álbum", "lens_model": "Modelo da lente", "let_others_respond": "Permitir que outros respondan", "level": "Nivel", @@ -1098,7 +1227,9 @@ "library_page_sort_created": "Data de creación", "library_page_sort_last_modified": "Última modificación", "library_page_sort_title": "Título do álbum", + "licenses": "Licenzas", "light": "Claro", + "like": "Gústame", "like_deleted": "Gústame eliminado", "link_motion_video": "Ligar vídeo en movemento", "link_to_oauth": "Ligar a OAuth", @@ -1106,25 +1237,33 @@ "list": "Lista", "loading": "Cargando", "loading_search_results_failed": "Erro ao cargar os resultados da busca", + "local": "Local", + "local_asset_cast_failed": "Non é posíbel proxectar un recurso que non está cargado no servidor", + "local_assets": "Recursos Locais", + "local_media_summary": "Resumo de Contido Local", "local_network": "Rede local", "local_network_sheet_info": "A aplicación conectarase ao servidor a través desta URL cando use a rede wifi especificada", - "location_permission": "Permiso de ubicación", - "location_permission_content": "Para usar a función de cambio automático, Immich necesita permiso de ubicación precisa para poder ler o nome da rede wifi actual", + "location_permission": "Permiso de localización", + "location_permission_content": "Para usar a función de cambio automático, Immich necesita permiso de localización precisa para poder ler o nome da rede wifi actual", "location_picker_choose_on_map": "Elixir no mapa", "location_picker_latitude_error": "Introducir unha latitude válida", - "location_picker_latitude_hint": "Introduza a túa latitude aquí", + "location_picker_latitude_hint": "Introduza a súa latitude aquí", "location_picker_longitude_error": "Introducir unha lonxitude válida", - "location_picker_longitude_hint": "Introduza a túa lonxitude aquí", + "location_picker_longitude_hint": "Introduza a súa lonxitude aquí", + "lock": "Bloquear", + "locked_folder": "Cartafol Bloqueado", + "log_detail_title": "Detalle do Rexistro", "log_out": "Pechar sesión", "log_out_all_devices": "Pechar Sesión en Todos os Dispositivos", + "logged_in_as": "Sesión iniciada como {user}", "logged_out_all_devices": "Pechouse sesión en todos os dispositivos", "logged_out_device": "Pechouse sesión no dispositivo", "login": "Iniciar sesión", "login_disabled": "O inicio de sesión foi desactivado", "login_form_api_exception": "Excepción da API. Por favor, comprobe a URL do servidor e inténteo de novo.", "login_form_back_button_text": "Atrás", - "login_form_email_hint": "oteuemail@email.com", - "login_form_endpoint_hint": "http://ip-do-teu-servidor:porto", + "login_form_email_hint": "oseuemail@dominio.com", + "login_form_endpoint_hint": "http://ip-do-servidor:porto", "login_form_endpoint_url": "URL do Punto Final do Servidor", "login_form_err_http": "Por favor, especifique http:// ou https://", "login_form_err_invalid_email": "Correo electrónico inválido", @@ -1133,42 +1272,44 @@ "login_form_err_trailing_whitespace": "Espazo en branco final", "login_form_failed_get_oauth_server_config": "Erro ao iniciar sesión usando OAuth, comprobe a URL do servidor", "login_form_failed_get_oauth_server_disable": "A función OAuth non está dispoñible neste servidor", - "login_form_failed_login": "Erro ao iniciar sesión, comproba a URL do servidor, correo electrónico e contrasinal", - "login_form_handshake_exception": "Houbo unha Excepción de Handshake co servidor. Activa o soporte para certificados autofirmados nas configuracións se estás a usar un certificado autofirmado.", + "login_form_failed_login": "Erro ao iniciar sesión, comprobe a URL do servidor, correo electrónico e contrasinal", + "login_form_handshake_exception": "Houbo unha Excepción de Handshake co servidor. Active o soporte para certificados autofirmados nas configuracións se está a usar un certificado autofirmado.", "login_form_password_hint": "contrasinal", "login_form_save_login": "Manter sesión iniciada", "login_form_server_empty": "Introduza unha URL do servidor.", "login_form_server_error": "Non se puido conectar co servidor.", "login_has_been_disabled": "O inicio de sesión foi desactivado.", - "login_password_changed_error": "Houbo un erro ao actualizar o teu contrasinal", + "login_password_changed_error": "Houbo un erro ao actualizar o seu contrasinal", "login_password_changed_success": "Contrasinal actualizado correctamente", - "logout_all_device_confirmation": "Estás seguro de que queres pechar sesión en todos os dispositivos?", - "logout_this_device_confirmation": "Estás seguro de que queres pechar sesión neste dispositivo?", + "logout_all_device_confirmation": "Está seguro de que quere pechar sesión en todos os dispositivos?", + "logout_this_device_confirmation": "Está seguro de que quere pechar sesión neste dispositivo?", + "logs": "Rexistros", "longitude": "Lonxitude", - "look": "Ollar", + "look": "Aspecto", "loop_videos": "Reproducir vídeos en bucle", "loop_videos_description": "Activar para reproducir automaticamente un vídeo en bucle no visor de detalles.", "main_branch_warning": "Está a usar unha versión de desenvolvemento; recomendamos encarecidamente usar unha versión de lanzamento!", "main_menu": "Menú principal", "make": "Marca", + "manage_geolocation": "Xestionar a localización", "manage_shared_links": "Xestionar ligazóns compartidas", "manage_sharing_with_partners": "Xestionar compartición con compañeiros/as", "manage_the_app_settings": "Xestionar a configuración da aplicación", - "manage_your_account": "Xestionar a túa conta", - "manage_your_api_keys": "Xestionar as túas claves API", - "manage_your_devices": "Xestionar os teus dispositivos con sesión iniciada", - "manage_your_oauth_connection": "Xestionar a túa conexión OAuth", + "manage_your_account": "Xestionar a súa conta", + "manage_your_api_keys": "Xestionar as súas claves API", + "manage_your_devices": "Xestionar os seus dispositivos con sesión iniciada", + "manage_your_oauth_connection": "Xestionar a súa conexión OAuth", "map": "Mapa", - "map_assets_in_bounds": "{count} fotos", - "map_cannot_get_user_location": "Non se pode obter a ubicación do usuario", + "map_assets_in_bounds": "{count, plural, =0 {Sen fotos nesta área} one {# foto} other {# fotos}}", + "map_cannot_get_user_location": "Non se pode obter a localización do usuario", "map_location_dialog_yes": "Si", - "map_location_picker_page_use_location": "Usar esta ubicación", - "map_location_service_disabled_content": "O servizo de ubicación debe estar activado para mostrar activos da túa ubicación actual. Queres activalo agora?", - "map_location_service_disabled_title": "Servizo de ubicación deshabilitado", + "map_location_picker_page_use_location": "Usar esta localización", + "map_location_service_disabled_content": "O servizo de localización debe estar activado para mostrar activos da súa localización actual. Quere activalo agora?", + "map_location_service_disabled_title": "Servizo de localización deshabilitado", "map_marker_for_images": "Marcador de mapa para imaxes tomadas en {city}, {country}", "map_marker_with_image": "Marcador de mapa con imaxe", - "map_no_location_permission_content": "Necesítase permiso de ubicación para mostrar activos da súa ubicación actual. Queres permitilo agora?", - "map_no_location_permission_title": "Permiso de ubicación denegado", + "map_no_location_permission_content": "Necesítase permiso de localización para mostrar activos da súa localización actual. Quere permitilo agora?", + "map_no_location_permission_title": "Permiso de localización denegado", "map_settings": "Configuración do mapa", "map_settings_dark_mode": "Modo escuro", "map_settings_date_range_option_day": "Últimas 24 horas", @@ -1180,74 +1321,106 @@ "map_settings_include_show_partners": "Incluír Compañeiros/as", "map_settings_only_show_favorites": "Mostrar Só Favoritos", "map_settings_theme_settings": "Tema do Mapa", - "map_zoom_to_see_photos": "Alonxe o zoom para ver fotos", + "map_zoom_to_see_photos": "Afaste o zoom para ver fotos", "mark_all_as_read": "Marcar todo como lido", "mark_as_read": "Marcar como lido", "marked_all_as_read": "Marcado todo como lido", "matches": "Coincidencias", + "matching_assets": "Recursos Correspondentes", "media_type": "Tipo de medio", "memories": "Recordos", "memories_all_caught_up": "Todo ao día", "memories_check_back_tomorrow": "Volva mañá para máis recordos", - "memories_setting_description": "Xestionar o que ves nos teus recordos", + "memories_setting_description": "Xestionar o que ve nos seus recordos", "memories_start_over": "Comezar de novo", - "memories_swipe_to_close": "Deslizar cara arriba para pechar", + "memories_swipe_to_close": "Deslice cara arriba para pechar", "memory": "Recordo", - "memory_lane_title": "Camiño dos Recordos {title}", + "memory_lane_title": "Camiño dos Recordos: {title}", "menu": "Menú", "merge": "Fusionar", "merge_people": "Fusionar persoas", "merge_people_limit": "Só pode fusionar ata 5 caras á vez", - "merge_people_prompt": "Queres fusionar estas persoas? Esta acción é irreversible.", + "merge_people_prompt": "Quere fusionar estas persoas? Esta acción é irreversible.", "merge_people_successfully": "Persoas fusionadas correctamente", "merged_people_count": "Fusionadas {count, plural, one {# persoa} other {# persoas}}", "minimize": "Minimizar", "minute": "Minuto", + "minutes": "Minutos", "missing": "Faltantes", + "mobile_app": "Aplicación Móbil", + "mobile_app_download_onboarding_note": "Podes acceder a estas opcións de novo dende a páxina de Utilidades.", "model": "Modelo", "month": "Mes", + "monthly_title_text_date_format": "MMMM a", "more": "Máis", + "move": "Mover", + "move_off_locked_folder": "Mover fóra do cartafol bloqueado", + "move_to_lock_folder_action_prompt": "{count} engadido/a ao cartafol bloqueado", + "move_to_locked_folder": "Mover ao cartafol bloqueado", + "move_to_locked_folder_confirmation": "Estas fotos e vídeo eliminaranse de todos os álbums e só serán visíbeis dende o cartafol bloqueado", + "moved_to_archive": "Moveuse {count, plural, one {# recurso} other {# recursos}} ao arquivo", + "moved_to_library": "Moveuse {count, plural, one {# recurso} other {# recursos}} á biblioteca", "moved_to_trash": "Movido ao lixo", "multiselect_grid_edit_date_time_err_read_only": "Non se pode editar a data de activo(s) de só lectura, omitindo", - "multiselect_grid_edit_gps_err_read_only": "Non se pode editar a ubicación de activo(s) de só lectura, omitindo", + "multiselect_grid_edit_gps_err_read_only": "Non se pode editar a localización de activo(s) de só lectura, omitindo", "mute_memories": "Silenciar Recordos", "my_albums": "Os meus álbums", "name": "Nome", "name_or_nickname": "Nome ou alcume", + "navigate": "Navegar", + "navigate_to_time": "Navegar ata Hora", + "network_requirement_photos_upload": "Usar datos móbiles para facer copia de seguridade das fotos", + "network_requirement_videos_upload": "Usar datos móbiles para facer copia de seguridade dos vídeos", + "network_requirements": "Requisitos de rede", + "network_requirements_updated": "Os requisitos de rede cambiaron, reiniciando cola de copia de seguridade", "networking_settings": "Rede", "networking_subtitle": "Xestionar a configuración do punto final do servidor", "never": "Nunca", "new_album": "Novo Álbum", "new_api_key": "Nova Chave API", + "new_date_range": "Novo rango de datas", "new_password": "Novo contrasinal", "new_person": "Nova persoa", + "new_pin_code": "Novo código PIN", + "new_pin_code_subtitle": "Esta é a túa primeira vez accedendo á carpeta segura. Crea un código PIN para acceder de maneira segura a esta páxina", + "new_timeline": "Nova liña de tempo", "new_user_created": "Novo usuario creado", "new_version_available": "NOVA VERSIÓN DISPOÑIBLE", "newest_first": "Máis recentes primeiro", "next": "Seguinte", "next_memory": "Seguinte recordo", "no": "Non", - "no_albums_message": "Crea un álbum para organizar as túas fotos e vídeos", - "no_albums_with_name_yet": "Parece que aínda non tes ningún álbum con este nome.", - "no_albums_yet": "Parece que aínda non tes ningún álbum.", - "no_archived_assets_message": "Arquiva fotos e vídeos para ocultalos da túa vista de Fotos", + "no_albums_message": "Cree un álbum para organizar as súas fotos e vídeos", + "no_albums_with_name_yet": "Parece que aínda non ten ningún álbum con este nome.", + "no_albums_yet": "Parece que aínda non ten ningún álbum.", + "no_archived_assets_message": "Arquive fotos e vídeos para ocultalos da súa vista de Fotos", "no_assets_message": "PREMA PARA CARGAR A SÚA PRIMEIRA FOTO", "no_assets_to_show": "Non hai activos para mostrar", + "no_cast_devices_found": "Non se atoparon dispositivos de transmisión", + "no_checksum_local": "Non hai suma de verificación dispoñible - non se poden obter os activos locais", + "no_checksum_remote": "Non hai suma de verificación dispoñible - non se pode obter o activo remoto", "no_duplicates_found": "Non se atoparon duplicados.", - "no_exif_info_available": "Non hai información exif dispoñible", - "no_explore_results_message": "Suba máis fotos para explorar a túa colección.", - "no_favorites_message": "Engade favoritos para atopar rapidamente as túas mellores fotos e vídeos", - "no_libraries_message": "Crea unha biblioteca externa para ver as túas fotos e vídeos", + "no_exif_info_available": "Non hai información EXIF dispoñible", + "no_explore_results_message": "Suba máis fotos para explorar a súa colección.", + "no_favorites_message": "Engada favoritos para atopar rapidamente as súas mellores fotos e vídeos", + "no_libraries_message": "Cree unha biblioteca externa para ver as súas fotos e vídeos", + "no_local_assets_found": "Non se atoparon elementos locais con esta suma de comprobación", + "no_locked_photos_message": "As fotos e vídeos no cartafol con chave están ocultos e non aparecerán mentres navegas ou buscas na túa biblioteca.", "no_name": "Sen Nome", "no_notifications": "Sen notificacións", + "no_people_found": "Non se atoparon persoas coincidentes", "no_places": "Sen lugares", + "no_remote_assets_found": "Non se atoparon activos remotos con esta suma de verificación", "no_results": "Sen resultados", - "no_results_description": "Proba cun sinónimo ou palabra chave máis xeral", - "no_shared_albums_message": "Crea un álbum para compartir fotos e vídeos con persoas na túa rede", + "no_results_description": "Probe cun sinónimo ou palabra chave máis xeral", + "no_shared_albums_message": "Cree un álbum para compartir fotos e vídeos con persoas na súa rede", + "no_uploads_in_progress": "Non hai cargas en curso", + "not_available": "Non dispoñible", "not_in_any_album": "Non está en ningún álbum", "not_selected": "Non seleccionado", "note_apply_storage_label_to_previously_uploaded assets": "Nota: Para aplicar a Etiqueta de Almacenamento a activos cargados previamente, execute o", "notes": "Notas", + "nothing_here_yet": "Aínda nada por aquí", "notification_permission_dialog_content": "Para activar as notificacións, vaia a Axustes e seleccione permitir.", "notification_permission_list_tile_content": "Conceda permiso para activar as notificacións.", "notification_permission_list_tile_enable_button": "Activar Notificacións", @@ -1255,14 +1428,21 @@ "notification_toggle_setting_description": "Activar notificacións por correo electrónico", "notifications": "Notificacións", "notifications_setting_description": "Xestionar notificacións", + "oauth": "OAuth", + "obtainium_configurator": "Configurador de Obtainium", + "obtainium_configurator_instructions": "Por favor, crea unha chave API e selecciona unha variante para xerar a túa ligazón de configuración de Obtainium.", "official_immich_resources": "Recursos Oficiais de Immich", "offline": "Fóra de liña", + "offset": "Desprazamento", "ok": "Aceptar", "oldest_first": "Máis antigos primeiro", "on_this_device": "Neste dispositivo", - "onboarding": "Incorporación", + "onboarding": "Primeiros pasos", + "onboarding_locale_description": "Selecciona o teu idioma preferido. Podes cambialo máis tarde na túa configuración.", "onboarding_privacy_description": "As seguintes funcións (opcionais) dependen de servizos externos e poden desactivarse en calquera momento na configuración da administración.", - "onboarding_theme_description": "Elixe un tema de cor para a túa instancia. Podes cambialo máis tarde na túa configuración.", + "onboarding_server_welcome_description": "Imos configurar a túa instancia con algunhas opcións comúns.", + "onboarding_theme_description": "Elixa un tema de cor para a súa instancia. Pode cambialo máis tarde na súa configuración.", + "onboarding_user_welcome_description": "Imos comezar!", "onboarding_welcome_user": "Benvido/a, {user}", "online": "En liña", "only_favorites": "Só favoritos", @@ -1272,17 +1452,20 @@ "open_the_search_filters": "Abrir os filtros de busca", "options": "Opcións", "or": "ou", - "organize_your_library": "Organizar a túa biblioteca", + "organize_into_albums": "Organizar en álbums", + "organize_into_albums_description": "Poñer as fotos existentes en álbums usando as opcións de sincronización actuais", + "organize_your_library": "Organizar a súa biblioteca", "original": "orixinal", "other": "Outro", "other_devices": "Outros dispositivos", + "other_entities": "Outras entidades", "other_variables": "Outras variables", "owned": "Propio", "owner": "Propietario", "partner": "Compañeiro/a", "partner_can_access": "{partner} pode acceder a", - "partner_can_access_assets": "Todas as túas fotos e vídeos excepto os de Arquivo e Eliminados", - "partner_can_access_location": "A ubicación onde se tomaron as túas fotos", + "partner_can_access_assets": "Todas as súas fotos e vídeos excepto os de Arquivo e Eliminados", + "partner_can_access_location": "A localización onde se tomaron as súas fotos", "partner_list_user_photos": "Fotos de {user}", "partner_list_view_all": "Ver todo", "partner_page_empty_message": "As súas fotos aínda non están compartidas con ningún compañeiro/a.", @@ -1290,7 +1473,7 @@ "partner_page_partner_add_failed": "Erro ao engadir compañeiro/a", "partner_page_select_partner": "Seleccionar compañeiro/a", "partner_page_shared_to_title": "Compartido con", - "partner_page_stop_sharing_content": "{partner} xa non poderá acceder ás túas fotos.", + "partner_page_stop_sharing_content": "{partner} xa non poderá acceder ás súas fotos.", "partner_sharing": "Compartición con Compañeiro/a", "partners": "Compañeiros/as", "password": "Contrasinal", @@ -1303,7 +1486,7 @@ "years": "Últimos {years, plural, one {ano} other {# anos}}" }, "path": "Ruta", - "pattern": "Padrón", + "pattern": "Patrón", "pause": "Pausa", "pause_memories": "Pausar recordos", "paused": "Pausado", @@ -1316,26 +1499,35 @@ "permanent_deletion_warning_setting_description": "Mostrar un aviso ao eliminar permanentemente activos", "permanently_delete": "Eliminar permanentemente", "permanently_delete_assets_count": "Eliminar permanentemente {count, plural, one {activo} other {activos}}", - "permanently_delete_assets_prompt": "Estás seguro de que queres eliminar permanentemente {count, plural, one {este activo?} other {estes # activos?}} Isto tamén {count, plural, one {o eliminará do teu} other {os eliminará dos teus}} álbum(s).", + "permanently_delete_assets_prompt": "Está seguro de que quere eliminar permanentemente {count, plural, one {este activo?} other {estes # activos?}} Isto tamén {count, plural, one {o eliminará do seu} other {os eliminará dos seus}} álbum(s).", "permanently_deleted_asset": "Activo eliminado permanentemente", "permanently_deleted_assets_count": "Eliminados permanentemente {count, plural, one {# activo} other {# activos}}", + "permission": "Permiso", + "permission_empty": "O teu permiso non debe estar baleiro", "permission_onboarding_back": "Atrás", "permission_onboarding_continue_anyway": "Continuar de todos os xeitos", "permission_onboarding_get_started": "Comezar", "permission_onboarding_go_to_settings": "Ir a axustes", "permission_onboarding_permission_denied": "Permiso denegado. Para usar Immich, conceda permisos de fotos e vídeos en Axustes.", "permission_onboarding_permission_granted": "Permiso concedido! Xa está todo listo.", - "permission_onboarding_permission_limited": "Permiso limitado. Para permitir que Immich faga copia de seguridade e xestione toda a túa colección da galería, conceda permisos de fotos e vídeos en Configuración.", - "permission_onboarding_request": "Immich require permiso para ver as túas fotos e vídeos.", + "permission_onboarding_permission_limited": "Permiso limitado. Para permitir que Immich faga copia de seguridade e xestione toda a súa colección da galería, conceda permisos de fotos e vídeos en Configuración.", + "permission_onboarding_request": "Immich require permiso para ver as súas fotos e vídeos.", "person": "Persoa", + "person_age_months": "{months, plural, one {# mes} other {# meses}} de idade", + "person_age_year_months": "1 ano, {months, plural, one {# mes} other {# meses}} de idade", + "person_age_years": "{years, plural, one {# ano} other {# anos}} de idade", "person_birthdate": "Nacido/a o {date}", "person_hidden": "{name}{hidden, select, true { (oculto)} other {}}", - "photo_shared_all_users": "Parece que compartiches as túas fotos con todos os usuarios ou non tes ningún usuario co que compartir.", + "photo_shared_all_users": "Parece que compartiu as súas fotos con todos os usuarios ou non ten ningún usuario co que compartir.", "photos": "Fotos", "photos_and_videos": "Fotos e Vídeos", "photos_count": "{count, plural, one {{count, number} Foto} other {{count, number} Fotos}}", "photos_from_previous_years": "Fotos de anos anteriores", - "pick_a_location": "Elixir unha ubicación", + "pick_a_location": "Elixir unha localización", + "pin_code_changed_successfully": "Código PIN cambiado correctamente", + "pin_code_reset_successfully": "Código PIN restablecido correctamente", + "pin_code_setup_successfully": "Código PIN configurado correctamente", + "pin_verification": "Verificación do código PIN", "place": "Lugar", "places": "Lugares", "places_count": "{count, plural, one {{count, number} Lugar} other {{count, number} Lugares}}", @@ -1343,22 +1535,26 @@ "play_memories": "Reproducir recordos", "play_motion_photo": "Reproducir Foto en Movemento", "play_or_pause_video": "Reproducir ou pausar vídeo", + "please_auth_to_access": "Por favor, autentícate para acceder", "port": "Porto", "preferences_settings_subtitle": "Xestionar as preferencias da aplicación", "preferences_settings_title": "Preferencias", + "preparing": "Preparando", "preset": "Preaxuste", "preview": "Vista previa", "previous": "Anterior", "previous_memory": "Recordo anterior", + "previous_or_next_day": "Día seguinte / Día anterior", + "previous_or_next_month": "Mes seguinte / Mes anterior", "previous_or_next_photo": "Foto anterior ou seguinte", + "previous_or_next_year": "Ano seguinte / Ano anterior", "primary": "Principal", "privacy": "Privacidade", + "profile": "Perfil", "profile_drawer_app_logs": "Rexistros", - "profile_drawer_client_out_of_date_major": "A aplicación móbil está desactualizada. Por favor, actualice á última versión maior.", - "profile_drawer_client_out_of_date_minor": "A aplicación móbil está desactualizada. Por favor, actualice á última versión menor.", "profile_drawer_client_server_up_to_date": "Cliente e Servidor están actualizados", - "profile_drawer_server_out_of_date_major": "O servidor está desactualizado. Por favor, actualice á última versión maior.", - "profile_drawer_server_out_of_date_minor": "O servidor está desactualizado. Por favor, actualice á última versión menor.", + "profile_drawer_github": "GitHub", + "profile_drawer_readonly_mode": "Modo só lectura activado. Mantén premido o ícone do avatar do usuario para saír.", "profile_image_of_user": "Imaxe de perfil de {user}", "profile_picture_set": "Imaxe de perfil establecida.", "public_album": "Álbum público", @@ -1368,38 +1564,44 @@ "purchase_activated_time": "Activado o {date}", "purchase_activated_title": "A súa chave activouse correctamente", "purchase_button_activate": "Activar", - "purchase_button_buy": "Comprar", - "purchase_button_buy_immich": "Comprar Immich", + "purchase_button_buy": "Apoiar", + "purchase_button_buy_immich": "Apoiar Immich", "purchase_button_never_show_again": "Non mostrar nunca máis", "purchase_button_reminder": "Lembrarme en 30 días", "purchase_button_remove_key": "Eliminar chave", "purchase_button_select": "Seleccionar", - "purchase_failed_activation": "Erro ao activar! Por favor, comproba o teu correo electrónico para a chave do produto correcta!", + "purchase_failed_activation": "Erro ao activar! Por favor, comprobe o seu correo electrónico para a chave do produto correcta!", "purchase_individual_description_1": "Para un individuo", "purchase_individual_description_2": "Estado de seguidor/a", + "purchase_individual_title": "Individual", "purchase_input_suggestion": "Ten unha chave de produto? Introduza a chave a continuación", - "purchase_license_subtitle": "Compre Immich para apoiar o desenvolvemento continuado do servizo", - "purchase_lifetime_description": "Compra vitalicia", - "purchase_option_title": "OPCIÓNS DE COMPRA", + "purchase_license_subtitle": "Apoie Immich para contribuír ao desenvolvemento continuado do servizo", + "purchase_lifetime_description": "Contribución vitalicia", + "purchase_option_title": "OPCIÓNS DE APOIO", "purchase_panel_info_1": "Construír Immich leva moito tempo e esforzo, e temos enxeñeiros a tempo completo traballando nel para facelo o mellor posible. A nosa misión é que o software de código aberto e as prácticas comerciais éticas se convertan nunha fonte de ingresos sostible para os desenvolvedores e crear un ecosistema respectuoso coa privacidade con alternativas reais aos servizos na nube explotadores.", - "purchase_panel_info_2": "Como estamos comprometidos a non engadir muros de pago, esta compra non che outorgará ningunha función adicional en Immich. Dependemos de usuarios coma ti para apoiar o desenvolvemento continuo de Immich.", + "purchase_panel_info_2": "Como estamos comprometidos a non engadir muros de pago, esta contribución non lle outorgará ningunha función adicional en Immich. Dependemos de usuarios coma vostede para apoiar o desenvolvemento continuo de Immich.", "purchase_panel_title": "Apoiar o proxecto", "purchase_per_server": "Por servidor", "purchase_per_user": "Por usuario", "purchase_remove_product_key": "Eliminar Chave do Produto", - "purchase_remove_product_key_prompt": "Estás seguro de que queres eliminar a chave do produto?", + "purchase_remove_product_key_prompt": "Está seguro de que quere eliminar a chave do produto?", "purchase_remove_server_product_key": "Eliminar chave do produto do Servidor", - "purchase_remove_server_product_key_prompt": "Estás seguro de que queres eliminar a chave do produto do Servidor?", + "purchase_remove_server_product_key_prompt": "Está seguro de que quere eliminar a chave do produto do Servidor?", "purchase_server_description_1": "Para todo o servidor", "purchase_server_description_2": "Estado de seguidor/a", "purchase_server_title": "Servidor", "purchase_settings_server_activated": "A chave do produto do servidor é xestionada polo administrador", + "query_asset_id": "Consultar o ID do activo", + "queue_status": "Pondo en cola {count}/{total}", "rating": "Clasificación por estrelas", "rating_clear": "Borrar clasificación", "rating_count": "{count, plural, one {# estrela} other {# estrelas}}", "rating_description": "Mostrar a clasificación EXIF no panel de información", "reaction_options": "Opcións de reacción", "read_changelog": "Ler Rexistro de Cambios", + "readonly_mode_disabled": "Modo só lectura desactivado", + "readonly_mode_enabled": "Modo só lectura activado", + "ready_for_upload": "Listo para a carga", "reassign": "Reasignar", "reassigned_assets_to_existing_person": "Reasignados {count, plural, one {# activo} other {# activos}} a {name, select, null {unha persoa existente} other {{name}}}", "reassigned_assets_to_new_person": "Reasignados {count, plural, one {# activo} other {# activos}} a unha nova persoa", @@ -1409,8 +1611,8 @@ "recent_searches": "Buscas recentes", "recently_added": "Engadido recentemente", "recently_added_page_title": "Engadido Recentemente", - "recently_taken": "Recentemente tomado", - "recently_taken_page_title": "Recentemente Tomado", + "recently_taken": "Tomado recentemente", + "recently_taken_page_title": "Tomado Recentemente", "refresh": "Actualizar", "refresh_encoded_videos": "Actualizar vídeos codificados", "refresh_faces": "Actualizar caras", @@ -1422,17 +1624,25 @@ "refreshing_faces": "Actualizando caras", "refreshing_metadata": "Actualizando metadatos", "regenerating_thumbnails": "Rexenerando miniaturas", + "remote": "Remoto", + "remote_assets": "Activos Remotos", + "remote_media_summary": "Resumo de Medios Remotos", "remove": "Eliminar", - "remove_assets_album_confirmation": "Estás seguro de que queres eliminar {count, plural, one {# activo} other {# activos}} do álbum?", - "remove_assets_shared_link_confirmation": "Estás seguro de que queres eliminar {count, plural, one {# activo} other {# activos}} desta ligazón compartida?", + "remove_assets_album_confirmation": "Está seguro de que quere eliminar {count, plural, one {# activo} other {# activos}} do álbum?", + "remove_assets_shared_link_confirmation": "Está seguro de que quere eliminar {count, plural, one {# activo} other {# activos}} desta ligazón compartida?", "remove_assets_title": "Eliminar activos?", "remove_custom_date_range": "Eliminar rango de datas personalizado", "remove_deleted_assets": "Eliminar Activos Eliminados", "remove_from_album": "Eliminar do álbum", + "remove_from_album_action_prompt": "{count} eliminado(s) do álbum", "remove_from_favorites": "Eliminar de favoritos", + "remove_from_lock_folder_action_prompt": "{count} eliminado(s) do cartafol con chave", + "remove_from_locked_folder": "Eliminar do cartafol con chave", + "remove_from_locked_folder_confirmation": "Estás seguro de que queres mover estas fotos e vídeos fóra do cartafol con chave? Serán visibles na túa biblioteca.", "remove_from_shared_link": "Eliminar da ligazón compartida", "remove_memory": "Eliminar recordo", "remove_photo_from_memory": "Eliminar foto deste recordo", + "remove_tag": "Eliminar a etiqueta", "remove_url": "Eliminar URL", "remove_user": "Eliminar usuario", "removed_api_key": "Chave API eliminada: {name}", @@ -1453,18 +1663,30 @@ "reset": "Restablecer", "reset_password": "Restablecer contrasinal", "reset_people_visibility": "Restablecer visibilidade das persoas", + "reset_pin_code": "Restablecer o código PIN", + "reset_pin_code_description": "Se esqueciches o teu código PIN, podes contactar co administrador do servidor para restablecelo", + "reset_pin_code_success": "Código PIN restablecido correctamente", + "reset_pin_code_with_password": "Sempre podes restablecer o teu código PIN coa túa contrasinal", + "reset_sqlite": "Restablecer a Base de Datos SQLite", + "reset_sqlite_confirmation": "Estás seguro de que queres restablecer a base de datos SQLite? Terás que pechar a sesión e iniciar sesión de novo para sincronizar os datos outra vez", + "reset_sqlite_success": "Base de datos SQLite restablecida correctamente", "reset_to_default": "Restablecer ao predeterminado", "resolve_duplicates": "Resolver duplicados", "resolved_all_duplicates": "Resolvéronse todos os duplicados", "restore": "Restaurar", "restore_all": "Restaurar todo", + "restore_trash_action_prompt": "{count} restaurado(s) do lixo", "restore_user": "Restaurar usuario", "restored_asset": "Activo restaurado", "resume": "Reanudar", + "resume_paused_jobs": "Retomar {count, plural, one {# traballo pausado} other {# traballos pausados}}", "retry_upload": "Reintentar carga", "review_duplicates": "Revisar duplicados", + "review_large_files": "Revisar ficheiros grandes", "role": "Rol", + "role_editor": "Editor", "role_viewer": "Visor", + "running": "Executándose", "save": "Gardar", "save_to_gallery": "Gardar na galería", "saved_api_key": "Chave API gardada", @@ -1495,8 +1717,8 @@ "search_filter_display_option_not_in_album": "Non nun álbum", "search_filter_display_options": "Opcións de Visualización", "search_filter_filename": "Buscar por nome de ficheiro", - "search_filter_location": "Ubicación", - "search_filter_location_title": "Seleccionar ubicación", + "search_filter_location": "Localización", + "search_filter_location_title": "Seleccionar localización", "search_filter_media_type": "Tipo de Medio", "search_filter_media_type_title": "Seleccionar tipo de medio", "search_filter_people_title": "Seleccionar persoas", @@ -1512,11 +1734,12 @@ "search_page_no_objects": "Non hai Información de Obxectos Dispoñible", "search_page_no_places": "Non hai Información de Lugares Dispoñible", "search_page_screenshots": "Capturas de pantalla", - "search_page_search_photos_videos": "Busca as túas fotos e vídeos", + "search_page_search_photos_videos": "Busca as súas fotos e vídeos", + "search_page_selfies": "Selfies", "search_page_things": "Cousas", "search_page_view_all_button": "Ver todo", - "search_page_your_activity": "A túa actividade", - "search_page_your_map": "O teu Mapa", + "search_page_your_activity": "A súa actividade", + "search_page_your_map": "O seu Mapa", "search_people": "Buscar persoas", "search_places": "Buscar lugares", "search_rating": "Buscar por clasificación...", @@ -1524,11 +1747,11 @@ "search_settings": "Configuración da busca", "search_state": "Buscar estado...", "search_suggestion_list_smart_search_hint_1": "A busca intelixente está activada por defecto, para buscar metadatos use a sintaxe ", - "search_suggestion_list_smart_search_hint_2": "m:o-teu-termo-de-busca", + "search_suggestion_list_smart_search_hint_2": "m:o-seu-termo-de-busca", "search_tags": "Buscar etiquetas...", "search_timezone": "Buscar fuso horario...", "search_type": "Tipo de busca", - "search_your_photos": "Buscar as túas fotos", + "search_your_photos": "Buscar as súas fotos", "searching_locales": "Buscando configuracións rexionais...", "second": "Segundo", "see_all_people": "Ver todas as persoas", @@ -1536,6 +1759,7 @@ "select_album_cover": "Seleccionar portada do álbum", "select_all": "Seleccionar todo", "select_all_duplicates": "Seleccionar todos os duplicados", + "select_all_in": "Seleccionar todo en {group}", "select_avatar_color": "Seleccionar cor do avatar", "select_face": "Seleccionar cara", "select_featured_photo": "Seleccionar foto destacada", @@ -1543,11 +1767,13 @@ "select_keep_all": "Seleccionar conservar todo", "select_library_owner": "Seleccionar propietario da biblioteca", "select_new_face": "Seleccionar nova cara", + "select_person_to_tag": "Seleccionar unha persoa para etiquetar", "select_photos": "Seleccionar fotos", "select_trash_all": "Seleccionar mover todo ao lixo", "select_user_for_sharing_page_err_album": "Erro ao crear o álbum", "selected": "Seleccionado", "selected_count": "{count, plural, other {# seleccionados}}", + "selected_gps_coordinates": "Coordenadas GPS seleccionadas", "send_message": "Enviar mensaxe", "send_welcome_email": "Enviar correo electrónico de benvida", "server_endpoint": "Punto Final do Servidor", @@ -1555,6 +1781,7 @@ "server_info_box_server_url": "URL do Servidor", "server_offline": "Servidor Fóra de Liña", "server_online": "Servidor En Liña", + "server_privacy": "Privacidade do Servidor", "server_stats": "Estatísticas do Servidor", "server_version": "Versión do Servidor", "set": "Establecer", @@ -1564,7 +1791,8 @@ "set_date_of_birth": "Establecer data de nacemento", "set_profile_picture": "Establecer imaxe de perfil", "set_slideshow_to_fullscreen": "Poñer Presentación a pantalla completa", - "setting_image_viewer_help": "O visor de detalles carga primeiro a miniatura pequena, despois carga a vista previa de tamaño medio (se está activada), finalmente carga o orixinal (se está activado).", + "set_stack_primary_asset": "Establecer como activo principal", + "setting_image_viewer_help": "O visor de detalles carga primeiro a miniatura pequena, despois carga a vista previa de tamaño medio (se está activada), e finalmente carga o orixinal (se está activado).", "setting_image_viewer_original_subtitle": "Activar para cargar a imaxe orixinal a resolución completa (grande!). Desactivar para reducir o uso de datos (tanto na rede como na caché do dispositivo).", "setting_image_viewer_original_title": "Cargar imaxe orixinal", "setting_image_viewer_preview_subtitle": "Activar para cargar unha imaxe de resolución media. Desactivar para cargar directamente o orixinal ou usar só a miniatura.", @@ -1580,22 +1808,27 @@ "setting_notifications_notify_seconds": "{count} segundos", "setting_notifications_single_progress_subtitle": "Información detallada do progreso da carga por activo", "setting_notifications_single_progress_title": "Mostrar progreso detallado da copia de seguridade en segundo plano", - "setting_notifications_subtitle": "Axustar as túas preferencias de notificación", + "setting_notifications_subtitle": "Axustar as súas preferencias de notificación", "setting_notifications_total_progress_subtitle": "Progreso xeral da carga (feitos/total activos)", "setting_notifications_total_progress_title": "Mostrar progreso total da copia de seguridade en segundo plano", + "setting_video_viewer_auto_play_subtitle": "Reproducir vídeos automaticamente cando se abren", + "setting_video_viewer_auto_play_title": "Reproducir vídeos automaticamente", "setting_video_viewer_looping_title": "Bucle", - "setting_video_viewer_original_video_subtitle": "Ao transmitir un vídeo desde o servidor, reproducir o orixinal aínda que haxa unha transcodificación dispoñible. Pode provocar buffering. Os vídeos dispoñibles localmente repródúcense en calidade orixinal independentemente desta configuración.", + "setting_video_viewer_original_video_subtitle": "Ao transmitir un vídeo desde o servidor, reproducir o orixinal aínda que haxa unha transcodificación dispoñible. Pode provocar buffering. Os vídeos dispoñibles localmente reprodúcense en calidade orixinal independentemente desta configuración.", "setting_video_viewer_original_video_title": "Forzar vídeo orixinal", "settings": "Configuración", "settings_require_restart": "Por favor, reinicie Immich para aplicar esta configuración", "settings_saved": "Configuración gardada", + "setup_pin_code": "Configurar un código PIN", "share": "Compartir", + "share_action_prompt": "Compartidos {count} activos", "share_add_photos": "Engadir fotos", "share_assets_selected": "{count} seleccionados", "share_dialog_preparing": "Preparando...", + "share_link": "Ligazón para Compartir", "shared": "Compartido", "shared_album_activities_input_disable": "O comentario está desactivado", - "shared_album_activity_remove_content": "Queres eliminar esta actividade?", + "shared_album_activity_remove_content": "Quere eliminar esta actividade?", "shared_album_activity_remove_title": "Eliminar Actividade", "shared_album_section_people_action_error": "Erro ao saír/eliminar do álbum", "shared_album_section_people_action_leave": "Eliminar usuario do álbum", @@ -1603,13 +1836,14 @@ "shared_album_section_people_title": "PERSOAS", "shared_by": "Compartido por", "shared_by_user": "Compartido por {user}", - "shared_by_you": "Compartido por ti", + "shared_by_you": "Compartido por vostede", "shared_from_partner": "Fotos de {partner}", "shared_intent_upload_button_progress_text": "{current} / {total} Subidos", "shared_link_app_bar_title": "Ligazóns Compartidas", "shared_link_clipboard_copied_massage": "Copiado ao portapapeis", "shared_link_clipboard_text": "Ligazón: {link}\nContrasinal: {password}", "shared_link_create_error": "Erro ao crear ligazón compartida", + "shared_link_custom_url_description": "Acceder a esta ligazón compartida cun URL personalizado", "shared_link_edit_description_hint": "Introduza a descrición da compartición", "shared_link_edit_expire_after_option_day": "1 día", "shared_link_edit_expire_after_option_days": "{count} días", @@ -1621,19 +1855,21 @@ "shared_link_edit_expire_after_option_year": "{count} ano", "shared_link_edit_password_hint": "Introduza o contrasinal da compartición", "shared_link_edit_submit_button": "Actualizar ligazón", - "shared_link_error_server_url_fetch": "Non se pode obter a url do servidor", + "shared_link_error_server_url_fetch": "Non se pode obter a URL do servidor", "shared_link_expires_day": "Caduca en {count} día", "shared_link_expires_days": "Caduca en {count} días", "shared_link_expires_hour": "Caduca en {count} hora", "shared_link_expires_hours": "Caduca en {count} horas", "shared_link_expires_minute": "Caduca en {count} minuto", "shared_link_expires_minutes": "Caduca en {count} minutos", - "shared_link_expires_never": "Caduca ∞", + "shared_link_expires_never": "Non caduca", "shared_link_expires_second": "Caduca en {count} segundo", "shared_link_expires_seconds": "Caduca en {count} segundos", "shared_link_individual_shared": "Compartido individualmente", + "shared_link_info_chip_metadata": "EXIF", "shared_link_manage_links": "Xestionar ligazóns Compartidas", "shared_link_options": "Opcións da ligazón compartida", + "shared_link_password_description": "Requirir un contrasinal para acceder a esta ligazón compartida", "shared_links": "Ligazóns compartidas", "shared_links_description": "Compartir fotos e vídeos cunha ligazón", "shared_photos_and_videos_count": "{assetCount, plural, other {# fotos e vídeos compartidos.}}", @@ -1642,7 +1878,7 @@ "sharing": "Compartir", "sharing_enter_password": "Por favor, introduza o contrasinal para ver esta páxina.", "sharing_page_album": "Álbums compartidos", - "sharing_page_description": "Crea álbums compartidos para compartir fotos e vídeos con persoas na túa rede.", + "sharing_page_description": "Cree álbums compartidos para compartir fotos e vídeos con persoas na súa rede.", "sharing_page_empty_list": "LISTA BALEIRA", "sharing_sidebar_description": "Mostrar unha ligazón a Compartir na barra lateral", "sharing_silver_appbar_create_shared_album": "Novo álbum compartido", @@ -1652,11 +1888,11 @@ "show_albums": "Mostrar álbums", "show_all_people": "Mostrar todas as persoas", "show_and_hide_people": "Mostrar e ocultar persoas", - "show_file_location": "Mostrar ubicación do ficheiro", + "show_file_location": "Mostrar localización do ficheiro", "show_gallery": "Mostrar galería", "show_hidden_people": "Mostrar persoas ocultas", "show_in_timeline": "Mostrar na liña de tempo", - "show_in_timeline_setting_description": "Mostrar fotos e vídeos deste usuario na túa liña de tempo", + "show_in_timeline_setting_description": "Mostrar fotos e vídeos deste usuario na súa liña de tempo", "show_keyboard_shortcuts": "Mostrar atallos de teclado", "show_metadata": "Mostrar metadatos", "show_or_hide_info": "Mostrar ou ocultar información", @@ -1668,6 +1904,7 @@ "show_slideshow_transition": "Mostrar transición da presentación", "show_supporter_badge": "Insignia de seguidor/a", "show_supporter_badge_description": "Mostrar unha insignia de seguidor/a", + "show_text_search_menu": "Mostrar o menú de busca de texto", "shuffle": "Aleatorio", "sidebar": "Barra lateral", "sidebar_display_description": "Mostrar unha ligazón á vista na barra lateral", @@ -1683,12 +1920,14 @@ "sort_created": "Data de creación", "sort_items": "Número de elementos", "sort_modified": "Data de modificación", + "sort_newest": "Foto máis recente", "sort_oldest": "Foto máis antiga", "sort_people_by_similarity": "Ordenar persoas por similitude", "sort_recent": "Foto máis recente", "sort_title": "Título", "source": "Fonte", "stack": "Apilar", + "stack_action_prompt": "{count} apilados", "stack_duplicates": "Apilar duplicados", "stack_select_one_photo": "Seleccionar unha foto principal para a pila", "stack_selected_photos": "Apilar fotos seleccionadas", @@ -1696,26 +1935,34 @@ "stacktrace": "Rastro da Pila", "start": "Iniciar", "start_date": "Data de inicio", + "start_date_before_end_date": "A data de inicio debe ser anterior á data de fin", "state": "Estado", "status": "Estado", + "stop_casting": "Deixade de emitir", "stop_motion_photo": "Deter Foto en Movemento", - "stop_photo_sharing": "Deixar de compartir as túas fotos?", - "stop_photo_sharing_description": "{partner} xa non poderá acceder ás túas fotos.", - "stop_sharing_photos_with_user": "Deixar de compartir as túas fotos con este usuario", - "storage": "Espazo de almacenamento", + "stop_photo_sharing": "Deixar de compartir as súas fotos?", + "stop_photo_sharing_description": "{partner} xa non poderá acceder ás súas fotos.", + "stop_sharing_photos_with_user": "Deixar de compartir as súas fotos con este usuario", + "storage": "Almacenamento", "storage_label": "Etiqueta de almacenamento", + "storage_quota": "Cota de Almacenamento", "storage_usage": "{used} de {available} usado", "submit": "Enviar", + "success": "Éxito", "suggestions": "Suxestións", "sunrise_on_the_beach": "Amencer na praia", "support": "Soporte", "support_and_feedback": "Soporte e Comentarios", - "support_third_party_description": "A túa instalación de Immich foi empaquetada por un terceiro. Os problemas que experimente poden ser causados por ese paquete, así que por favor, comunica os problemas con eles en primeira instancia usando as ligazóns a continuación.", + "support_third_party_description": "A súa instalación de Immich foi empaquetada por un terceiro. Os problemas que experimente poden ser causados por ese paquete, así que por favor, comunique os problemas con eles en primeira instancia usando as ligazóns a continuación.", "swap_merge_direction": "Intercambiar dirección de fusión", "sync": "Sincronizar", "sync_albums": "Sincronizar álbums", "sync_albums_manual_subtitle": "Sincronizar todos os vídeos e fotos cargados aos álbums de copia de seguridade seleccionados", - "sync_upload_album_setting_subtitle": "Crear e suba as túas fotos e vídeos aos álbums seleccionados en Immich", + "sync_local": "Sincronizar Local", + "sync_remote": "Sincronizar Remoto", + "sync_status": "Estado de Sincronización", + "sync_status_subtitle": "Ver e xestionar o sistema de sincronización", + "sync_upload_album_setting_subtitle": "Crear e subir as súas fotos e vídeos aos álbums seleccionados en Immich", "tag": "Etiqueta", "tag_assets": "Etiquetar activos", "tag_created": "Etiqueta creada: {tag}", @@ -1725,11 +1972,12 @@ "tag_updated": "Etiqueta actualizada: {tag}", "tagged_assets": "Etiquetados {count, plural, one {# activo} other {# activos}}", "tags": "Etiquetas", + "tap_to_run_job": "Tocar para executar tarefa", "template": "Modelo", "theme": "Tema", "theme_selection": "Selección de tema", - "theme_selection_description": "Establecer automaticamente o tema a claro ou escuro baseándose na preferencia do sistema do teu navegador", - "theme_setting_asset_list_storage_indicator_title": "Mostrar indicador de almacenamento nas tellas de activos", + "theme_selection_description": "Establecer automaticamente o tema a claro ou escuro baseándose na preferencia do sistema do seu navegador", + "theme_setting_asset_list_storage_indicator_title": "Mostrar indicador de almacenamento nas celas de activos", "theme_setting_asset_list_tiles_per_row_title": "Número de activos por fila ({count})", "theme_setting_colorful_interface_subtitle": "Aplicar cor primaria ás superficies de fondo.", "theme_setting_colorful_interface_title": "Interface colorida", @@ -1751,28 +1999,38 @@ "to_change_password": "Cambiar contrasinal", "to_favorite": "Favorito", "to_login": "Iniciar sesión", + "to_multi_select": "Para a selección múltiple", "to_parent": "Ir ao pai", + "to_select": "Para seleccionar", "to_trash": "Lixo", "toggle_settings": "Alternar configuración", + "total": "Total", "total_usage": "Uso total", "trash": "Lixo", + "trash_action_prompt": "{count} movidos ao lixo", "trash_all": "Mover Todo ao Lixo", - "trash_count": "Lixo {count, number}", + "trash_count": "Lixo ({count, number})", "trash_delete_asset": "Mover ao Lixo/Eliminar Activo", "trash_emptied": "Lixo baleirado", "trash_no_results_message": "As fotos e vídeos movidos ao lixo aparecerán aquí.", "trash_page_delete_all": "Eliminar Todo", - "trash_page_empty_trash_dialog_content": "Queres baleirar os teus activos no lixo? Estes elementos eliminaranse permanentemente de Immich", + "trash_page_empty_trash_dialog_content": "Quere baleirar os seus activos no lixo? Estes elementos eliminaranse permanentemente de Immich", "trash_page_info": "Os elementos no lixo eliminaranse permanentemente despois de {days} días", "trash_page_no_assets": "Non hai activos no lixo", "trash_page_restore_all": "Restaurar Todo", "trash_page_select_assets_btn": "Seleccionar activos", "trash_page_title": "Lixo ({count})", "trashed_items_will_be_permanently_deleted_after": "Os elementos no lixo eliminaranse permanentemente despois de {days, plural, one {# día} other {# días}}.", + "troubleshoot": "Solucionar problemas", "type": "Tipo", + "unable_to_change_pin_code": "Non é posible cambiar o código PIN", + "unable_to_setup_pin_code": "Non é posible configurar o código PIN", "unarchive": "Desarquivar", + "unarchive_action_prompt": "{count} eliminados do Arquivo", "unarchived_count": "{count, plural, other {Desarquivados #}}", + "undo": "Desfacer", "unfavorite": "Desmarcar como favorito", + "unfavorite_action_prompt": "{count} eliminados de Favoritos", "unhide_person": "Mostrar persoa", "unknown": "Descoñecido", "unknown_country": "País Descoñecido", @@ -1783,48 +2041,65 @@ "unlinked_oauth_account": "Conta OAuth desvinculada", "unmute_memories": "Desilenciar Recordos", "unnamed_album": "Álbum Sen Nome", - "unnamed_album_delete_confirmation": "Estás seguro de que queres eliminar este álbum?", - "unnamed_share": "Compartir Sen Nome", + "unnamed_album_delete_confirmation": "Está seguro de que quere eliminar este álbum?", + "unnamed_share": "Compartición Sen Nome", "unsaved_change": "Cambio sen gardar", "unselect_all": "Deseleccionar todo", "unselect_all_duplicates": "Deseleccionar todos os duplicados", + "unselect_all_in": "Desmarcar todo en {group}", "unstack": "Desapilar", + "unstack_action_prompt": "{count} desapilados", "unstacked_assets_count": "Desapilados {count, plural, one {# activo} other {# activos}}", + "untagged": "Sen etiquetar", "up_next": "A continuación", + "update_location_action_prompt": "Actualizar a localización de {count} elementos seleccionados con:", + "updated_at": "Actualizado", "updated_password": "Contrasinal actualizado", "upload": "Subir", + "upload_action_prompt": "{count} en cola de espera para cargar", "upload_concurrency": "Concorrencia de subida", - "upload_dialog_info": "Queres facer copia de seguridade do(s) Activo(s) seleccionado(s) no servidor?", + "upload_details": "Detalles da Carga", + "upload_dialog_info": "Quere facer copia de seguridade do(s) Activo(s) seleccionado(s) no servidor?", "upload_dialog_title": "Subir Activo", - "upload_errors": "Subida completada con {count, plural, one {# erro} other {# erros}}, actualice a páxina para ver os novos activos subidos.", + "upload_errors": "Subida completada con {count, plural, one {# erro} other {# erros}}. Actualice a páxina para ver os novos activos subidos.", + "upload_finished": "Carga finalizada", "upload_progress": "Restantes {remaining, number} - Procesados {processed, number}/{total, number}", "upload_skipped_duplicates": "Omitidos {count, plural, one {# activo duplicado} other {# activos duplicados}}", "upload_status_duplicates": "Duplicados", "upload_status_errors": "Erros", "upload_status_uploaded": "Subido", - "upload_success": "Subida exitosa, actualice a páxina para ver os novos activos subidos.", + "upload_success": "Subida exitosa. Actualice a páxina para ver os novos activos subidos.", "upload_to_immich": "Subir a Immich ({count})", "uploading": "Subindo", + "uploading_media": "Cargando multimedia", + "url": "URL", "usage": "Uso", + "use_biometric": "Usar biometría", "use_current_connection": "usar conexión actual", "use_custom_date_range": "Usar rango de datas personalizado no seu lugar", "user": "Usuario", + "user_has_been_deleted": "Este usuario foi eliminado.", "user_id": "ID de Usuario", "user_liked": "A {user} gustoulle {type, select, photo {esta foto} video {este vídeo} asset {este activo} other {isto}}", - "user_purchase_settings": "Compra", - "user_purchase_settings_description": "Xestionar a túa compra", + "user_pin_code_settings": "Código PIN", + "user_pin_code_settings_description": "Xestionar seu código PIN", + "user_privacy": "Privacidade do usuario", + "user_purchase_settings": "Contribución", + "user_purchase_settings_description": "Xestionar a súa contribución", "user_role_set": "Establecer {user} como {role}", "user_usage_detail": "Detalle de uso do usuario", "user_usage_stats": "Estatísticas de uso da conta", "user_usage_stats_description": "Ver estatísticas de uso da conta", "username": "Nome de usuario", "users": "Usuarios", + "users_added_to_album_count": "Engadido/s {count, plural, one {# usuario} other {# usuarios}} ao álbum", "utilities": "Utilidades", "validate": "Validar", "validate_endpoint_error": "Por favor, introduza unha URL válida", + "variables": "Variables", "version": "Versión", "version_announcement_closing": "O seu amigo, Alex", - "version_announcement_message": "Ola! Unha nova versión de Immich está dispoñible. Por favor, toma un tempo para ler as notas de lanzamento para asegurarse de que a túa configuración está actualizada para evitar calquera configuración incorrecta, especialmente se usas WatchTower ou calquera mecanismo que xestione a actualización automática da túa instancia de Immich.", + "version_announcement_message": "Ola! Unha nova versión de Immich está dispoñible. Por favor, tome un tempo para ler as notas de lanzamento para asegurarse de que a súa configuración está actualizada para evitar calquera configuración incorrecta, especialmente se usa WatchTower ou calquera mecanismo que xestione a actualización automática da súa instancia de Immich.", "version_history": "Historial de Versións", "version_history_item": "Instalado {version} o {date}", "video": "Vídeo", @@ -1836,6 +2111,7 @@ "view_album": "Ver Álbum", "view_all": "Ver Todo", "view_all_users": "Ver todos os usuarios", + "view_details": "Ver detalles", "view_in_timeline": "Ver na liña de tempo", "view_link": "Ver ligazón", "view_links": "Ver ligazóns", @@ -1843,7 +2119,9 @@ "view_next_asset": "Ver seguinte activo", "view_previous_asset": "Ver activo anterior", "view_qr_code": "Ver código QR", + "view_similar_photos": "Ver fotos semellantes", "view_stack": "Ver Pila", + "view_user": "Ver Usuario", "viewer_remove_from_stack": "Eliminar da Pila", "viewer_stack_use_as_main_asset": "Usar como Activo Principal", "viewer_unstack": "Desapilar", @@ -1854,10 +2132,12 @@ "welcome": "Benvido/a", "welcome_to_immich": "Benvido/a a Immich", "wifi_name": "Nome da wifi", + "wrong_pin_code": "Código PIN incorrecto", "year": "Ano", "years_ago": "Hai {years, plural, one {# ano} other {# anos}}", "yes": "Si", - "you_dont_have_any_shared_links": "Non tes ningunha ligazón compartida", - "your_wifi_name": "O nome da túa wifi", - "zoom_image": "Ampliar Imaxe" + "you_dont_have_any_shared_links": "Non ten ningunha ligazón compartida", + "your_wifi_name": "O nome da súa wifi", + "zoom_image": "Ampliar Imaxe", + "zoom_to_bounds": "Axustar ao perímetro" } diff --git a/i18n/he.json b/i18n/he.json index 3bf4f703e8..3d3a82e9e1 100644 --- a/i18n/he.json +++ b/i18n/he.json @@ -15,7 +15,7 @@ "add_a_name": "הוספת שם", "add_a_title": "הוספת כותרת", "add_birthday": "הוספת יום הולדת", - "add_endpoint": "הוסף נקודת קצה", + "add_endpoint": "הוסף כתובת URL", "add_exclusion_pattern": "הוספת דפוס החרגה", "add_import_path": "הוספת נתיב יבוא", "add_location": "הוספת מיקום", @@ -28,10 +28,12 @@ "add_to_album": "הוספה לאלבום", "add_to_album_bottom_sheet_added": "נוסף ל {album}", "add_to_album_bottom_sheet_already_exists": "כבר ב {album}", + "add_to_album_bottom_sheet_some_local_assets": "לא ניתן היה להוסיף חלק מהקבצים המקומיים לאלבום", "add_to_album_toggle": "החלפת מצב בחירה עבור {album}", "add_to_albums": "הוספה לאלבומים", - "add_to_albums_count": "Add to albums ({count})", + "add_to_albums_count": "הוסף ({count}) לאלבום", "add_to_shared_album": "הוספה לאלבום משותף", + "add_upload_to_stack": "הוסף את ההעלאה לערימה", "add_url": "הוספת קישור", "added_to_archive": "נוסף לארכיון", "added_to_favorites": "נוסף למועדפים", @@ -124,8 +126,12 @@ "logging_level_description": "כאשר פועל, באיזה רמת יומן לתעד.", "logging_settings": "רישום ביומן", "machine_learning_availability_checks": "בדיקת זמינות", + "machine_learning_availability_checks_description": "זהה ותעדף אוטומטית שרתי למידת מכונה זמינים", "machine_learning_availability_checks_enabled": "הפעלת בדיקות זמינות", "machine_learning_availability_checks_interval": "תזמון בדיקה", + "machine_learning_availability_checks_interval_description": "מרווח זמן במילישניות בין בדיקות זמינות", + "machine_learning_availability_checks_timeout": "פסק זמן לבקשה", + "machine_learning_availability_checks_timeout_description": "פסק זמן במילישניות עבור בדיקות זמינות", "machine_learning_clip_model": "מודל CLIP", "machine_learning_clip_model_description": "שמו של מודל CLIP רשום כאן. שים לב שעליך להפעיל מחדש את המשימה 'חיפוש חכם' עבור כל התמונות בעת שינוי מודל.", "machine_learning_duplicate_detection": "איתור כפילויות", @@ -399,7 +405,7 @@ "advanced_settings_proxy_headers_title": "כותרות פרוקסי", "advanced_settings_readonly_mode_subtitle": "מאפשר את מצב לקריאה בלבד בו התמונות ניתנות לצפייה בלבד, דברים כמו בחירת תמונות מרובות, שיתוף, שידור, מחיקה הם כולם מושבתים. אפשר/השבת מצב לקריאה בלבד באמצעות יצגן המשתמש מהמסך הראשי", "advanced_settings_readonly_mode_title": "מצב לקריאה בלבד", - "advanced_settings_self_signed_ssl_subtitle": "מדלג על אימות תעודת SSL עבור נקודת הקצה של השרת. דרוש עבור תעודות בחתימה עצמית.", + "advanced_settings_self_signed_ssl_subtitle": "מדלג על אימות תעודת SSL עבור כתובת URL של השרת. דרוש עבור תעודות בחתימה עצמית.", "advanced_settings_self_signed_ssl_title": "התר תעודות SSL בחתימה עצמית", "advanced_settings_sync_remote_deletions_subtitle": "מחק או שחזר תמונה במכשיר זה באופן אוטומטי כאשר פעולה זו נעשית בדפדפן", "advanced_settings_sync_remote_deletions_title": "סנכרן מחיקות שבוצעו במכשירים אחרים [נסיוני]", @@ -498,6 +504,8 @@ "asset_restored_successfully": "תמונה שוחזרה בהצלחה", "asset_skipped": "דילג", "asset_skipped_in_trash": "באשפה", + "asset_trashed": "התמונה הועברה לאשפה", + "asset_troubleshoot": "פתרון בעיות בתמונות", "asset_uploaded": "הועלה", "asset_uploading": "מעלה…", "asset_viewer_settings_subtitle": "ניהול הגדרות מציג הגלריה שלך", @@ -531,8 +539,10 @@ "autoplay_slideshow": "מצגת תמונות אוטומטית", "back": "חזרה", "back_close_deselect": "חזור, סגור, או בטל בחירה", + "background_backup_running_error": "גיבוי ברקע פועל כעת, לא ניתן להפעיל גיבוי ידני", "background_location_permission": "הרשאת מיקום ברקע", "background_location_permission_content": "כדי להחליף רשתות בעת ריצה ברקע, היישום צריך *תמיד* גישה למיקום מדויק על מנת לקרוא את השם של רשת האינטרנט האלחוטי", + "background_options": "אפשרויות רקע", "backup": "גיבוי", "backup_album_selection_page_albums_device": "({count}) אלבומים במכשיר", "backup_album_selection_page_albums_tap": "הקש כדי לכלול, הקש פעמיים כדי להחריג", @@ -540,6 +550,7 @@ "backup_album_selection_page_select_albums": "בחירת אלבומים", "backup_album_selection_page_selection_info": "פרטי בחירה", "backup_album_selection_page_total_assets": "סה״כ תמונות ייחודיות", + "backup_albums_sync": "סנכרון אלבומי גיבוי", "backup_all": "הכל", "backup_background_service_backup_failed_message": "נכשל בגיבוי תמונות. מנסה שוב…", "backup_background_service_connection_failed_message": "נכשל בהתחברות לשרת. מנסה שוב…", @@ -582,13 +593,14 @@ "backup_controller_page_start_backup": "התחל גיבוי", "backup_controller_page_status_off": "גיבוי חזית אוטומטי כבוי", "backup_controller_page_status_on": "גיבוי חזית אוטומטי מופעל", - "backup_controller_page_storage_format": "{used}מתוך {total} בשימוש", + "backup_controller_page_storage_format": "{used} מתוך {total} בשימוש", "backup_controller_page_to_backup": "אלבומים לגבות", "backup_controller_page_total_sub": "כל התמונות והסרטונים הייחודיים מאלבומים שנבחרו", "backup_controller_page_turn_off": "כיבוי גיבוי חזית", "backup_controller_page_turn_on": "הפעל גיבוי חזית", "backup_controller_page_uploading_file_info": "מעלה מידע על הקובץ", "backup_err_only_album": "לא ניתן להסיר את האלבום היחיד", + "backup_error_sync_failed": "הסינכרון נכשל. לא ניתן להשלים את הגיבוי.", "backup_info_card_assets": "תמונות", "backup_manual_cancelled": "בוטל", "backup_manual_in_progress": "העלאה כבר בתהליך. נסה אחרי זמן מה", @@ -656,6 +668,8 @@ "change_pin_code": "שנה קוד PIN", "change_your_password": "החלף את הסיסמה שלך", "changed_visibility_successfully": "הנראות שונתה בהצלחה", + "charging": "טוען", + "charging_requirement_mobile_backup": "גיבוי ברקע דורש טעינה של המכשיר", "check_corrupt_asset_backup": "בדוק גיבויים פגומים של תמונות", "check_corrupt_asset_backup_button": "בצע בדיקה", "check_corrupt_asset_backup_description": "הרץ בדיקה זו רק על Wi-Fi ולאחר שכל התמונות גובו. ההליך עשוי לקחת כמה דקות.", @@ -687,7 +701,6 @@ "comments_and_likes": "תגובות & לייקים", "comments_are_disabled": "תגובות מושבתות", "common_create_new_album": "צור אלבום חדש", - "common_server_error": "נא לבדוק את חיבור הרשת שלך, תוודא/י שהשרת נגיש ושגרסאות אפליקציה/שרת תואמות.", "completed": "הושלמו", "confirm": "אישור", "confirm_admin_password": "אישור סיסמת מנהל", @@ -742,6 +755,7 @@ "create_user": "צור משתמש", "created": "נוצר", "created_at": "נוצר", + "creating_linked_albums": "יוצר אלבומים מקושרים...", "crop": "חתוך", "curated_object_page_title": "דברים", "current_device": "מכשיר נוכחי", @@ -868,7 +882,6 @@ "edit_tag": "ערוך תג", "edit_title": "ערוך כותרת", "edit_user": "ערוך משתמש", - "edited": "נערך", "editor": "עורך", "editor_close_without_save_prompt": "השינויים לא יישמרו", "editor_close_without_save_title": "לסגור את העורך?", @@ -891,7 +904,9 @@ "error": "שגיאה", "error_change_sort_album": "שינוי סדר מיון אלבום נכשל", "error_delete_face": "שגיאה במחיקת פנים מתמונה", + "error_getting_places": "שגיאה בקבלת מקומות", "error_loading_image": "שגיאה בטעינת התמונה", + "error_loading_partners": "שגיאה בטעינת שותפים: {error}", "error_saving_image": "שגיאה: {error}", "error_tag_face_bounding_box": "שגיאה בתיוג הפנים – לא ניתן לקבל את קואורדינטות המסגרת", "error_title": "שגיאה - משהו השתבש", @@ -1056,6 +1071,7 @@ "favorites_page_no_favorites": "לא נמצאו תמונות מועדפים", "feature_photo_updated": "תמונה מייצגת עודכנה", "features": "תכונות", + "features_in_development": "תכונות בפיתוח", "features_setting_description": "ניהול תכונות היישום", "file_name": "שם הקובץ", "file_name_or_extension": "שם קובץ או סיומת", @@ -1102,7 +1118,6 @@ "header_settings_field_validator_msg": "ערך אינו יכול להיות ריק", "header_settings_header_name_input": "שם כותרת", "header_settings_header_value_input": "ערך כותרת", - "headers_settings_tile_subtitle": "הגדר כותרות פרוקסי שהיישום צריך לשלוח עם כל בקשת רשת", "headers_settings_tile_title": "כותרות פרוקסי מותאמות", "hi_user": "היי {name}, ({email})", "hide_all_people": "הסתר את כל האנשים", @@ -1220,6 +1235,7 @@ "local": "מקומי", "local_asset_cast_failed": "לא ניתן לשדר תמונה שלא הועלתה לשרת", "local_assets": "תמונות מקומיות", + "local_media_summary": "סיכום של מדיה מקומית", "local_network": "רשת מקומית", "local_network_sheet_info": "היישום יתחבר לשרת דרך הכתובת הזאת כאשר משתמשים ברשת האינטרנט האלחוטי שמצוינת", "location_permission": "הרשאת מיקום", @@ -1231,6 +1247,7 @@ "location_picker_longitude_hint": "הזן את קו האורך שלך כאן", "lock": "נעל", "locked_folder": "תיקיה נעולה", + "log_detail_title": "פרטי יומן", "log_out": "התנתק", "log_out_all_devices": "התנתק מכל המכשירים", "logged_in_as": "מחובר כ {user}", @@ -1242,7 +1259,7 @@ "login_form_back_button_text": "חזרה", "login_form_email_hint": "yourmail@email.com", "login_form_endpoint_hint": "http://your-server-ip:port", - "login_form_endpoint_url": "כתובת נקודת קצה השרת", + "login_form_endpoint_url": "כתובת URL של השרת", "login_form_err_http": "נא לציין //:http או //:https", "login_form_err_invalid_email": "דוא\"ל שגוי", "login_form_err_invalid_url": "כתובת לא חוקית", @@ -1261,6 +1278,7 @@ "login_password_changed_success": "סיסמה עודכנה בהצלחה", "logout_all_device_confirmation": "האם באמת ברצונך להתנתק מכל המכשירים?", "logout_this_device_confirmation": "האם באמת ברצונך להתנתק מהמכשיר הזה?", + "logs": "יומנים", "longitude": "קו אורך", "look": "מראה", "loop_videos": "הפעלה חוזרת של סרטונים", @@ -1303,6 +1321,7 @@ "mark_as_read": "סמן כנקרא", "marked_all_as_read": "כל ההתראות סומנו כנקראו", "matches": "התאמות", + "matching_assets": "תמונות תואמות", "media_type": "סוג מדיה", "memories": "זכרונות", "memories_all_caught_up": "ראית הכל", @@ -1343,9 +1362,10 @@ "name_or_nickname": "שם או כינוי", "network_requirement_photos_upload": "השתמש בנתונים ניידים לגיבוי תמונות", "network_requirement_videos_upload": "השתמש בנתונים ניידים לגיבוי סרטונים", + "network_requirements": "דרישות רשת", "network_requirements_updated": "דרישות הרשת השתנו, תור הגיבוי אופס", "networking_settings": "רשת", - "networking_subtitle": "ניהול הגדרות נקודת קצה שרת", + "networking_subtitle": "ניהול הגדרות כתובת URL של השרת", "never": "אף פעם", "new_album": "אלבום חדש", "new_api_key": "מפתח API חדש", @@ -1353,6 +1373,7 @@ "new_person": "אדם חדש", "new_pin_code": "קוד PIN חדש", "new_pin_code_subtitle": "זאת הפעם הראשונה שנכנסת לתיקיה הנעולה. צור קוד PIN כדי לאבטח את הגישה לדף זה", + "new_timeline": "ציר הזמן החדש", "new_user_created": "משתמש חדש נוצר", "new_version_available": "גרסה חדשה זמינה", "newest_first": "החדש ביותר ראשון", @@ -1366,20 +1387,25 @@ "no_assets_message": "לחץ כדי להעלות את התמונה הראשונה שלך", "no_assets_to_show": "אין תמונות להצגה", "no_cast_devices_found": "לא נמצאו מכשירי שידור", + "no_checksum_local": "אין Checksum זמין - לא ניתן לאחזר תמונות מקומיות", + "no_checksum_remote": "אין Checksum זמין - לא ניתן לאחזר תמונות מהשרת", "no_duplicates_found": "לא נמצאו כפילויות.", "no_exif_info_available": "אין מידע זמין על מטא-נתונים (exif)", "no_explore_results_message": "העלה תמונות נוספות כדי לחקור את האוסף שלך.", "no_favorites_message": "הוסף מועדפים כדי למצוא במהירות את התמונות והסרטונים הכי טובים שלך", "no_libraries_message": "צור ספרייה חיצונית כדי לראות את התמונות והסרטונים שלך", + "no_local_assets_found": "לא נמצאו תמונות עם Checksum זהה", "no_locked_photos_message": "תמונות וסרטונים בתיקייה הנעולה מוסתרים ולא יופיעו בזמן הגלישה או החיפוש בספרייה שלך.", "no_name": "אין שם", "no_notifications": "אין התראות", "no_people_found": "לא נמצאו אנשים תואמים", "no_places": "אין מקומות", + "no_remote_assets_found": "לא נמצאו תמונות בשרת עם Checksum זהה", "no_results": "אין תוצאות", "no_results_description": "נסה להשתמש במילה נרדפת או במילת מפתח יותר כללית", "no_shared_albums_message": "צור אלבום כדי לשתף תמונות וסרטונים עם אנשים ברשת שלך", "no_uploads_in_progress": "אין העלאות בתהליך", + "not_available": "לא רלוונטי", "not_in_any_album": "לא בשום אלבום", "not_selected": "לא נבחרו", "note_apply_storage_label_to_previously_uploaded assets": "הערה: כדי להחיל את תווית האחסון על תמונות שהועלו בעבר, הפעל את", @@ -1475,7 +1501,7 @@ "permission_onboarding_permission_limited": "הרשאה מוגבלת. כדי לתת ליישום לגבות ולנהל את כל אוסף הגלריה שלך, הענק הרשאה לתמונות וסרטונים בהגדרות.", "permission_onboarding_request": "היישום דורש הרשאה כדי לראות את התמונות והסרטונים שלך.", "person": "אדם", - "person_age_months": "בגיל {months, plural, one {חודש #} other {# חודשים}}", + "person_age_months": "{months, plural, one {חודש #} other {# חודשים}}", "person_age_year_months": "בגיל שנה ו{months, plural, one {חודש #} other {# חודשים}}", "person_age_years": "בגיל {years, plural, other {# שנים}}", "person_birthdate": "נולד בתאריך {date}", @@ -1501,6 +1527,7 @@ "port": "יציאה", "preferences_settings_subtitle": "ניהול העדפות יישום", "preferences_settings_title": "העדפות", + "preparing": "בהכנה", "preset": "הגדרות קבועות מראש", "preview": "תצוגה מקדימה", "previous": "הקודם", @@ -1513,13 +1540,9 @@ "privacy": "פרטיות", "profile": "פרופיל", "profile_drawer_app_logs": "יומן", - "profile_drawer_client_out_of_date_major": "גרסת היישום לנייד מיושנת. נא לעדכן לגרסה הראשית האחרונה.", - "profile_drawer_client_out_of_date_minor": "גרסת היישום לנייד מיושנת. נא לעדכן לגרסה המשנית האחרונה.", "profile_drawer_client_server_up_to_date": "היישום והשרת מעודכנים", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "מצב לקריאה בלבד מופעל. לחץ לחיצה ארוכה על סמל היצגן של המשתמש כדי לצאת.", - "profile_drawer_server_out_of_date_major": "השרת אינו מעודכן. נא לעדכן לגרסה הראשית האחרונה.", - "profile_drawer_server_out_of_date_minor": "השרת אינו מעודכן. נא לעדכן לגרסה המשנית האחרונה.", "profile_image_of_user": "תמונת פרופיל של {user}", "profile_picture_set": "תמונת פרופיל נבחרה.", "public_album": "אלבום ציבורי", @@ -1741,7 +1764,7 @@ "selected_gps_coordinates": "קואורדינטות GPS שנבחרו", "send_message": "שלח הודעה", "send_welcome_email": "שלח דוא\"ל קבלת פנים", - "server_endpoint": "נקודת קצה שרת", + "server_endpoint": "כתובת URL של השרת", "server_info_box_app_version": "גרסת יישום", "server_info_box_server_url": "כתובת שרת", "server_offline": "השרת מנותק", @@ -1867,6 +1890,7 @@ "show_slideshow_transition": "הצג מעבר מצגת", "show_supporter_badge": "תג תומך", "show_supporter_badge_description": "הצג תג תומך", + "show_text_search_menu": "הצג תפריט חיפוש טקסט", "shuffle": "ערבוב", "sidebar": "סרגל צד", "sidebar_display_description": "הצג קישור לתצוגה בסרגל הצד", @@ -1897,6 +1921,7 @@ "stacktrace": "Stack trace", "start": "התחל", "start_date": "תאריך התחלה", + "start_date_before_end_date": "תאריך ההתחלה חייב להיות לפני תאריך הסיום", "state": "מדינה", "status": "מצב", "stop_casting": "הפסקת שידור", @@ -1920,8 +1945,8 @@ "sync_albums": "סנכרן אלבומים", "sync_albums_manual_subtitle": "סנכרן את כל הסרטונים והתמונות שהועלו לאלבומי הגיבוי שנבחרו", "sync_local": "סנכרן מקומי", - "sync_remote": "סנכרן נקודת קצה מרוחקת", - "sync_status": "סנכרן מצב", + "sync_remote": "סנכרן מהשרת", + "sync_status": "סטטוס סנכרון", "sync_status_subtitle": "הצג ונהל את מערכת הסנכרון", "sync_upload_album_setting_subtitle": "צור והעלה תמונות וסרטונים שלך לאלבומים שנבחרו ביישום", "tag": "תג", @@ -2056,7 +2081,7 @@ "users_added_to_album_count": "נוספו {count, plural, one {משתמש #} other {# משתמשים}} לאלבום", "utilities": "כלים", "validate": "לאמת", - "validate_endpoint_error": "נא להזין כתובת תקנית", + "validate_endpoint_error": "נא להזין כתובת URL תקנית", "variables": "משתנים", "version": "גרסה", "version_announcement_closing": "החבר שלך, אלכס", @@ -2099,5 +2124,6 @@ "yes": "כן", "you_dont_have_any_shared_links": "אין לך קישורים משותפים", "your_wifi_name": "שם אינטרנט אלחוטי שלך", - "zoom_image": "זום לתמונה" + "zoom_image": "זום לתמונה", + "zoom_to_bounds": "התמקד באזור" } diff --git a/i18n/hi.json b/i18n/hi.json index 4c9eb7bd5d..fb1698c0a2 100644 --- a/i18n/hi.json +++ b/i18n/hi.json @@ -28,7 +28,12 @@ "add_to_album": "एल्बम में डालें", "add_to_album_bottom_sheet_added": "{album} में डालें", "add_to_album_bottom_sheet_already_exists": "{album} में पहले से है", + "add_to_album_bottom_sheet_some_local_assets": "कुछ स्थानीय एसेट एल्बम में नहीं जोड़े जा सके", + "add_to_album_toggle": "{album} के लिए चयन टॉगल करें", + "add_to_albums": "एकाधिक एल्बम में डाले", + "add_to_albums_count": "एल्बमों में डालें ({count})", "add_to_shared_album": "शेयर किए गए एल्बम में डालें", + "add_upload_to_stack": "स्टैक में अपलोड करें", "add_url": "URL डालें", "added_to_archive": "संग्रहीत कर दिया गया है", "added_to_favorites": "पसंदीदा में डाला गया", @@ -115,11 +120,18 @@ "library_settings_description": "बाहरी संग्रह सेटिंग प्रबंधित करें", "library_tasks_description": "नई और/या परिवर्तित संपत्तियों के लिए बाहरी लाइब्रेरीज़ को स्कैन करें", "library_watching_enable_description": "एक्सटर्नल लाइब्रेरीज में बदलावों के लिए निगरानी रखें", - "library_watching_settings": "पुस्तकालय निगरानी (प्रायोगिक)", + "library_watching_settings": "पुस्तकालय निगरानी [प्रायोगिक]", "library_watching_settings_description": "परिवर्तित फ़ाइलों पर स्वचालित रूप से नज़र रखें", "logging_enable_description": "लॉगिंग करने देना", "logging_level_description": "सक्षम होने पर, किस लॉग स्तर का उपयोग करना है।", "logging_settings": "लॉगिंग", + "machine_learning_availability_checks": "उपलब्धता जांच", + "machine_learning_availability_checks_description": "उपलब्ध मशीन लर्निंग सर्वर का स्वचालित रूप से पता लगाएं और प्राथमिकता दें", + "machine_learning_availability_checks_enabled": "उपलब्धता जांच सक्षम करें", + "machine_learning_availability_checks_interval": "अंतराल की जाँच करें", + "machine_learning_availability_checks_interval_description": "उपलब्धता जांच के बीच मिलीसेकेंड में अंतराल", + "machine_learning_availability_checks_timeout": "अनुरोध समयबाह्य हुआ", + "machine_learning_availability_checks_timeout_description": "उपलब्धता जांच के लिए मिलीसेकंड में समयबाह्य अंतराल", "machine_learning_clip_model": "क्लिप मॉडल", "machine_learning_clip_model_description": "CLIP मॉडल का नाम यहां सूचीबद्ध है। ध्यान दें कि मॉडल बदलने पर आपको सभी छवियों के लिए 'स्मार्ट सर्च' जोब फिर से चलाना होगा।", "machine_learning_duplicate_detection": "डुप्लिकेट का पता लगाना", @@ -142,6 +154,18 @@ "machine_learning_min_detection_score_description": "किसी चेहरे का पता लगाने के लिए न्यूनतम आत्मविश्वास स्कोर 0-1 होना चाहिए।", "machine_learning_min_recognized_faces": "निम्नतम पहचाने चेहरे", "machine_learning_min_recognized_faces_description": "किसी व्यक्ति के लिए पहचाने जाने वाले चेहरों की न्यूनतम संख्या।", + "machine_learning_ocr": "ओ.सी.आर", + "machine_learning_ocr_description": "चित्रों में पाठ को पहचानने के लिए मशीन लर्निंग का उपयोग करें", + "machine_learning_ocr_enabled": "ओ.सी.आर. सक्षम करें", + "machine_learning_ocr_enabled_description": "यदि अक्षम किया गया है, तो चित्रों पर पाठ-पहचान नहीं होगा।", + "machine_learning_ocr_max_resolution": "अधिकतम रिज़ॉल्यूशन", + "machine_learning_ocr_max_resolution_description": "इस रिज़ॉल्यूशन से ऊपर के प्रदर्शन का आकार मूल अनुपात को संरक्षित करते हुए बदल दिया जाएगा। उच्च मान अधिक सटीक होते हैं, लेकिन संसाधित होने में अधिक मेमोरी और समय लगाते हैं।", + "machine_learning_ocr_min_detection_score": "न्यूनतम खोज अंक", + "machine_learning_ocr_min_detection_score_description": "पाठ का पता लगाने के लिए 0-1 के बीच न्यूनतम आत्मविश्वास अंक। कम अंक अधिक पाठ का पता लगाएंगे लेकिन परिणाम गलत हो सकते हैं।", + "machine_learning_ocr_min_recognition_score": "न्यूनतम पहचान अंक", + "machine_learning_ocr_min_score_recognition_description": "पाठ को पहचानने के लिए 0-1 के बीच न्यूनतम आत्मविश्वास अंक। कम अंक अधिक पाठ को पहचानेंगे लेकिन परिणाम गलत हो सकते हैं।", + "machine_learning_ocr_model": "ओसीआर प्रतिमान", + "machine_learning_ocr_model_description": "सर्वर प्रतिमान मोबाइल प्रतिमान की तुलना में अधिक सटीक होते हैं, लेकिन संसाधित होने में अधिक मेमोरी और समय लेते हैं।", "machine_learning_settings": "मशीन लर्निंग सेटिंग्स", "machine_learning_settings_description": "मशीन लर्निंग सुविधाओं और सेटिंग्स को प्रबंधित करें", "machine_learning_smart_search": "स्मार्ट खोज", @@ -199,6 +223,8 @@ "notification_email_ignore_certificate_errors_description": "टीएलएस प्रमाणपत्र सत्यापन त्रुटियों पर ध्यान न दें (अनुशंसित नहीं)", "notification_email_password_description": "ईमेल सर्वर से प्रमाणीकरण करते समय उपयोग किया जाने वाला पासवर्ड", "notification_email_port_description": "ईमेल सर्वर का पोर्ट (जैसे 25, 465, या 587)", + "notification_email_secure": "एस एम टी पी एस", + "notification_email_secure_description": "एस.एम.टी.पी.एस. प्रयोग करें (टी.एल.एस पर एस.एम.टी.पी)", "notification_email_sent_test_email_button": "परीक्षण ईमेल भेजें और सहेजें", "notification_email_setting_description": "ईमेल सूचनाएं भेजने के लिए सेटिंग्स", "notification_email_test_email": "परीक्षण ईमेल भेजें", @@ -231,6 +257,7 @@ "oauth_storage_quota_default_description": "GiB में कोटा का उपयोग तब किया जाएगा जब कोई दावा प्रदान नहीं किया गया हो ।", "oauth_timeout": "ब्रेक का अनुरोध", "oauth_timeout_description": "अनुरोधों के लिए समय-सीमा मिलीसेकंड में", + "ocr_job_description": "चित्रों में पाठ को पहचानने के लिए मशीन लर्निंग का उपयोग करें", "password_enable_description": "ईमेल और पासवर्ड से लॉगिन करें", "password_settings": "पासवर्ड लॉग इन", "password_settings_description": "पासवर्ड लॉगिन सेटिंग प्रबंधित करें", @@ -321,7 +348,7 @@ "transcoding_max_b_frames": "अधिकतम बी-फ्रेम", "transcoding_max_b_frames_description": "उच्च मान संपीड़न दक्षता में सुधार करते हैं, लेकिन एन्कोडिंग को धीमा कर देते हैं।", "transcoding_max_bitrate": "अधिकतम बिटरेट", - "transcoding_max_bitrate_description": "अधिकतम बिटरेट सेट करने से फ़ाइल आकार को गुणवत्ता पर मामूली लागत के साथ अधिक पूर्वानुमानित किया जा सकता है। 720p पर, सामान्य मान VP9 या HEVC के लिए 2600k kbit/s या H.264 के लिए 4500k kbit/s हैं। 0 पर सेट होने पर अक्षम।", + "transcoding_max_bitrate_description": "अधिकतम बिटरेट सेट करने से फ़ाइल आकार को गुणवत्ता पर मामूली लागत के साथ अधिक पूर्वानुमानित किया जा सकता है। 720p पर, सामान्य मान VP9 या HEVC के लिए 2600k kbit/s या H.264 के लिए 4500k kbit/s हैं। 0 पर सेट होने पर अक्षम। जब कोई इकाई निर्दिष्ट नहीं की जाती है, तो k (kbit/s के लिए) मान लिया जाता है; इसलिए 5000, 5000k, और 5M (Mbit/s के लिए) समतुल्य हैं।", "transcoding_max_keyframe_interval": "अधिकतम मुख्यफ़्रेम अंतराल", "transcoding_max_keyframe_interval_description": "मुख्यफ़्रेम के बीच अधिकतम फ़्रेम दूरी निर्धारित करता है।", "transcoding_optimal_description": "लक्ष्य रिज़ॉल्यूशन से अधिक ऊंचे वीडियो या स्वीकृत प्रारूप में नहीं", @@ -355,6 +382,9 @@ "trash_number_of_days_description": "संपत्तियों को स्थायी रूप से हटाने से पहले उन्हें कूड़ेदान में रखने के लिए दिनों की संख्या", "trash_settings": "ट्रैश सेटिंग", "trash_settings_description": "ट्रैश सेटिंग प्रबंधित करें", + "unlink_all_oauth_accounts": "सभी ओ.औथ खातों से संपर्क तोड़ दें", + "unlink_all_oauth_accounts_description": "नए प्रदाता पर सतानांतरण करने से पहले सभी ओ.औथ खातों से संपर्क तोड़ना याद रखें।", + "unlink_all_oauth_accounts_prompt": "क्या आप वाकई सभी ओ.औथ खातों से संपर्क तोड़ना चाहते हैं? इससे प्रत्येक उपयोगकर्ता के लिए ओ.औथ आई.डी रद्द हो जाएगी और इसे पूर्ववत नहीं किया जा सकेगा।", "user_cleanup_job": "उपयोगकर्ता सफ़ाई", "user_delete_delay": "{user} के खाते और परिसंपत्तियों को {delay, plural, one {# day} other {# days}} में स्थायी रूप से हटाने के लिए शेड्यूल किया जाएगा।", "user_delete_delay_settings": "हटाने में देरी", @@ -388,6 +418,8 @@ "advanced_settings_prefer_remote_title": "दूरस्थ छवियों को प्राथमिकता दें", "advanced_settings_proxy_headers_subtitle": "प्रत्येक नेटवर्क अनुरोध के साथ इम्मिच द्वारा भेजे जाने वाले प्रॉक्सी हेडर को परिभाषित करें", "advanced_settings_proxy_headers_title": "प्रॉक्सी हेडर", + "advanced_settings_readonly_mode_subtitle": "रीड-ओनली प्रणाली को सक्षम करता है जहां चित्र को केवल देखा जा सकता है, एकाधिक चित्रों का चयन करना, साझा करना, कास्टिंग करना, हटाना जैसी सभी चीज़ें अक्षम हैं। मुख्य स्क्रीन में उपयोगकर्ता- अवतार के माध्यम से रीड-ओनली प्रणाली को सक्षम/अक्षम करें", + "advanced_settings_readonly_mode_title": "रीड-ओनली प्रणाली", "advanced_settings_self_signed_ssl_subtitle": "सर्वर एंडपॉइंट के लिए SSL प्रमाणपत्र सत्यापन को छोड़ देता है। स्व-हस्ताक्षरित प्रमाणपत्रों के लिए आवश्यक है।", "advanced_settings_self_signed_ssl_title": "स्व-हस्ताक्षरित SSL प्रमाणपत्रों की अनुमति दें", "advanced_settings_sync_remote_deletions_subtitle": "वेब पर कार्रवाई किए जाने पर इस डिवाइस पर किसी संपत्ति को स्वचालित रूप से हटाएँ या पुनर्स्थापित करें", @@ -415,6 +447,7 @@ "album_remove_user_confirmation": "क्या आप वाकई {user} को हटाना चाहते हैं?", "album_search_not_found": "आपकी खोज से मेल खाता कोई एल्बम नहीं मिला", "album_share_no_users": "ऐसा लगता है कि आपने यह एल्बम सभी उपयोगकर्ताओं के साथ साझा कर दिया है या आपके पास साझा करने के लिए कोई उपयोगकर्ता नहीं है।", + "album_summary": "एल्बम सारांश", "album_updated": "एल्बम अपडेट किया गया", "album_updated_setting_description": "जब किसी साझा एल्बम में नई संपत्तियाँ हों तो एक ईमेल सूचना प्राप्त करें", "album_user_left": "बायाँ {album}", @@ -448,11 +481,16 @@ "api_key_description": "यह की केवल एक बार दिखाई जाएगी। विंडो बंद करने से पहले कृपया इसे कॉपी करना सुनिश्चित करें।।", "api_key_empty": "आपका एपीआई कुंजी नाम खाली नहीं होना चाहिए", "api_keys": "एपीआई कीज", + "app_architecture_variant": "रूपान्तर (स्थापत्य/आर्किटेक्चर)", "app_bar_signout_dialog_content": "क्या आप सुनिश्चित हैं कि आप लॉग आउट करना चाहते हैं?", "app_bar_signout_dialog_ok": "हाँ", "app_bar_signout_dialog_title": "लॉग आउट", + "app_download_links": "ऐप डाउनलोड लिंक", "app_settings": "एप्लिकेशन सेटिंग", + "app_stores": "ऐप स्टोर/गोदाम", + "app_update_available": "आधुनिक ऐप उपलब्ध है", "appears_in": "प्रकट होता है", + "apply_count": "लागू करें ({count, number})", "archive": "संग्रहालय", "archive_action_prompt": "{count} को संग्रह में जोड़ा गया", "archive_or_unarchive_photo": "फ़ोटो को संग्रहीत या असंग्रहीत करें", @@ -461,17 +499,17 @@ "archive_size": "पुरालेख आकार", "archive_size_description": "डाउनलोड के लिए संग्रह आकार कॉन्फ़िगर करें (GiB में)", "archived": "संग्रहित", - "archived_count": "{count, plural, other {# संग्रहीत किए गए}", + "archived_count": "{count, plural, other {# संग्रहीत किए गए}}", "are_these_the_same_person": "क्या ये वही व्यक्ति हैं?", "are_you_sure_to_do_this": "क्या आप वास्तव में इसे करना चाहते हैं?", "asset_action_delete_err_read_only": "केवल पढ़ने योग्य परिसंपत्ति(ओं) को हटाया नहीं जा सकता, छोड़ा जा सकता है", "asset_action_share_err_offline": "ऑफ़लाइन परिसंपत्ति(एँ) प्राप्त नहीं की जा सकती, छोड़ी जा रही है", "asset_added_to_album": "एल्बम में डाला गया", - "asset_adding_to_album": "एल्बम में डाला जा रहा है..।", + "asset_adding_to_album": "एल्बम में डाला जा रहा है…", "asset_description_updated": "संपत्ति विवरण अद्यतन कर दिया गया है", "asset_filename_is_offline": "एसेट {filename} ऑफ़लाइन है", "asset_has_unassigned_faces": "एसेट में अनिर्धारित चेहरे हैं", - "asset_hashing": "हैशिंग...।", + "asset_hashing": "हैशिंग…", "asset_list_group_by_sub_title": "द्वारा समूह बनाएं", "asset_list_layout_settings_dynamic_layout_title": "गतिशील लेआउट", "asset_list_layout_settings_group_automatically": "स्वचालित", @@ -485,6 +523,8 @@ "asset_restored_successfully": "संपत्ति(याँ) सफलतापूर्वक पुनर्स्थापित की गईं", "asset_skipped": "छोड़ा गया", "asset_skipped_in_trash": "कचरे में", + "asset_trashed": "एसेट नष्ट किया गया", + "asset_troubleshoot": "एसेट समस्या निवारण", "asset_uploaded": "अपलोड किए गए", "asset_uploading": "अपलोड हो रहा है…", "asset_viewer_settings_subtitle": "अपनी गैलरी व्यूअर सेटिंग प्रबंधित करें", @@ -492,7 +532,9 @@ "assets": "संपत्तियां", "assets_added_count": "{count, plural, one {# asset} other {# assets}} जोड़ा गया", "assets_added_to_album_count": "एल्बम में {count, plural, one {# asset} other {# assets}} जोड़ा गया", + "assets_added_to_albums_count": "{assetTotal, plural, one {# asset} other {# assets}} को {albumTotal, plural, one {# album} other {# albums}} से जोड़ा गया", "assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} को एल्बम में नहीं जोड़ा जा सकता", + "assets_cannot_be_added_to_albums": "{count, plural, one {Asset} other {Assets}} किसी एल्बम से नहीं जोड़े जा सकते", "assets_count": "{count, plural, one {# आइटम} other {# आइटम्स}}", "assets_deleted_permanently": "{count} संपत्ति(याँ) स्थायी रूप से हटा दी गईं", "assets_deleted_permanently_from_server": "{count} संपत्ति(याँ) इमिच सर्वर से स्थायी रूप से हटा दी गईं", @@ -509,14 +551,17 @@ "assets_trashed_count": "ट्रैश की गई {count, plural, one {# asset} other {# assets}}", "assets_trashed_from_server": "{count} संपत्ति(याँ) इमिच सर्वर से कचरे में डाली गईं", "assets_were_part_of_album_count": "{count, plural, one {Asset was} other {Assets were}}एल्बम का पहले से ही हिस्सा थे", + "assets_were_part_of_albums_count": "{count, plural, one {Asset was} other {Assets were}} पहले ही एल्बम में संयोजित हैं", "authorized_devices": "अधिकृत उपकरण", "automatic_endpoint_switching_subtitle": "उपलब्ध होने पर निर्दिष्ट वाई-फाई से स्थानीय रूप से कनेक्ट करें और अन्यत्र वैकल्पिक कनेक्शन का उपयोग करें", "automatic_endpoint_switching_title": "स्वचालित URL स्विचिंग", "autoplay_slideshow": "ऑटोप्ले स्लाइड शो", "back": "वापस", "back_close_deselect": "वापस जाएँ, बंद करें, या अचयनित करें", + "background_backup_running_error": "परिप्रेक्ष्य बैकअप अभी जारी है, नियमावली बैकअप प्रारंभ नहीं किया जा सकता", "background_location_permission": "पृष्ठभूमि स्थान अनुमति", "background_location_permission_content": "पृष्ठभूमि में चलते समय नेटवर्क बदलने के लिए, Immich के पास *हमेशा* सटीक स्थान तक पहुंच होनी चाहिए ताकि ऐप वाई-फाई नेटवर्क का नाम पढ़ सके", + "background_options": "परिप्रेक्ष्य विकल्प", "backup": "बैकअप", "backup_album_selection_page_albums_device": "डिवाइस पर एल्बम ({count})", "backup_album_selection_page_albums_tap": "शामिल करने के लिए टैप करें, बाहर करने के लिए डबल टैप करें", @@ -524,8 +569,10 @@ "backup_album_selection_page_select_albums": "एल्बम चुनें", "backup_album_selection_page_selection_info": "चयन जानकारी", "backup_album_selection_page_total_assets": "कुल अद्वितीय संपत्तियाँ", + "backup_albums_sync": "बैकअप एल्बम का तुल्यकालन", "backup_all": "सभी", "backup_background_service_backup_failed_message": "संपत्तियों का बैकअप लेने में विफल. पुनः प्रयास किया जा रहा है…", + "backup_background_service_complete_notification": "एसेट का बैकअप पूरा हुआ", "backup_background_service_connection_failed_message": "सर्वर से कनेक्ट करने में विफल. पुनः प्रयास किया जा रहा है…", "backup_background_service_current_upload_notification": "{filename} अपलोड हो रहा है", "backup_background_service_default_notification": "नई परिसंपत्तियों की जांच की जा रही है…", @@ -573,6 +620,7 @@ "backup_controller_page_turn_on": "अग्रभूमि बैकअप चालू करें", "backup_controller_page_uploading_file_info": "फ़ाइल जानकारी अपलोड करना", "backup_err_only_album": "एकमात्र एल्बम नहीं हटाया जा सकता", + "backup_error_sync_failed": "तुल्यकालन विफल. बैकअप संसाधित नहीं किया जा सकता।", "backup_info_card_assets": "संपत्ति", "backup_manual_cancelled": "रद्द", "backup_manual_in_progress": "अपलोड पहले से ही प्रगति पर है। कुछ देर बाद प्रयास करें", @@ -634,12 +682,16 @@ "change_password_description": "यह या तो पहली बार है जब आप सिस्टम में साइन इन कर रहे हैं या आपका पासवर्ड बदलने का अनुरोध किया गया है।", "change_password_form_confirm_password": "पासवर्ड की पुष्टि कीजिये", "change_password_form_description": "नमस्ते {name},\n\nया तो आप पहली बार सिस्टम में साइन इन कर रहे हैं या फिर आपका पासवर्ड बदलने का अनुरोध किया गया है। कृपया नीचे नया पासवर्ड डालें।", + "change_password_form_log_out": "अन्य सभी डिवाइस को लॉग आउट करें", + "change_password_form_log_out_description": "अन्य सभी डिवाइस से लॉग आउट करना अनुशंसित है", "change_password_form_new_password": "नया पासवर्ड", "change_password_form_password_mismatch": "सांकेतिक शब्द मेल नहीं खाते", "change_password_form_reenter_new_password": "नया पासवर्ड पुनः दर्ज करें", "change_pin_code": "पिन कोड बदलें", "change_your_password": "अपना पासवर्ड बदलें", "changed_visibility_successfully": "दृश्यता सफलतापूर्वक परिवर्तित", + "charging": "चार्जिंग", + "charging_requirement_mobile_backup": "परिप्रेक्ष्य बैकअप के लिए डिवाइस का चार्जिंग पे लगे होना आवश्यक है", "check_corrupt_asset_backup": "दूषित परिसंपत्ति बैकअप की जाँच करें", "check_corrupt_asset_backup_button": "जाँच करें", "check_corrupt_asset_backup_description": "यह जाँच केवल वाई-फ़ाई पर ही करें और सभी संपत्तियों का बैकअप लेने के बाद ही करें। इस प्रक्रिया में कुछ मिनट लग सकते हैं।", @@ -661,7 +713,7 @@ "client_cert_subtitle": "केवल PKCS12 (.p12, .pfx) फ़ॉर्मैट का समर्थन करता है। प्रमाणपत्र आयात/हटाएँ केवल लॉगिन से पहले उपलब्ध हैं", "client_cert_title": "SSL क्लाइंट प्रमाणपत्र", "clockwise": "दक्षिणावर्त", - "close": "बंद", + "close": "बंद करें", "collapse": "गिर जाना", "collapse_all": "सभी को संकुचित करें", "color": "रंग", @@ -671,9 +723,8 @@ "comments_and_likes": "टिप्पणियाँ और पसंद", "comments_are_disabled": "टिप्पणियाँ अक्षम हैं", "common_create_new_album": "नया एल्बम बनाएँ", - "common_server_error": "कृपया अपने नेटवर्क कनेक्शन की जांच करें, सुनिश्चित करें कि सर्वर पहुंच योग्य है और ऐप/सर्वर संस्करण संगत हैं।", - "completed": "पुरा होना", - "confirm": "पुष्टि", + "completed": "पूरित", + "confirm": "पुष्टि करें", "confirm_admin_password": "एडमिन पासवर्ड की पुष्टि करें", "confirm_delete_face": "क्या आप वाकई एसेट से {name} चेहरा हटाना चाहते हैं?", "confirm_delete_shared_link": "क्या आप वाकई इस साझा लिंक को हटाना चाहते हैं?", @@ -682,13 +733,13 @@ "confirm_password": "पासवर्ड की पुष्टि कीजिये", "confirm_tag_face": "क्या आप इस चेहरे को {name} के रूप में टैग करना चाहते हैं?", "confirm_tag_face_unnamed": "क्या आप इस चेहरे को टैग करना चाहते हैं?", - "connected_device": "कनेक्टेड डिवाइस", + "connected_device": "योजित यंत्र", "connected_to": "से जुड़ा", "contain": "समाहित", "context": "संदर्भ", "continue": "जारी", "control_bottom_app_bar_create_new_album": "नया एल्बम बनाएँ", - "control_bottom_app_bar_delete_from_immich": "Immich से हटाएं", + "control_bottom_app_bar_delete_from_immich": "इम्मिच से हटाएं", "control_bottom_app_bar_delete_from_local": "डिवाइस से हटाएं", "control_bottom_app_bar_edit_location": "स्थान संपादित करें", "control_bottom_app_bar_edit_time": "तारीख और समय संपादित करें", @@ -710,6 +761,7 @@ "create": "तैयार करें", "create_album": "एल्बम बनाओ", "create_album_page_untitled": "शीर्षकहीन", + "create_api_key": "ऐ.पी.आई. चाभी बनाएं", "create_library": "लाइब्रेरी बनाएं", "create_link": "लिंक बनाएं", "create_link_to_share": "शेयर करने के लिए लिंक बनाएं", @@ -726,6 +778,7 @@ "create_user": "उपयोगकर्ता बनाइये", "created": "बनाया", "created_at": "बनाया था", + "creating_linked_albums": "जुड़े हुए एल्बम बनाए जा रहे हैं..।", "crop": "छाँटें", "curated_object_page_title": "चीज़ें", "current_device": "वर्तमान उपकरण", @@ -738,6 +791,7 @@ "daily_title_text_date_year": "ई, एमएमएम दिन, वर्ष", "dark": "डार्क", "dark_theme": "डार्क थीम टॉगल करें", + "date": "दिनांक", "date_after": "इसके बाद की तारीख", "date_and_time": "तिथि और समय", "date_before": "पहले की तारीख", @@ -745,6 +799,7 @@ "date_of_birth_saved": "जन्मतिथि सफलतापूर्वक सहेजी गई", "date_range": "तिथि सीमा", "day": "दिन", + "days": "दिन", "deduplicate_all": "सभी को डुप्लिकेट करें", "deduplication_criteria_1": "छवि का आकार बाइट्स में", "deduplication_criteria_2": "EXIF डेटा की संख्या", @@ -833,6 +888,8 @@ "edit_date": "संपादन की तारीख", "edit_date_and_time": "दिनांक और समय संपादित करें", "edit_date_and_time_action_prompt": "{count} तारीख और समय संपादित किए गए", + "edit_date_and_time_by_offset": "अंकुर से दिनांक बदलें", + "edit_date_and_time_by_offset_interval": "नयी दिनांक सीमा: {from} - {to}", "edit_description": "संपादित करें वर्णन", "edit_description_prompt": "कृपया एक नया विवरण चुनें:", "edit_exclusion_pattern": "बहिष्करण पैटर्न संपादित करें", @@ -849,7 +906,6 @@ "edit_tag": "टैग बदलें", "edit_title": "शीर्षक संपादित करें", "edit_user": "यूजर को संपादित करो", - "edited": "संपादित", "editor": "संपादक", "editor_close_without_save_prompt": "परिवर्तन सहेजे नहीं जाएँगे", "editor_close_without_save_title": "संपादक बंद करें?", @@ -872,7 +928,9 @@ "error": "गलती", "error_change_sort_album": "एल्बम का क्रम बदलने में असफल रहा", "error_delete_face": "एसेट से चेहरे को हटाने में त्रुटि हुई", + "error_getting_places": "स्थानों को प्राप्त करने में त्रुटि हुई", "error_loading_image": "छवि लोड करने में त्रुटि", + "error_loading_partners": "जोड़ीदार लोड करने में त्रुटि हुई: {error}", "error_saving_image": "त्रुटि: {error}", "error_tag_face_bounding_box": "चेहरे को टैग करने में त्रुटि – बाउंडिंग बॉक्स निर्देशांक प्राप्त नहीं कर सके", "error_title": "त्रुटि - कुछ गलत हो गया", @@ -905,6 +963,7 @@ "failed_to_load_notifications": "सूचनाएँ लोड करने में विफल", "failed_to_load_people": "लोगों को लोड करने में विफल", "failed_to_remove_product_key": "उत्पाद कुंजी निकालने में विफल", + "failed_to_reset_pin_code": "पिन कोड रीसेट करना विफल हुआ", "failed_to_stack_assets": "परिसंपत्तियों का ढेर लगाने में विफल", "failed_to_unstack_assets": "परिसंपत्तियों का ढेर खोलने में विफल", "failed_to_update_notification_status": "सूचना की स्थिति अपडेट करने में विफल", @@ -913,6 +972,7 @@ "paths_validation_failed": "{paths, plural, one {# पथ} other {# पथ}} सत्यापन में विफल रहे", "profile_picture_transparent_pixels": "प्रोफ़ाइल चित्रों में पारदर्शी पिक्सेल नहीं हो सकते।", "quota_higher_than_disk_size": "आपने डिस्क आकार से अधिक कोटा निर्धारित किया है", + "something_went_wrong": "कुछ त्रुटि हुई", "unable_to_add_album_users": "उपयोगकर्ताओं को एल्बम में डालने में असमर्थ", "unable_to_add_assets_to_shared_link": "साझा लिंक में संपत्ति डालने में असमर्थ", "unable_to_add_comment": "टिप्पणी डालने में असमर्थ", @@ -998,22 +1058,42 @@ }, "exif": "एक्सिफ", "exif_bottom_sheet_description": "विवरण जोड़ें..।", + "exif_bottom_sheet_description_error": "विवरण के आधुनीकरण करने में त्रुटि हुई", + "exif_bottom_sheet_details": "विवरण", + "exif_bottom_sheet_location": "स्थान", + "exif_bottom_sheet_no_description": "कोई विवरण नहीं", + "exif_bottom_sheet_people": "लोग", "exif_bottom_sheet_person_add_person": "नाम डालें", "exit_slideshow": "स्लाइड शो से बाहर निकलें", "expand_all": "सभी का विस्तार", + "experimental_settings_new_asset_list_subtitle": "कार्य प्रगति पर है", + "experimental_settings_new_asset_list_title": "प्रयोगात्मक फोटो ग्रिड सक्षम करें", + "experimental_settings_subtitle": "अपने जोखिम पर उपयोग करें!", + "experimental_settings_title": "प्रयोगात्मक", "expire_after": "एक्सपायर आफ्टर", "expired": "खत्म हो चुका", + "expires_date": "{date} को समाप्त हो रहा है", "explore": "अन्वेषण करना", + "explorer": "समन्वेषक", "export": "निर्यात", "export_as_json": "JSON के रूप में निर्यात करें", + "export_database": "डेटाबेस निर्यात करें", + "export_database_description": "इस.क्यू.लाइट डेटाबेस निर्यात करें", "extension": "विस्तार", "external": "बाहरी", "external_libraries": "बाहरी पुस्तकालय", + "external_network": "बाहरी नेटवर्क", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", "face_unassigned": "सौंपे नहीं गए", + "failed": "विफल हुआ", + "failed_to_authenticate": "प्रमाणित करने में विफल", + "failed_to_load_assets": "एसेट लोड करने में विफल", + "failed_to_load_folder": "फोल्डर लोड करने में विफल", "favorite": "पसंदीदा", + "favorite_action_prompt": "{count} पसंदीदा संकलन में जोड़े गए", "favorite_or_unfavorite_photo": "पसंदीदा या नापसंद फोटो", "favorites": "पसंदीदा", + "favorites_page_no_favorites": "कोई पसंदीदा एसेट नहीं मिले", "feature_photo_updated": "फ़ीचर फ़ोटो अपडेट किया गया", "file_name": "फ़ाइल का नाम", "file_name_or_extension": "फ़ाइल का नाम या एक्सटेंशन", @@ -1546,6 +1626,7 @@ "year": "वर्ष", "yes": "हाँ", "you_dont_have_any_shared_links": "आपके पास कोई साझा लिंक नहीं है", - "your_wifi_name": "Your WiFi name", - "zoom_image": "छवि ज़ूम करें" + "your_wifi_name": "आपके वाईफाई का नाम", + "zoom_image": "छवि ज़ूम करें", + "zoom_to_bounds": "सीमा तक ज़ूम करें" } diff --git a/i18n/hr.json b/i18n/hr.json index 32331976c1..a900ebac2a 100644 --- a/i18n/hr.json +++ b/i18n/hr.json @@ -15,9 +15,9 @@ "add_a_name": "Dodaj ime", "add_a_title": "Dodaj naslov", "add_birthday": "Dodaj rođendan", - "add_endpoint": "Dodaj krajnju točnu", + "add_endpoint": "Dodaj krajnju točku", "add_exclusion_pattern": "Dodaj uzorak izuzimanja", - "add_import_path": "Dodaj import folder", + "add_import_path": "Dodaj putanju uvoza", "add_location": "Dodaj lokaciju", "add_more_users": "Dodaj još korisnika", "add_partner": "Dodaj partnera", @@ -28,35 +28,37 @@ "add_to_album": "Dodaj u album", "add_to_album_bottom_sheet_added": "Dodano u {album}", "add_to_album_bottom_sheet_already_exists": "Već u {album}", + "add_to_album_bottom_sheet_some_local_assets": "Neke lokalne stavke nije moguće dodati u album", "add_to_album_toggle": "Uključi/isključi odabir za {album}", "add_to_albums": "Dodaj u albume", "add_to_albums_count": "Dodaj u albume ({count})", "add_to_shared_album": "Dodaj u dijeljeni album", + "add_upload_to_stack": "Dodaj preneseno u skup", "add_url": "Dodaj URL", "added_to_archive": "Dodano u arhivu", "added_to_favorites": "Dodano u omiljeno", "added_to_favorites_count": "Dodano {count, number} u omiljeno", "admin": { - "add_exclusion_pattern_description": "Dodajte uzorke izuzimanja. Globiranje pomoću *, ** i ? je podržano. Za ignoriranje svih datoteka u bilo kojem direktoriju pod nazivom \"Raw\", koristite \"**/Raw/**\". Da biste zanemarili sve datoteke koje završavaju na \".tif\", koristite \"**/*.tif\". Da biste zanemarili apsolutni put, koristite \"/path/to/ignore/**\".", + "add_exclusion_pattern_description": "Dodajte uzorke izuzimanja. Globiranje pomoću *, ** i ? je podržano. Za ignoriranje svih datoteka u bilo kojem direktoriju pod nazivom \"Raw\", koristite \"**/Raw/**\". Kako biste ignorirali sve datoteke koje završavaju na \".tif\", koristite \"**/*.tif\". Kako biste ignorirali apsolutnu putanju, koristite \"/putanja/za/ignoriranje/**\".", "admin_user": "Administrator", - "asset_offline_description": "Ovo sredstvo vanjske knjižnice više nije pronađeno na disku i premješteno je u smeće. Ako je datoteka premještena unutar biblioteke, provjerite svoju vremensku traku za novo odgovarajuće sredstvo. Da biste vratili ovo sredstvo, provjerite može li Immich pristupiti donjoj stazi datoteke i skenirajte biblioteku.", - "authentication_settings": "Postavke autentikacije", - "authentication_settings_description": "Uredi lozinku, OAuth, i druge postavke autentikacije", - "authentication_settings_disable_all": "Jeste li sigurni da želite onemogućenit sve načine prijave? Prijava će biti potpuno onemogućena.", - "authentication_settings_reenable": "Za ponovno uključivanje upotrijebite naredbu poslužitelja.", + "asset_offline_description": "Ova stavka vanjske biblioteke nije pronađena na disku i premještena je u smeće. Ako je datoteka premještena unutar biblioteke, provjerite svoju vremensku traku za novu odgovarajuću stavku. Da biste vratili ovu stavku, provjerite može li Immich pristupiti donjoj putanji datoteke i skenirajte biblioteku.", + "authentication_settings": "Postavke autentifikacije", + "authentication_settings_description": "Upravljajte lozinkom, OAuthom i drugim postavkama autentifikacije", + "authentication_settings_disable_all": "Jeste li sigurni da želite onemogućiti sve načine prijave? Prijava će biti potpuno onemogućena.", + "authentication_settings_reenable": "Za ponovno uključivanje upotrijebite naredbu servera.", "background_task_job": "Pozadinski zadaci", "backup_database": "Kreiraj sigurnosnu kopiju baze podataka", "backup_database_enable_description": "Omogućite sigurnosne kopije baze podataka", "backup_keep_last_amount": "Količina prethodnih sigurnosnih kopija za čuvanje", "backup_onboarding_1_description": "kopija izvan lokacije u oblaku ili na drugoj fizičkoj lokaciji.", - "backup_onboarding_2_description": "lokalne kopije na različitim uređajima. To uključuje glavne datoteke i sigurnosnu kopiju tih datoteka lokalno.", + "backup_onboarding_2_description": "lokalne kopije na različitim uređajima. To uključuje glavne datoteke i lokalnu sigurnosnu kopiju tih datoteka.", "backup_onboarding_3_description": "ukupne kopije vaših podataka, uključujući izvorne datoteke. To uključuje 1 kopiju izvan lokacije i 2 lokalne kopije.", "backup_onboarding_description": "Preporučuje se 3-2-1 strategija sigurnosnog kopiranja za zaštitu vaših podataka. Trebali biste čuvati kopije svojih prenesenih fotografija/videozapisa kao i Immich bazu podataka za sveobuhvatno rješenje sigurnosne kopije.", - "backup_onboarding_footer": "Za više informacija o sigurnosnom kopiranju Immich, molimo pogledajte dokumentaciju.", + "backup_onboarding_footer": "Za više informacija o sigurnosnom kopiranju Immicha, molimo pogledajte dokumentaciju.", "backup_onboarding_parts_title": "3-2-1 sigurnosna kopija uključuje:", "backup_onboarding_title": "Sigurnosne kopije", - "backup_settings": "Postavke sigurnosne kopije", - "backup_settings_description": "Upravljajte postavkama izvoza baze podataka.", + "backup_settings": "Postavke sigurnosne kopije baze podataka", + "backup_settings_description": "Upravljajte postavkama sigurnosne kopije baze podataka.", "cleared_jobs": "Izbrisani poslovi za: {job}", "config_set_by_file": "Konfiguracija je trenutno postavljena konfiguracijskom datotekom", "confirm_delete_library": "Jeste li sigurni da želite izbrisati biblioteku {library}?", @@ -65,23 +67,23 @@ "confirm_reprocess_all_faces": "Jeste li sigurni da želite ponovno obraditi sva lica? Ovo će također obrisati imenovane osobe.", "confirm_user_password_reset": "Jeste li sigurni da želite poništiti lozinku korisnika {user}?", "confirm_user_pin_code_reset": "Jeste li sigurni da želite resetirati PIN korisnika {user}?", - "create_job": "Izradi zadatak", - "cron_expression": "Cron izraz (expression)", + "create_job": "Stvori posao", + "cron_expression": "Cron izraz", "cron_expression_description": "Postavite interval skeniranja koristeći cron format. Za više informacija pogledajte npr. Crontab Guru", - "cron_expression_presets": "Cron unaprijed postavljene postavke izraza", + "cron_expression_presets": "Unaprijed postavljene postavke cron izraza", "disable_login": "Onemogući prijavu", - "duplicate_detection_job_description": "Pokrenite strojno učenje na materijalima kako biste otkrili slične slike. Oslanja se na Pametno Pretraživanje", - "exclusion_pattern_description": "Uzorci izuzimanja omogućuju vam da zanemarite datoteke i mape prilikom skeniranja svoje biblioteke. Ovo je korisno ako imate mape koje sadrže datoteke koje ne želite uvesti, kao što su RAW datoteke.", - "external_library_management": "Upravljanje vanjskom knjižnicom", + "duplicate_detection_job_description": "Pokrenite strojno učenje na stavkama kako biste otkrili slične slike. Oslanja se na Pametno pretraživanje", + "exclusion_pattern_description": "Uzorci izuzimanja omogućuju vam da ignorirate datoteke i mape prilikom skeniranja svoje biblioteke. Ovo je korisno ako imate mape koje sadrže datoteke koje ne želite uvesti, kao što su RAW datoteke.", + "external_library_management": "Upravljanje vanjskom bibliotekom", "face_detection": "Detekcija lica", - "face_detection_description": "Prepoznajte lica u sredstvima pomoću strojnog učenja. Za videozapise u obzir se uzima samo minijaturni prikaz. \"Sve\" (ponovno) obrađuje svu imovinu. \"Nedostaje\" stavlja u red čekanja sredstva koja još nisu obrađena. Otkrivena lica bit će stavljena u red čekanja za prepoznavanje lica nakon dovršetka prepoznavanja lica, grupirajući ih u postojeće ili nove osobe.", - "facial_recognition_job_description": "Grupirajte otkrivena lica u osobe. Ovaj se korak pokreće nakon dovršetka prepoznavanja lica. \"Sve\" (ponovno) grupira sva lica. \"Nedostajuća\" lica u redovima kojima nije dodijeljena osoba.", + "face_detection_description": "Detektirajte lica u stavkama pomoću strojnog učenja. Za videozapise se uzima u obzir samo sličica. \"Osvježi\" (ponovno) obrađuje sve stavke. \"Poništi\" dodatno briše sve trenutne podatke o licu. \"Nedostaje\" stavlja u red čekanja stavke koje još nisu obrađene. Detektirana lica bit će stavljena u red čekanja za Prepoznavanje lica nakon što se dovrši Detekcija lica, grupirajući ih u postojeće ili nove osobe.", + "facial_recognition_job_description": "Grupirajte otkrivena lica u osobe. Ovaj korak se izvršava nakon što je Detekcija lica dovršena. \"Resetiraj\" (ponovno) grupira sva lica. \"Nedostaje\" stavlja u red lica kojima nije dodijeljena osoba.", "failed_job_command": "Naredba {command} nije uspjela za posao: {job}", - "force_delete_user_warning": "UPOZORENJE: Ovo će odmah ukloniti korisnika i sve pripadajuće podatke. Ovo se ne može poništiti i datoteke se ne mogu vratiti.", + "force_delete_user_warning": "UPOZORENJE: Ovo će odmah ukloniti korisnika i sve pripadajuće stavke. Ovo se ne može poništiti i datoteke se ne mogu vratiti.", "image_format": "Format", "image_format_description": "WebP proizvodi manje datoteke od JPEG-a, ali se sporije kodira.", - "image_fullsize_description": "Slika pune veličine bez meta podataka, koristi se prilikom zumiranja", - "image_fullsize_enabled": "Omogući generiranje slike pune veličine", + "image_fullsize_description": "Slika pune veličine bez metapodataka, koristi se prilikom zumiranja", + "image_fullsize_enabled": "Omogući generiranje slika pune veličine", "image_fullsize_enabled_description": "Generiraj sliku pune veličine za formate koji nisu prilagođeni webu. Kada je opcija \"Preferiraj ugrađeni pregled\" omogućena, ugrađeni pregledi koriste se izravno bez konverzije. Ne utječe na formate prilagođene webu kao što je JPEG.", "image_fullsize_quality_description": "Kvaliteta slike pune veličine od 1 do 100. Veća vrijednost znači bolja kvaliteta, ali stvara veće datoteke.", "image_fullsize_title": "Postavke slike pune veličine", @@ -89,7 +91,7 @@ "image_prefer_embedded_preview_setting_description": "Koristite ugrađene preglede u RAW fotografije kao ulaz za obradu slike kada su dostupni. To može proizvesti preciznije boje za neke slike, ali kvaliteta pregleda ovisi o kameri i slika može imati više artifakta kompresije.", "image_prefer_wide_gamut": "Preferirajte široku gamu", "image_prefer_wide_gamut_setting_description": "Koristite Display P3 za sličice. Ovo bolje čuva živost slika sa širokim prostorima boja, ali slike mogu izgledati drugačije na starim uređajima sa starom verzijom preglednika. sRGB slike čuvaju se kao sRGB kako bi se izbjegle promjene boja.", - "image_preview_description": "Slika srednje veličine s ogoljenim metapodacima, koristi se prilikom pregledavanja jednog sredstva i za strojno učenje", + "image_preview_description": "Slika srednje veličine s uklonjenim metapodacima, koristi se prilikom pregledavanja jedne stavke i za strojno učenje", "image_preview_quality_description": "Kvaliteta pregleda od 1-100. Više je bolje, ali proizvodi veće datoteke i može smanjiti odziv aplikacije. Postavljanje niske vrijednosti može utjecati na kvalitetu strojnog učenja.", "image_preview_title": "Postavke pregleda", "image_quality": "Kvaliteta", @@ -116,27 +118,31 @@ "library_scanning_enable_description": "Omogući periodično skeniranje biblioteke", "library_settings": "Externa biblioteka", "library_settings_description": "Upravljajte postavkama vanjske biblioteke", - "library_tasks_description": "Skeniraj eksterne biblioteke za nove i/ili promijenjene resurse", + "library_tasks_description": "Skeniraj vanjske biblioteke za nove i/ili promijenjene stavke", "library_watching_enable_description": "Pratite vanjske biblioteke za promjena datoteke", "library_watching_settings": "Gledanje biblioteke (EKSPERIMENTALNO)", "library_watching_settings_description": "Automatsko praćenje promijenjenih datoteke", "logging_enable_description": "Omogući zapisivanje", "logging_level_description": "Kada je omogućeno, koju razinu zapisivanja koristiti.", "logging_settings": "Zapisivanje", + "machine_learning_availability_checks": "Provjere dostupnosti", + "machine_learning_availability_checks_enabled": "Omogući provjere dostupnosti", + "machine_learning_availability_checks_interval": "Provjeri interval", + "machine_learning_availability_checks_interval_description": "Interval u milisekundama između provjera dostupnosti", "machine_learning_clip_model": "CLIP model", "machine_learning_clip_model_description": "Naziv CLIP modela navedenog ovdje. Imajte na umu da morate ponovno pokrenuti posao 'Pametno Pretraživanje' za sve slike nakon promjene modela.", "machine_learning_duplicate_detection": "Detekcija Duplikata", "machine_learning_duplicate_detection_enabled": "Omogući detekciju duplikata", - "machine_learning_duplicate_detection_enabled_description": "Ako je onemogućeno, potpuno identična sredstva i dalje će biti deduplicirana.", + "machine_learning_duplicate_detection_enabled_description": "Ako je onemogućeno, potpuno identične stavke i dalje će biti deduplicirane.", "machine_learning_duplicate_detection_setting_description": "Upotrijebite CLIP ugradnje da biste pronašli vjerojatne duplikate", "machine_learning_enabled": "Uključi strojsko učenje", "machine_learning_enabled_description": "Ukoliko je ovo isključeno, sve funkcije strojnoga učenja biti će isključene bez obzira na postavke ispod.", - "machine_learning_facial_recognition": "Detekcija lica", + "machine_learning_facial_recognition": "Prepoznavanje lica", "machine_learning_facial_recognition_description": "Detektiraj, prepoznaj i grupiraj lica u fotografijama", "machine_learning_facial_recognition_model": "Model prepoznavanja lica", "machine_learning_facial_recognition_model_description": "Modeli su navedeni silaznim redoslijedom veličine. Veći modeli su sporiji i koriste više memorije, ali daju bolje rezultate. Imajte na umu da morate ponovno pokrenuti posao detekcije lica za sve slike nakon promjene modela.", "machine_learning_facial_recognition_setting": "Omogući prepoznavanje lica", - "machine_learning_facial_recognition_setting_description": "Ako je onemogućeno, slike neće biti kodirane za prepoznavanje lica i neće popuniti odjeljak Ljudi na stranici Istraživanje.", + "machine_learning_facial_recognition_setting_description": "Ako je onemogućeno, slike neće biti kodirane za prepoznavanje lica i neće popuniti odjeljak Osobe na stranici Istraži.", "machine_learning_max_detection_distance": "Maksimalna udaljenost za detektiranje", "machine_learning_max_detection_distance_description": "Maksimalna udaljenost između dvije slike da bi se smatrale duplikatima, u rasponu od 0,001-0,1. Više vrijednosti otkrit će više duplikata, ali mogu rezultirati netočnim rezultatima.", "machine_learning_max_recognition_distance": "Maksimalna udaljenost za detekciju", @@ -144,7 +150,7 @@ "machine_learning_min_detection_score": "Minimalni rezultat otkrivanja", "machine_learning_min_detection_score_description": "Minimalni rezultat pouzdanosti za detektirano lice od 0-1. Niže vrijednosti otkrit će više lica, ali mogu dovesti do lažno pozitivnih rezultata.", "machine_learning_min_recognized_faces": "Minimum prepoznatih lica", - "machine_learning_min_recognized_faces_description": "Najmanji broj prepoznatih lica za osobu koja se stvara. Povećanje toga čini prepoznavanje lica preciznijim po cijenu povećanja šanse da lice nije dodijeljeno osobi.", + "machine_learning_min_recognized_faces_description": "Najmanji broj prepoznatih lica za osobu koja se stvara. Povećanje toga čini Prepoznavanje lica preciznijim po cijenu povećanja šanse da lice nije dodijeljeno osobi.", "machine_learning_settings": "Postavke strojnog učenja", "machine_learning_settings_description": "Upravljajte značajkama i postavkama strojnog učenja", "machine_learning_smart_search": "Pametna pretraga", @@ -170,21 +176,21 @@ "memory_cleanup_job": "Čišćenje memorije", "memory_generate_job": "Generiranje memorije", "metadata_extraction_job": "Izdvoj metapodatke", - "metadata_extraction_job_description": "Izdvojite podatke o metapodacima iz svakog sredstva, kao što su GPS, lica i rezolucija", + "metadata_extraction_job_description": "Izdvojite metapodatke iz svake stavke, kao što su GPS, lica i rezolucija", "metadata_faces_import_setting": "Omogući uvoz lica", "metadata_faces_import_setting_description": "Uvezite lica iz EXIF podataka slike i sidecar datoteka", "metadata_settings": "Postavke Metapodataka", "metadata_settings_description": "Upravljanje postavkama metapodataka", "migration_job": "Migracija", - "migration_job_description": "Premjestite minijature za sredstva i lica u najnoviju strukturu mapa", + "migration_job_description": "Premjestite sličice za stavke i lica u najnoviju strukturu mapa", "nightly_tasks_cluster_faces_setting_description": "Pokreni prepoznavanje lica na novootkrivenim licima", "nightly_tasks_cluster_new_faces_setting": "Grupiraj nova lica", "nightly_tasks_database_cleanup_setting": "Zadaci čišćenja baze podataka", "nightly_tasks_database_cleanup_setting_description": "Očisti stare, istekle podatke iz baze podataka", "nightly_tasks_generate_memories_setting": "Generiraj uspomene", - "nightly_tasks_generate_memories_setting_description": "Stvori nove uspomene iz sadržaja", + "nightly_tasks_generate_memories_setting_description": "Stvori nove uspomene iz stavki", "nightly_tasks_missing_thumbnails_setting": "Generiraj nedostajuće sličice", - "nightly_tasks_missing_thumbnails_setting_description": "Stavi u red čekanja sadržaje bez sličica za generiranje sličica", + "nightly_tasks_missing_thumbnails_setting_description": "Stavke bez sličica stavi u red čekanja za generiranje sličica", "nightly_tasks_settings": "Postavke noćnih zadataka", "nightly_tasks_settings_description": "Upravljanje noćnim zadacima", "nightly_tasks_start_time_setting": "Vrijeme početka", @@ -193,7 +199,7 @@ "nightly_tasks_sync_quota_usage_setting_description": "Ažuriraj korisničku kvotu za pohranu na temelju trenutne potrošnje", "no_paths_added": "Nema dodanih putanja", "no_pattern_added": "Nije dodan uzorak", - "note_apply_storage_label_previous_assets": "Napomena: da biste primijenili Oznaku Pohrane na prethodno prenesena sredstva, pokrenite", + "note_apply_storage_label_previous_assets": "Napomena: Da biste primijenili Oznaku pohrane na prethodno prenesene stavke, pokrenite", "note_cannot_be_changed_later": "NAPOMENA: Ovo se ne može promijeniti kasnije!", "notification_email_from_address": "Od adrese", "notification_email_from_address_description": "E-mail adresa pošiljatelja, na primjer: \"Immich Photo Server \". Obavezno koristite adresu s koje vam je dopušteno slanje e-pošte.", @@ -202,6 +208,8 @@ "notification_email_ignore_certificate_errors_description": "Ignoriraj pogreške provjere valjanosti TLS certifikata (nije preporučeno)", "notification_email_password_description": "Lozinka za korištenje pri autentifikaciji s poslužiteljem e-pošte", "notification_email_port_description": "Port poslužitelja e-pošte (npr. 25, 465, ili 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "Koristi SMTPS (SMTP umjesto TLS)", "notification_email_sent_test_email_button": "Pošaljite probni e-mail i spremi", "notification_email_setting_description": "Postavke za slanje e-mail obavijeste", "notification_email_test_email": "Pošalji probni e-mail", @@ -260,21 +268,21 @@ "sidecar_job": "Sidecar metapodaci", "sidecar_job_description": "Otkrijte ili sinkronizirajte sidecar metapodatke iz datotečnog sustava", "slideshow_duration_description": "Broj sekundi za prikaz svake slike", - "smart_search_job_description": "Pokrenite strojno učenje na sredstvima za podršku pametnog pretraživanja", - "storage_template_date_time_description": "Vremenska oznaka stvaranja sredstva koristi se za informacije o datumu i vremenu", + "smart_search_job_description": "Pokrenite strojno učenje na stavkama za korištenje Pametnog pretraživanja", + "storage_template_date_time_description": "Vremenska oznaka stvaranja stavke koristi se za informacije o datumu i vremenu", "storage_template_date_time_sample": "Vrijeme uzorka {date}", "storage_template_enable_description": "Omogući mehanizam predloška za pohranu", "storage_template_hash_verification_enabled": "Omogućena hash provjera", "storage_template_hash_verification_enabled_description": "Omogućuje hash provjeru, nemojte je onemogućiti osim ako niste sigurni u implikacije", "storage_template_migration": "Migracija predloška za pohranu", - "storage_template_migration_description": "Primijenite trenutni {template} na prethodno prenesena sredstva", - "storage_template_migration_info": "Predložak za pohranu će sve nastavke (ekstenzije) pretvoriti u mala slova. Promjene predloška primjenjivat će se samo na nova sredstva. Za retroaktivnu primjenu predloška na prethodno prenesena sredstva, pokrenite {job}.", + "storage_template_migration_description": "Primijenite trenutni {template} na prethodno prenesene stavke", + "storage_template_migration_info": "Predložak za pohranu pretvorit će sve datotečne nastavke u mala slova. Promjene predloška primijenit će se samo na nove stavke. Da biste retroaktivno primijenili predložak na prethodno prenesene stavke, pokrenite {job}.", "storage_template_migration_job": "Posao Migracije Predloška Pohrane", "storage_template_more_details": "Za više pojedinosti o ovoj značajci pogledajte Predložak pohrane i njegove implikacije", "storage_template_onboarding_description_v2": "Kada je omogućena, ova će značajka automatski organizira datoteke prema predlošku koji je definirao korisnik. Za više informacija pogledajte dokumentaciju.", "storage_template_path_length": "Približno ograničenje duljine putanje: {length, number}/{limit, number}", "storage_template_settings": "Predložak pohrane", - "storage_template_settings_description": "Upravljajte strukturom mape i nazivom datoteke učitanog sredstva", + "storage_template_settings_description": "Upravljajte strukturom mape i nazivom datoteke učitane stavke", "storage_template_user_label": "{label} je korisnička oznaka za pohranu", "system_settings": "Postavke Sustava", "tag_cleanup_job": "Čišćenje oznaka", @@ -292,7 +300,7 @@ "theme_settings": "Postavke tema", "theme_settings_description": "Upravljajte prilagodbom Immich web sučelja", "thumbnail_generation_job": "Generirajte sličice", - "thumbnail_generation_job_description": "Generirajte velike, male i zamućene sličice za svaki materijal, kao i sličice za svaku osobu", + "thumbnail_generation_job_description": "Generirajte velike, male i zamućene sličice za svaku stavku, kao i sličice za svaku osobu", "transcoding_acceleration_api": "API ubrzanja", "transcoding_acceleration_api_description": "API koji će komunicirati s vašim uređajem radi ubrzanja transkodiranja. Ova postavka je 'najveći trud': vratit će se na softversko transkodiranje u slučaju kvara. VP9 može ili ne mora raditi ovisno o vašem hardveru.", "transcoding_acceleration_nvenc": "NVENC (zahtjeva NVIDIA GPU)", @@ -355,20 +363,20 @@ "transcoding_video_codec_description": "VP9 ima visoku učinkovitost i web-kompatibilnost, ali treba dulje za transkodiranje. HEVC ima sličnu izvedbu, ali ima slabiju web kompatibilnost. H.264 široko je kompatibilan i brzo se transkodira, ali proizvodi mnogo veće datoteke. AV1 je najučinkovitiji kodek, ali nema podršku na starijim uređajima.", "trash_enabled_description": "Omogućite značajke Smeća", "trash_number_of_days": "Broj dana", - "trash_number_of_days_description": "Broj dana za držanje sredstava u smeću prije njihovog trajnog uklanjanja", + "trash_number_of_days_description": "Broj dana za čuvanje stavki u smeću prije njihovog trajnog uklanjanja", "trash_settings": "Postavke Smeća", "trash_settings_description": "Upravljanje postavkama smeća", "unlink_all_oauth_accounts": "Odspoji sve OAuth račune", "unlink_all_oauth_accounts_description": "Zapamtite da odspojite sve OAuth račune prije prelaska na novog pružatelja usluge.", "unlink_all_oauth_accounts_prompt": "Jeste li sigurni da želite odspojiti sve OAuth račune? Ovo će resetirati OAuth ID za svakog korisnika i ne može se poništiti.", "user_cleanup_job": "Čišćenje korisnika", - "user_delete_delay": "Račun i sredstva korisnika {user} bit će zakazani za trajno brisanje za {delay, plural, one {# day} other {# days}}.", + "user_delete_delay": "Račun i stavke korisnika {user} bit će stavljeni u red čekanja trajnog brisanja za {delay, plural, one {# dan} other {# dana}}.", "user_delete_delay_settings": "Brisanje odgode", - "user_delete_delay_settings_description": "Broj dana nakon uklanjanja za trajno brisanje korisničkog računa i imovine. Posao brisanja korisnika pokreće se u ponoć kako bi se provjerili korisnici koji su spremni za brisanje. Promjene ove postavke bit će procijenjene pri sljedećem izvršavanju.", - "user_delete_immediately": "Račun i sredstva korisnika {user} bit će stavljeni u red čekanja za trajno brisanje odmah.", - "user_delete_immediately_checkbox": "Stavite korisnika i imovinu u red za trenutačno brisanje", + "user_delete_delay_settings_description": "Broj dana nakon uklanjanja za trajno brisanje korisničkog računa i stavki. Posao brisanja korisnika pokreće se u ponoć kako bi se provjerili korisnici koji su spremni za brisanje. Promjene ove postavke bit će procijenjene pri sljedećem izvršavanju.", + "user_delete_immediately": "Račun i stavke korisnika {user} bit će stavljeni u red čekanja za trajno brisanje odmah.", + "user_delete_immediately_checkbox": "Stavite korisnika i stavke u red čekanja za trenutno brisanje", "user_details": "Detalji korisnika", - "user_management": "Upravljanje Korisnicima", + "user_management": "Upravljanje korisnicima", "user_password_has_been_reset": "Korisnička lozinka je poništena:", "user_password_reset_description": "Molimo dostavite privremenu lozinku korisniku i obavijestite ga da će morati promijeniti lozinku pri sljedećoj prijavi.", "user_restore_description": "Račun korisnika {user} bit će vraćen.", @@ -390,13 +398,15 @@ "advanced_settings_enable_alternate_media_filter_subtitle": "Koristite ovu opciju za filtriranje medija tijekom sinkronizacije na temelju alternativnih kriterija. Pokušajte ovo samo ako imate problema s aplikacijom koja ne prepoznaje sve albume.", "advanced_settings_enable_alternate_media_filter_title": "[EKSPERIMENTALNO] Koristite alternativni filter za sinkronizaciju albuma na uređaju", "advanced_settings_log_level_title": "Razina zapisivanja: {level}", - "advanced_settings_prefer_remote_subtitle": "Neki uređaji sporo učitavaju sličice s lokalnih resursa. Aktivirajte ovu postavku kako biste umjesto toga učitali slike s udaljenih izvora.", + "advanced_settings_prefer_remote_subtitle": "Neki uređaji sporo učitavaju sličice s lokalnih stavki. Aktivirajte ovu postavku kako biste umjesto toga učitali slike s udaljenih izvora.", "advanced_settings_prefer_remote_title": "Preferiraj udaljene slike", "advanced_settings_proxy_headers_subtitle": "Definirajte zaglavlja posrednika koja Immich treba slati sa svakim mrežnim zahtjevom.", "advanced_settings_proxy_headers_title": "Proxy zaglavlja", + "advanced_settings_readonly_mode_subtitle": "Omogućuje read-only mod u kojem je moguće samo pregledavanje fotografija, radnje poput odabira više fotografija, dijeljenje, proiciranje i brisanje svih fotografija su onemogućene. Upali/ugasi read-only mod preko korisnickog avatara na glavnom ekranu.", + "advanced_settings_readonly_mode_title": "Read-only mod", "advanced_settings_self_signed_ssl_subtitle": "Preskoči provjeru SSL certifikata za krajnju točku poslužitelja. Potrebno za samo-potpisane certifikate.", "advanced_settings_self_signed_ssl_title": "Dopusti samo-potpisane SSL certifikate", - "advanced_settings_sync_remote_deletions_subtitle": "Automatski izbriši ili obnovi resurs na ovom uređaju kada se ta radnja izvrši na webu", + "advanced_settings_sync_remote_deletions_subtitle": "Automatski izbriši ili obnovi stavku na ovom uređaju kada se ta radnja izvrši na webu", "advanced_settings_sync_remote_deletions_title": "Sinkroniziraj udaljena brisanja [EKSPERIMENTALNO]", "advanced_settings_tile_subtitle": "Postavke za napredne korisnike", "advanced_settings_troubleshooting_subtitle": "Omogući dodatne značajke za rješavanje problema", @@ -421,14 +431,15 @@ "album_remove_user_confirmation": "Jeste li sigurni da želite ukloniti {user}?", "album_search_not_found": "Nema albuma koji odgovaraju vašem pretraživanju", "album_share_no_users": "Čini se da ste podijelili ovaj album sa svim korisnicima ili nemate nijednog korisnika s kojim biste ga dijelili.", + "album_summary": "Sažetak albuma", "album_updated": "Album ažuriran", - "album_updated_setting_description": "Primite obavijest e-poštom kada dijeljeni album ima nova sredstva", + "album_updated_setting_description": "Primite obavijest e-poštom kada dijeljeni album ima nove stavke", "album_user_left": "Napušten {album}", "album_user_removed": "Uklonjen {user}", "album_viewer_appbar_delete_confirm": "Jeste li sigurni da želite izbrisati ovaj album s vašeg računa?", "album_viewer_appbar_share_err_delete": "Neuspješno brisanje albuma", "album_viewer_appbar_share_err_leave": "Neuspješno napuštanje albuma", - "album_viewer_appbar_share_err_remove": "Postoje problemi s uklanjanjem resursa iz albuma", + "album_viewer_appbar_share_err_remove": "Postoje problemi s uklanjanjem stavki iz albuma", "album_viewer_appbar_share_err_title": "Neuspješno mijenjanje naslova albuma", "album_viewer_appbar_share_leave": "Napusti album", "album_viewer_appbar_share_to": "Podijeli s", @@ -437,8 +448,8 @@ "albums": "Albumi", "albums_count": "{count, plural, one {{count, number} Album} other {{count, number} Albumi}}", "albums_default_sort_order": "Zadani redoslijed sortiranja albuma", - "albums_default_sort_order_description": "Početni redoslijed sortiranja elemenata prilikom izrade novih albuma.", - "albums_feature_description": "Zbirke resursa koje se mogu dijeliti s drugim korisnicima.", + "albums_default_sort_order_description": "Početni redoslijed sortiranja stavki prilikom izrade novih albuma.", + "albums_feature_description": "Zbirke stavki koje se mogu dijeliti s drugim korisnicima.", "albums_on_device_count": "Albumi na uređaju ({count})", "all": "Sve", "all_albums": "Svi albumi", @@ -454,61 +465,66 @@ "api_key_description": "Ova će vrijednost biti prikazana samo jednom. Obavezno ju kopirajte prije zatvaranja prozora.", "api_key_empty": "Naziv vašeg API ključa ne smije biti prazan", "api_keys": "API Ključevi", + "app_architecture_variant": "Varijanta(Arhitektura)", "app_bar_signout_dialog_content": "Jeste li sigurni da se želite odjaviti?", "app_bar_signout_dialog_ok": "Da", "app_bar_signout_dialog_title": "Odjavi se", - "app_settings": "Postavke Aplikacije", + "app_download_links": "Poveznica za preuzimanje aplikacije", + "app_settings": "Postavke aplikacije", + "app_update_available": "Ažuriranje aplikacije je dostupno", "appears_in": "Pojavljuje se u", "archive": "Arhiva", "archive_action_prompt": "{count} dodano u arhivu", "archive_or_unarchive_photo": "Arhivirajte ili dearhivirajte fotografiju", - "archive_page_no_archived_assets": "Nema arhiviranih resursa", + "archive_page_no_archived_assets": "Nema arhiviranih stavki", "archive_page_title": "Arhiviraj ({count})", "archive_size": "Veličina arhive", "archive_size_description": "Konfigurirajte veličinu arhive za preuzimanja (u GiB)", - "archived": "Ahrivirano", + "archived": "Arhivirano", "archived_count": "{count, plural, other {Archived #}}", "are_these_the_same_person": "Je li ovo ista osoba?", "are_you_sure_to_do_this": "Jeste li sigurni da to želite učiniti?", - "asset_action_delete_err_read_only": "Nije moguće izbrisati resurse samo za čitanje, preskačem", - "asset_action_share_err_offline": "Nije moguće dohvatiti izvanmrežne resurse, preskačem", + "asset_action_delete_err_read_only": "Nije moguće izbrisati stavke samo za čitanje, preskakanje", + "asset_action_share_err_offline": "Nije moguće dohvatiti izvanmrežne stavke, preskakanje", "asset_added_to_album": "Dodano u album", "asset_adding_to_album": "Dodavanje u album…", - "asset_description_updated": "Opis imovine je ažuriran", - "asset_filename_is_offline": "Sredstvo {filename} je izvan mreže", - "asset_has_unassigned_faces": "Materijal ima nedodijeljena lica", - "asset_hashing": "Sažimanje…", + "asset_description_updated": "Opis stavke je ažuriran", + "asset_filename_is_offline": "Stavka {filename} je izvan mreže", + "asset_has_unassigned_faces": "Stavka ima nedodijeljena lica", + "asset_hashing": "Hashiranje…", "asset_list_group_by_sub_title": "Grupiraj po", "asset_list_layout_settings_dynamic_layout_title": "Dinamički raspored", "asset_list_layout_settings_group_automatically": "Automatski", - "asset_list_layout_settings_group_by": "Grupiraj resurse po", + "asset_list_layout_settings_group_by": "Grupiraj stavke po", "asset_list_layout_settings_group_by_month_day": "Mjesec + dan", "asset_list_layout_sub_title": "Raspored", - "asset_list_settings_subtitle": "Postavke izgleda mreže fotografija", - "asset_list_settings_title": "Mreža Fotografija", - "asset_offline": "Sredstvo izvan mreže", - "asset_offline_description": "Ovaj materijal je izvan mreže. Immich ne može pristupiti lokaciji datoteke. Provjerite je li sredstvo dostupno, a zatim ponovno skenirajte biblioteku.", - "asset_restored_successfully": "Resurs uspješno obnovljen", + "asset_list_settings_subtitle": "Postavke izgleda Mreže fotografija", + "asset_list_settings_title": "Mreža fotografija", + "asset_offline": "Stavka izvan mreže", + "asset_offline_description": "Ova vanjska stavka nije pronađena na disku. Za pomoć se obratite Immich administratoru.", + "asset_restored_successfully": "Stavka uspješno obnovljena", "asset_skipped": "Preskočeno", "asset_skipped_in_trash": "U smeću", - "asset_uploaded": "Učitano", - "asset_uploading": "Šaljem…", - "asset_viewer_settings_subtitle": "Upravljajte postavkama preglednika vaše galerije", - "asset_viewer_settings_title": "Preglednik Resursa", - "assets": "Sredstva", - "assets_added_count": "Dodano {count, plural, one {# asset} other {# assets}}", - "assets_added_to_album_count": "Dodano {count, plural, one {# asset} other {# assets}} u album", - "assets_added_to_albums_count": "Dodano je {assetTotal} datoteka u {albumTotal} albuma", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Sadržaj se ne može dodati u album} other {{count} sadržaja se ne mogu dodati u album}}", - "assets_cannot_be_added_to_albums": "{count, plural, one {Datoteka se ne može dodati ni u jedan album} few {Datoteke se ne mogu dodati ni u jedan album} other {Datoteka se ne može dodati ni u jedan album}}", - "assets_count": "{count, plural, one {# asset} other {# assets}}", - "assets_deleted_permanently": "{count} resurs(i) uspješno uklonjeni", - "assets_deleted_permanently_from_server": "{count} resurs(i) trajno obrisan(i) sa Immich poslužitelja", - "assets_downloaded_failed": "{count, plural, one {Preuzeta # datoteka – {error} datoteka nije uspjela} other {Preuzeto je # datoteka – {error} datoteke nisu uspjele}}", - "assets_downloaded_successfully": "{count, plural, one {Uspješno preuzeta # datoteka} other {Uspješno preuzete # datoteke}}", - "assets_moved_to_trash_count": "{count, plural, one {# asset} other {# asset}} premješteno u smeće", - "assets_permanently_deleted_count": "Trajno izbrisano {count, plural, one {# asset} other {# assets}}", - "assets_removed_count": "Uklonjeno {count, plural, one {# asset} other {# assets}}", + "asset_trashed": "Stavka premještena u smeće", + "asset_troubleshoot": "Rješavanje problema sa stavkom", + "asset_uploaded": "Preneseno", + "asset_uploading": "Prenošenje…", + "asset_viewer_settings_subtitle": "Upravljajte postavkama vašeg preglednika galerije", + "asset_viewer_settings_title": "Preglednik stavki", + "assets": "Stavke", + "assets_added_count": "{count, plural, one {Dodana # stavka} few {Dodane # stavke} other {Dodano # stavki}}", + "assets_added_to_album_count": "{count, plural, one {Dodana # stavka} few {Dodane # stavke} other {Dodano # stavki}} u album", + "assets_added_to_albums_count": "{assetTotal, plural, one {Dodana # stavka} other {Dodano # stavki}} u {albumTotal, plural, one {# album} other {# albuma}}", + "assets_cannot_be_added_to_album_count": "{count, plural, one {Stavka se ne može} other {Stavke se ne mogu}} dodati u album", + "assets_cannot_be_added_to_albums": "{count, plural, one {Stavka se ne može} few {Stavke se ne mogu} other {Stavki se ne može}} dodati ni u jedan album", + "assets_count": "{count, plural, one {# stavka} few {# stavke} other {# stavki}}", + "assets_deleted_permanently": "Trajno {count, plural, one {izbrisana # stavka} few {izbrisane # stavke} other {izbrisano # stavki}}", + "assets_deleted_permanently_from_server": "Trajno {count, plural, one {izbrisana # stavka} few {izbrisane # stavke} other {izbrisano # stavki}} s Immich servera", + "assets_downloaded_failed": "{count, plural, one {Preuzeta # datoteka – {error} datoteka nije uspjela} few {Preuzete # datoteke - {error} datoteke nisu uspjele} other {Preuzeto # datoteka – {error} datoteke nisu uspjele}}", + "assets_downloaded_successfully": "{count, plural, one {Uspješno preuzeta # datoteka} few {Uspješno preuzete # datoteke} other {Uspješno preueto # datoteka}}", + "assets_moved_to_trash_count": "{count, plural, one {# stavka premještena} few {# stavke premještene} other {# stavk premještenoi}} u smeće", + "assets_permanently_deleted_count": "Trajno {count, plural, one {izbrisana # stavka} few {izbrisane # stavke} other {izbrisano # stavki}}", + "assets_removed_count": "{count, plural, one {Uklonjena # stavka} few {Uklonjene # stavke} other {Uklonjeno # stavki}}", "assets_removed_permanently_from_device": "{count} resurs(i) trajno uklonjen(i) s vašeg uređaja", "assets_restore_confirmation": "Jeste li sigurni da želite obnoviti sve svoje resurse bačene u otpad? Ne možete poništiti ovu radnju! Imajte na umu da se bilo koji izvanmrežni resursi ne mogu obnoviti na ovaj način.", "assets_restored_count": "Vraćeno {count, plural, one {# asset} other {# assets}}", @@ -518,7 +534,7 @@ "assets_trashed_from_server": "{count} resurs(i) premješten(i) u smeće s Immich poslužitelja", "assets_were_part_of_album_count": "{count, plural, one {Asset was} other {Assets were}} već dio albuma", "assets_were_part_of_albums_count": "{count, plural, one {Datoteka je već bila dio albuma} few {Datoteke su već bile dio albuma} other {Datoteka je već bila dio albuma}}", - "authorized_devices": "Ovlašteni Uređaji", + "authorized_devices": "Ovlašteni uređaji", "automatic_endpoint_switching_subtitle": "Povežite se lokalno preko naznačene Wi-Fi mreže kada je dostupna i koristite alternativne veze na drugim lokacijama", "automatic_endpoint_switching_title": "Automatsko prebacivanje URL-a", "autoplay_slideshow": "Automatsko prikazivanje slajdova", @@ -680,7 +696,6 @@ "comments_and_likes": "Komentari i lajkovi", "comments_are_disabled": "Komentari onemogućeni", "common_create_new_album": "Kreiraj novi album", - "common_server_error": "Provjerite svoju mrežnu vezu, osigurajte da je poslužitelj dostupan i da su verzije aplikacije/poslužitelja kompatibilne.", "completed": "Dovršeno", "confirm": "Potvrdi", "confirm_admin_password": "Potvrdite lozinku administratora", @@ -861,7 +876,6 @@ "edit_tag": "Uredi oznaku", "edit_title": "Uredi Naslov", "edit_user": "Uredi korisnika", - "edited": "Uređeno", "editor": "Urednik", "editor_close_without_save_prompt": "Promjene neće biti spremljene", "editor_close_without_save_title": "Zatvoriti uređivač?", @@ -1034,7 +1048,7 @@ "export_database_description": "Izvezi SQLite bazu podataka", "extension": "Proširenje (Extension)", "external": "Vanjski", - "external_libraries": "Vanjske Biblioteke", + "external_libraries": "Vanjske biblioteke", "external_network": "Vanjska mreža", "external_network_sheet_info": "Kada niste na željenoj Wi-Fi mreži, aplikacija će se povezati s poslužiteljem putem prve dostupne URL adrese s popisa ispod, redom od vrha prema dnu", "face_unassigned": "Nedodijeljeno", @@ -1048,7 +1062,7 @@ "favorites": "Omiljene", "favorites_page_no_favorites": "Nema pronađenih omiljenih stavki", "feature_photo_updated": "Istaknuta fotografija ažurirana", - "features": "Značajke (Features)", + "features": "Značajke", "features_setting_description": "Upravljajte značajkama aplikacije", "file_name": "Naziv datoteke", "file_name_or_extension": "Naziv ili ekstenzija datoteke", @@ -1092,7 +1106,6 @@ "header_settings_field_validator_msg": "Vrijednost ne može biti prazna", "header_settings_header_name_input": "Naziv zaglavlja", "header_settings_header_value_input": "Vrijednost zaglavlja", - "headers_settings_tile_subtitle": "Definirajte proxy zaglavlja koja aplikacija treba slati sa svakim mrežnim zahtjevom", "headers_settings_tile_title": "Prilagođena proxy zaglavlja", "hi_user": "Bok {name} ({email})", "hide_all_people": "Sakrij sve ljude", @@ -1220,7 +1233,7 @@ "location_picker_longitude_error": "Unesite valjanu geografsku dužinu", "location_picker_longitude_hint": "Unesite ovdje svoju geografsku dužinu", "lock": "Zaključaj", - "locked_folder": "Zaključana Mapa", + "locked_folder": "Zaključana mapa", "log_out": "Odjavi se", "log_out_all_devices": "Odjava sa svih uređaja", "logged_in_as": "Prijavljeni kao {user}", @@ -1500,12 +1513,8 @@ "privacy": "Privatnost", "profile": "Profil", "profile_drawer_app_logs": "Zapisnici", - "profile_drawer_client_out_of_date_major": "Mobilna aplikacija je zastarjela. Ažurirajte na najnoviju glavnu verziju.", - "profile_drawer_client_out_of_date_minor": "Mobilna aplikacija je zastarjela. Ažurirajte na najnoviju manju verziju.", "profile_drawer_client_server_up_to_date": "Klijent i poslužitelj su ažurirani", "profile_drawer_github": "GitHub", - "profile_drawer_server_out_of_date_major": "Poslužitelj je zastario. Ažurirajte na najnoviju glavnu verziju.", - "profile_drawer_server_out_of_date_minor": "Poslužitelj je zastario. Ažurirajte na najnoviju manju verziju.", "profile_image_of_user": "Profilna slika korisnika {user}", "profile_picture_set": "Profilna slika postavljena.", "public_album": "Javni album", @@ -1900,6 +1909,8 @@ "sync_albums_manual_subtitle": "Sinkroniziraj sve prenesene videozapise i fotografije u odabrane albume za sigurnosnu kopiju", "sync_local": "Sinkroniziraj lokalno", "sync_remote": "Sinkroniziraj udaljeno", + "sync_status": "Status sinkronizacije", + "sync_status_subtitle": "Pregledajte i upravljajte sistemom sinkronizacije", "sync_upload_album_setting_subtitle": "Kreiraj i prenesi svoje fotografije i videozapise u odabrane albume na Immichu", "tag": "Oznaka", "tag_assets": "Označi stavke", diff --git a/i18n/hu.json b/i18n/hu.json index 131df91a79..e67ad73041 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -28,10 +28,12 @@ "add_to_album": "Felvétel albumba", "add_to_album_bottom_sheet_added": "Hozzáadva a(z) \"{album}\" albumhoz", "add_to_album_bottom_sheet_already_exists": "Már benne van a(z) \"{album}\" albumban", + "add_to_album_bottom_sheet_some_local_assets": "Néhány helyi elem nem adható hozzá az albumhoz", "add_to_album_toggle": "{album} kijelölésének váltása", "add_to_albums": "Hozzáadás albumokhoz", "add_to_albums_count": "Hozzáadás albumokhoz ({count})", "add_to_shared_album": "Felvétel megosztott albumba", + "add_upload_to_stack": "Feltöltés hozzáadása csoporthoz", "add_url": "URL hozzáadása", "added_to_archive": "Hozzáadva az archívumhoz", "added_to_favorites": "Hozzáadva a kedvencekhez", @@ -118,7 +120,7 @@ "library_settings_description": "Külső képtár beállításainak kezelése", "library_tasks_description": "Külső könyvtárak szkennelése új és/vagy módosított elemek után", "library_watching_enable_description": "Külső képtár változásainak figyelése", - "library_watching_settings": "Képtár figyelése (KÍSÉRLETI)", + "library_watching_settings": "Könyvtár figyelése [KÍSÉRLETI]", "library_watching_settings_description": "Megváltozott fájlok automatikus észlelése", "logging_enable_description": "Naplózás engedélyezése", "logging_level_description": "Ha be van kapcsolva, milyen részletességű legyen a naplózás.", @@ -148,10 +150,22 @@ "machine_learning_max_detection_distance_description": "Két kép közötti maximális távolság, amely esetében még duplikációnak tekintendők (0.001 és 0.1 közötti érték). Minél magasabb az érték, annál több lesz a megtalált duplikáció, de a hamis találatok esélye is egyre nagyobb.", "machine_learning_max_recognition_distance": "Maximum felismerési távolság", "machine_learning_max_recognition_distance_description": "Két arc közötti maximális távolság, amely alapján ugyanazon személynek tekinthetők, 0 és 2 között. Ennek csökkentése megakadályozhatja, hogy két különböző személyt ugyanannak a személynek jelöljünk, míg a növelése megakadályozhatja, hogy ugyanazt a személyt két különböző személyként jelöljük. Vedd figyelembe, hogy könnyebb két személyt összevonni, mint egy személyt kettéválasztani, ezért lehetőség szerint inkább alacsonyabb küszöbértéket válassz.", - "machine_learning_min_detection_score": "Minimum keresési pontszám", + "machine_learning_min_detection_score": "Minimális észlelési érték", "machine_learning_min_detection_score_description": "Az arcok észleléséhez szükséges minimális megbízhatósági pontszám 0 és 1 között. Minél alacsonyabb az érték, annál több lesz a megtalált arc, de a hamis találatok esélye is egyre nagyobb.", "machine_learning_min_recognized_faces": "Minimum felismert arc", "machine_learning_min_recognized_faces_description": "Egy személy létrehozásához szükséges minimálisan felismert arcok száma. Ennek növelésével a arcfelismerés pontosabbá válik, azonban növeli annak az esélyét, hogy egy arc nem rendelődik hozzá egy személyhez.", + "machine_learning_ocr": "OCR (Optikai karakterfelismerés)", + "machine_learning_ocr_description": "Gépi tanulás használata a képeken megjelenő szövegek felismerésére", + "machine_learning_ocr_enabled": "OCR engedélyezése", + "machine_learning_ocr_enabled_description": "Kikapcsolt állapotban a képeken nem történik szövegfelismerés.", + "machine_learning_ocr_max_resolution": "Maximális felbontás", + "machine_learning_ocr_max_resolution_description": "Az ennél nagyobb felbontású előnézetek átméretezésre kerülnek a képarány megtartásával. A magasabb értékeknél az előnézetek pontosabbak, de ez hosszabb feldolgozási időt és több memóriát igényel.", + "machine_learning_ocr_min_detection_score": "Minimális észlelési érték", + "machine_learning_ocr_min_detection_score_description": "A szövegfelismerés minimális bizalmi szintje 0 és 1 között. Az alacsonyabb érték több szöveget észlelhet, de növeli a téves találatok esélyét.", + "machine_learning_ocr_min_recognition_score": "Minimális felismerési érték", + "machine_learning_ocr_min_score_recognition_description": "A szövegfelismerés minimális bizalmi szintje 0 és 1 között. Az alacsonyabb értékek több szöveget ismerhetnek fel, de növelhetik a téves találatok számát.", + "machine_learning_ocr_model": "Szövegfelismerő modell (OCR)", + "machine_learning_ocr_model_description": "A szervermodellek pontosabbak, mint a mobilmodellek, de hosszabb feldolgozási időt és több memóriát igényelnek.", "machine_learning_settings": "Gépi Tanulási Beállítások", "machine_learning_settings_description": "Gépi tanulási funkciók és beállítások kezelése", "machine_learning_smart_search": "Okos Keresés", @@ -175,7 +189,7 @@ "map_settings_description": "Térkép beállítások kezelése", "map_style_description": "Egy style.json térképtémára mutató URL cím", "memory_cleanup_job": "Memória takarítás", - "memory_generate_job": "Emlék generálása", + "memory_generate_job": "Emlékek generálása", "metadata_extraction_job": "Metaadatok kinyerése", "metadata_extraction_job_description": "Metaadat információk (pl. GPS, arcok és felbontás) kinyerése minden elemből", "metadata_faces_import_setting": "Arc importálás engedélyezése", @@ -209,6 +223,8 @@ "notification_email_ignore_certificate_errors_description": "TLS tanúsítvány érvényességi hibák figyelmen kívül hagyása (nem ajánlott)", "notification_email_password_description": "Az email szerverrel való hitelesítéshez használt jelszó", "notification_email_port_description": "Email szerver portja (pl. 25, 465 vagy 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "SMTPS használata (SMTP TLS-en keresztül)", "notification_email_sent_test_email_button": "Teszt email küldése és mentés", "notification_email_setting_description": "Email értesítés küldés beállításai", "notification_email_test_email": "Teszt email küldése", @@ -241,6 +257,7 @@ "oauth_storage_quota_default_description": "Alapértelmezett tárhely kvóta GiB-ban, amennyiben a felhasználó nem jelezte az igényét.", "oauth_timeout": "Kérés időkorlátja", "oauth_timeout_description": "Kérések időkorlátja milliszekundumban", + "ocr_job_description": "Gépi tanulás használata a képeken lévő szövegek felismerésére", "password_enable_description": "Bejelentkezés emaillel és jelszóval", "password_settings": "Jelszavas Bejelentkezés", "password_settings_description": "Jelszavas bejelentkezés beállítások kezelése", @@ -331,7 +348,7 @@ "transcoding_max_b_frames": "B-képkockák maximum száma", "transcoding_max_b_frames_description": "Nagyobb értékek megnövelik a tömörítés hatékonyságát, de lelassítják a kódolást. Nem minden hardvereszköz támogatja. A 0 érték kikapcsolja a B-képkockákat, míg -1 esetén a szoftver magának választ értéket.", "transcoding_max_bitrate": "Maximum bitráta", - "transcoding_max_bitrate_description": "Maximum bitráta beállítása konzisztensebb fájlméretet eredményez egy kevés minőségi romlás árán. 720p esetén jellemző érték lehet 2600 kbit/s a VP9 vagy HEVC kódoláshoz, 4500 kbit/s a H.264 kódoláshoz. A 0 érték esetén nincs maximum bitráta.", + "transcoding_max_bitrate_description": "Maximum bitráta beállítása konzisztensebb fájlméretet eredményez egy kevés minőségi romlás árán. 720p esetén jellemző érték lehet 2600 kbit/s a VP9 vagy HEVC kódoláshoz, 4500 kbit/s a H.264 kódoláshoz. A 0 érték esetén nincs maximum bitráta. Ha nincs megadva mértékegység, alapértelmezetten „k” (kbit/s) értendő - tehát az 5000, 5000k és az 5M (Mbit/s) azonos beállítások.", "transcoding_max_keyframe_interval": "Maximum kulcskocka intervallum", "transcoding_max_keyframe_interval_description": "Beállítja a kulcskockák közötti legnagyobb lehetséges távolságot. Alacsony érték csökkenti a tömörítési hatékonyságot, de lejátszás közben az előre- és hátratekerés gyorsabb, valamint javíthatja a gyorsan mozgó jelenetek képminőségét. 0 esetén a szoftver magának állítja be az értéket.", "transcoding_optimal_description": "A célfelbontásnál nagyobb vagy a nem elfogadott formátumú videókat", @@ -349,7 +366,7 @@ "transcoding_target_resolution": "Célfelbontás", "transcoding_target_resolution_description": "A magasabb felbontás jobb minőségben őrzi meg a részleteket, de tovább tart létrehozni, nagyobb fájlmérethez vezet és belassíthatja az alkalmazást.", "transcoding_temporal_aq": "Időbeli (Temporal) AQ", - "transcoding_temporal_aq_description": "Csak NVENC esetén. Növeli a nagyon részletes, keveset mozgó videóanyag minőségét. Nem minden régi eszköz támogatja.", + "transcoding_temporal_aq_description": "Csak NVENC esetén. Az Időbeli Adaptív Kvantálás növeli a nagyon részletes, keveset mozgó videóanyag minőségét. Nem minden régi eszköz támogatja.", "transcoding_threads": "Folyamatok száma", "transcoding_threads_description": "Magas értékek esetén gyorsabban kódol, viszont kevesebb erőforrást hagy a szerver többi folyamatának. Nem ajánlott a CPU magjainak számánál nagyobb érték beállítása. A 0 érték maximalizálja a processzor kihasználását.", "transcoding_tone_mapping": "Tónusleképezés (tone-mapping)", @@ -400,11 +417,11 @@ "advanced_settings_prefer_remote_subtitle": "Néhány eszköz fájdalmasan lassan tölti be az eszközön lévő indexképeket. Ez a beállítás inkább a távoli képeket (a szerverről) tölti be helyettük.", "advanced_settings_prefer_remote_title": "Távoli képek előnyben részesítése", "advanced_settings_proxy_headers_subtitle": "Add meg azokat a proxy fejléceket, amiket az app elküldjön minden hálózati kérésnél", - "advanced_settings_proxy_headers_title": "Proxy Fejlécek", + "advanced_settings_proxy_headers_title": "Egyedi Proxy Fejlécek [KÍSÉRLETI]", "advanced_settings_readonly_mode_subtitle": "Bekapcsol egy írásvédett módot ahol csak fotókat nézni lehetséges, egyebek, mint több kép kiválasztása, megosztás, kivetítés és törlés ki vannak kapcsolva. Ki/bekapcsolható a felhasználó ikonjáról a fő képernyőn", - "advanced_settings_readonly_mode_title": "Írásvédett Mód", + "advanced_settings_readonly_mode_title": "Írásvédett mód", "advanced_settings_self_signed_ssl_subtitle": "Nem ellenőrzi a szerver SSL tanúsítványát. Önaláírt tanúsítvány esetén szükséges beállítás.", - "advanced_settings_self_signed_ssl_title": "Önaláírt SSL tanúsítványok engedélyezése", + "advanced_settings_self_signed_ssl_title": "Önaláírt SSL tanúsítványok engedélyezése [KÍSÉRLETI]", "advanced_settings_sync_remote_deletions_subtitle": "Automatikusan törölni vagy visszaállítani egy elemet ezen az eszközön, ha az adott műveletet a weben hajtották végre", "advanced_settings_sync_remote_deletions_title": "Távoli törlések szinkronizálása [KÍSÉRLETI FUNKCIÓ]", "advanced_settings_tile_subtitle": "Haladó felhasználói beállítások", @@ -464,10 +481,14 @@ "api_key_description": "Ez csak most az egyszer jelenik meg. Az ablak bezárása előtt feltétlenül másold.", "api_key_empty": "Az API Kulcs név nem kéne, hogy üres legyen", "api_keys": "API Kulcsok", + "app_architecture_variant": "Variant (Architektúra)", "app_bar_signout_dialog_content": "Biztos, hogy ki szeretnél jelentkezni?", "app_bar_signout_dialog_ok": "Igen", "app_bar_signout_dialog_title": "Kijelentkezés", + "app_download_links": "App letöltési linkek", "app_settings": "Alkalmazás Beállítások", + "app_stores": "App Store-ok", + "app_update_available": "Egy új frissítés érhető el", "appears_in": "Itt szerepel", "apply_count": "Alkalmaz ({count, number})", "archive": "Archívum", @@ -548,8 +569,10 @@ "backup_album_selection_page_select_albums": "Válassz albumokat", "backup_album_selection_page_selection_info": "Összegzés", "backup_album_selection_page_total_assets": "Összes egyedi elem", + "backup_albums_sync": "Backup albumok szinkronizálása", "backup_all": "Összes", "backup_background_service_backup_failed_message": "Az elemek mentése sikertelen. Újrapróbálkozás…", + "backup_background_service_complete_notification": "Az adatok mentése befejeződött", "backup_background_service_connection_failed_message": "A szerverhez csatlakozás sikertelen. Újrapróbálkozás…", "backup_background_service_current_upload_notification": "Feltöltés {filename}", "backup_background_service_default_notification": "Új elemek ellenőrzése…", @@ -597,6 +620,7 @@ "backup_controller_page_turn_on": "Előtérben mentés bekapcsolása", "backup_controller_page_uploading_file_info": "Fájl információk feltöltése", "backup_err_only_album": "Az utolsó albumot nem tudod törölni", + "backup_error_sync_failed": "A szinkronizálás nem sikerült. A biztonsági mentés nem elkészíthető.", "backup_info_card_assets": "elemek", "backup_manual_cancelled": "Megszakítva", "backup_manual_in_progress": "Feltöltés már folyamatban. Próbáld meg később", @@ -658,6 +682,8 @@ "change_password_description": "Most jelentkezel be a rendszerbe első alkalommal, vagy valaki jelszó-változtatást kezdeményezett. Kérjük, add meg az új jelszót.", "change_password_form_confirm_password": "Jelszó Megerősítése", "change_password_form_description": "Szia {name}!\n\nMost jelentkezel be először a rendszerbe vagy más okból szükséges a jelszavad meváltoztatása. Kérjük, add meg új jelszavad.", + "change_password_form_log_out": "Kijelentkezés az összes többi eszközről", + "change_password_form_log_out_description": "Javasolt kijelentkezni az összes többi eszközről", "change_password_form_new_password": "Új Jelszó", "change_password_form_password_mismatch": "A beírt jelszavak nem egyeznek", "change_password_form_reenter_new_password": "Jelszó (Még Egyszer)", @@ -684,8 +710,8 @@ "client_cert_import_success_msg": "Kliens tanúsítvány importálva", "client_cert_invalid_msg": "Érvénytelen tanúsítvány fájl vagy hibás jelszó", "client_cert_remove_msg": "Kliens tanúsítvány eltávolítva", - "client_cert_subtitle": "Csak a PKCS12 (.p12, .pfx) formátum támogatott. Tanúsítvány Importálása/Eltávolítása csak a bejelentkezés előtt lehetséges", - "client_cert_title": "SSL Kliens Tanúsítvány", + "client_cert_subtitle": "Csak a PKCS12 (.p12, .pfx) formátum támogatott. Tanúsítvány importálása/eltávolítása csak a bejelentkezés előtt lehetséges", + "client_cert_title": "SSL kliens tanúsítvány [KÍSÉRLETI]", "clockwise": "Óramutató járásával megegyező irány", "close": "Bezárás", "collapse": "Összecsuk", @@ -697,7 +723,6 @@ "comments_and_likes": "Megjegyzések és reakciók", "comments_are_disabled": "A megjegyzések le vannak tiltva", "common_create_new_album": "Új album létrehozása", - "common_server_error": "Kérjük, ellenőrizd a hálózati kapcsolatot, gondoskodj róla, hogy a szerver elérhető legyen, valamint az alkalmazás és a szerver kompatibilis verziójú legyen.", "completed": "Kész", "confirm": "Jóváhagy", "confirm_admin_password": "Admin Jelszó Újból", @@ -736,6 +761,7 @@ "create": "Létrehoz", "create_album": "Album létrehozása", "create_album_page_untitled": "Névtelen", + "create_api_key": "API kulcs létrehozása", "create_library": "Képtár Létrehozása", "create_link": "Link létrehozása", "create_link_to_share": "Megosztási link létrehozása", @@ -765,6 +791,7 @@ "daily_title_text_date_year": "yyyy MMM dd (E)", "dark": "Sötét", "dark_theme": "Sötét téma kapcsolása", + "date": "Dátum", "date_after": "Dátumtól", "date_and_time": "Dátum és Idő", "date_before": "Dátumig", @@ -879,7 +906,6 @@ "edit_tag": "Címke módosítása", "edit_title": "Cím Módosítása", "edit_user": "Felhasználó módosítása", - "edited": "Módosítva", "editor": "Szerkesztő", "editor_close_without_save_prompt": "A változtatások nem lesznek elmentve", "editor_close_without_save_title": "Szerkesztő bezárása?", @@ -1035,6 +1061,7 @@ "exif_bottom_sheet_description_error": "Hiba a leírás frissítésekor", "exif_bottom_sheet_details": "RÉSZLETEK", "exif_bottom_sheet_location": "HELY", + "exif_bottom_sheet_no_description": "Nincs leírás", "exif_bottom_sheet_people": "EMBEREK", "exif_bottom_sheet_person_add_person": "Elnevez", "exit_slideshow": "Kilépés a Diavetítésből", @@ -1073,6 +1100,7 @@ "features_setting_description": "Az alkalmazás jellemzőinek kezelése", "file_name": "Fájlnév", "file_name_or_extension": "Fájlnév vagy kiterjesztés", + "file_size": "Fájlméret", "filename": "Fájlnév", "filetype": "Fájltípus", "filter": "Szűrő", @@ -1112,11 +1140,10 @@ "hash_asset": "Elem hash-elése", "hashed_assets": "Hash-elt elemek", "hashing": "Hash-elés folyamatban", - "header_settings_add_header_tip": "Fejléc Hozzáadása", + "header_settings_add_header_tip": "Fejléc hozzáadása", "header_settings_field_validator_msg": "Az érték nem lehet üres", "header_settings_header_name_input": "Fejléc neve", "header_settings_header_value_input": "Fejléc értéke", - "headers_settings_tile_subtitle": "Add meg azokat a proxy fejléceket, amiket az app elküldjön minden hálózati kérésnél", "headers_settings_tile_title": "Egyéni proxy fejlécek", "hi_user": "Szia {name} ({email})", "hide_all_people": "Minden személy elrejtése", @@ -1237,6 +1264,7 @@ "local_media_summary": "Helyi média összegzés", "local_network": "Helyi hálózat", "local_network_sheet_info": "Az alkalmazés ezen az URL címen fogja elérni a szervert, ha a megadott WiFi hálózathoz van csatlankozva", + "location": "Lokáció", "location_permission": "Helymeghatározási engedély", "location_permission_content": "A Hálózatok automatikus váltásához az Immich-nek szüksége van a pontos helymeghatározásra, hogy az alkalmazás le tudja kérni a Wi-Fi hálózat nevét", "location_picker_choose_on_map": "Válassz a térképen", @@ -1341,6 +1369,8 @@ "minute": "Perc", "minutes": "Percek", "missing": "Hiányzók", + "mobile_app": "Mobilapplikáció", + "mobile_app_download_onboarding_note": "Töltse le a kiegészítő mobilalkalmazást az alábbi opciók segítségével", "model": "Modell", "month": "Hónap", "monthly_title_text_date_format": "y MMMM", @@ -1359,6 +1389,8 @@ "my_albums": "Saját albumaim", "name": "Név", "name_or_nickname": "Név vagy becenév", + "navigate": "Navigáció", + "navigate_to_time": "Navigálás adott időponthoz", "network_requirement_photos_upload": "Mobil adatforgalmat használjon a fényképek biztonsági mentéséhez", "network_requirement_videos_upload": "Mobil adatforgalmat használjon a videók biztonsági mentéséhez", "network_requirements": "Hálózati követelmények", @@ -1368,6 +1400,7 @@ "never": "Soha", "new_album": "Új Album", "new_api_key": "Új API Kulcs", + "new_date_range": "Új dátumtartomány", "new_password": "Új jelszó", "new_person": "Új személy", "new_pin_code": "Új PIN kód", @@ -1386,20 +1419,25 @@ "no_assets_message": "KATTINTS AZ ELSŐ FÉNYKÉP FELTÖLTÉSÉHEZ", "no_assets_to_show": "Nincs megjeleníthető elem", "no_cast_devices_found": "Nem található eszköz vetítéshez", + "no_checksum_local": "Nincs elérhető ellenőrzőösszeg - a helyi eszközök nem kérhetők le", + "no_checksum_remote": "Nincs elérhető ellenőrzőösszeg - a távoli eszköz nem kérhető le", "no_duplicates_found": "Nem találhatók duplikátumok.", "no_exif_info_available": "Nincs elérhető Exif információ", "no_explore_results_message": "Tölts fel több képet, hogy böngészhesd a gyűjteményed.", "no_favorites_message": "Add hozzá a kedvencekhez, hogy gyorsan megtaláld a legjobb képeidet és videóidat", "no_libraries_message": "Hozz létre külső képtárat a fényképeid és videóid megtekintéséhez", + "no_local_assets_found": "Nem találhatók helyi eszközök ezzel az ellenőrzőösszeggel", "no_locked_photos_message": "A zárolt mappában elhelyezett fotók és videók rejtettek, és nem jelennek meg a könyvtárad böngészése vagy keresése közben sem.", "no_name": "Nincs Név", "no_notifications": "Nincsenek értesítések", "no_people_found": "Nem található személy", "no_places": "Nincsenek helyek", + "no_remote_assets_found": "Nem találhatók távoli eszközök ezzel az ellenőrzőösszeggel", "no_results": "Nincs találat", "no_results_description": "Próbálkozz szinonimákkal vagy általánosabb kulcsszavakkal", "no_shared_albums_message": "Hozz létre egy új albumot, hogy megoszthasd fényképeid és videóid másokkal", "no_uploads_in_progress": "Nincs folyamatban lévő feltöltés", + "not_available": "N/A", "not_in_any_album": "Nincs albumban", "not_selected": "Nincs kiválasztva", "note_apply_storage_label_to_previously_uploaded assets": "Megjegyzés: a korábban feltöltött elemek Tárhely Címkézéséhez futtasd a(z)", @@ -1413,6 +1451,9 @@ "notifications": "Értesítések", "notifications_setting_description": "Értesítések kezelése", "oauth": "OAuth", + "obtainium_configurator": "Obtainium Konfigurátor", + "obtainium_configurator_instructions": "Az Obtainium segítségével közvetlenül az Immich GitHub-os kiadásából telepítheted és frissítheted az Android-alkalmazást. Hozz létre egy API-kulcsot és válassz egy változatot az Obtainium konfigurációs hivatkozás elkészítéséhez", + "ocr": "OCR", "official_immich_resources": "Hivatalos Immich Források", "offline": "Nem elérhető (offline)", "offset": "Eltolás", @@ -1434,6 +1475,8 @@ "open_the_search_filters": "Keresési szűrők megnyitása", "options": "Beállítások", "or": "vagy", + "organize_into_albums": "Albumokba rendezés", + "organize_into_albums_description": "Meglévő fotók albumokba helyezése, a jelenlegi szinkronizációs beállítások alapján", "organize_your_library": "Rendszerezd a képtáradat", "original": "eredeti", "other": "Egyéb", @@ -1515,10 +1558,14 @@ "play_memories": "Emlékek lejátszása", "play_motion_photo": "Mozgókép lejátszása", "play_or_pause_video": "Videó elindítása vagy megállítása", + "play_original_video": "Eredeti videó lejátszása", + "play_original_video_setting_description": "A rendszer az eredeti videók lejátszását részesíti előnyben a transzkódolt verziókkal szemben. Ha az eredeti fájl nem kompatibilis, előfordulhat, hogy nem játszható le megfelelően.", + "play_transcoded_video": "Transzkódolt videó lejátszása", "please_auth_to_access": "Kérlek jelentkezz be a hozzáféréshez", "port": "Port", "preferences_settings_subtitle": "Alkalmazásbeállítások kezelése", "preferences_settings_title": "Beállítások", + "preparing": "Előkészítés", "preset": "Sablon", "preview": "Előnézet", "previous": "Előző", @@ -1531,12 +1578,9 @@ "privacy": "Magánszféra", "profile": "Profil", "profile_drawer_app_logs": "Naplók", - "profile_drawer_client_out_of_date_major": "A mobilalkalmazás elavult. Kérjük, frissítsd a legfrisebb főverzióra.", - "profile_drawer_client_out_of_date_minor": "A mobilalkalmazás elavult. Kérjük, frissítsd a legfrisebb alverzióra.", "profile_drawer_client_server_up_to_date": "A Kliens és a Szerver is naprakész", "profile_drawer_github": "GitHub", - "profile_drawer_server_out_of_date_major": "A szerver elavult. Kérjük, frissítsd a legfrisebb főverzióra.", - "profile_drawer_server_out_of_date_minor": "A szerver elavult. Kérjük, frissítsd a legfrisebb alverzióra.", + "profile_drawer_readonly_mode": "Csak olvasható mód engedélyezve. A kilépéshez hosszan nyomja meg a felhasználói avatar ikont.", "profile_image_of_user": "{user} profilképe", "profile_picture_set": "Profilkép beállítva.", "public_album": "Nyilvános album", @@ -1573,6 +1617,7 @@ "purchase_server_description_2": "Támogató státusz", "purchase_server_title": "Szerver", "purchase_settings_server_activated": "A szerver termékkulcsot az admin kezeli", + "query_asset_id": "Lekérdezési eszköz azonosítója", "queue_status": "Feldolgozva {count}/{total}", "rating": "Értékelés csillagokkal", "rating_clear": "Értékelés törlése", @@ -1580,6 +1625,9 @@ "rating_description": "Exif értékelés megjelenítése az infópanelen", "reaction_options": "Reakció lehetőségek", "read_changelog": "Változásnapló Elolvasása", + "readonly_mode_disabled": "Csak olvasható mód kikapcsolva", + "readonly_mode_enabled": "Csak olvasható mód bekapcsolva", + "ready_for_upload": "Készen áll a feltöltésre", "reassign": "Hozzárendel", "reassigned_assets_to_existing_person": "{count, plural, other {# elem}} hozzárendelve{name, select, null { egy létező személyhez} other {: {name}}}", "reassigned_assets_to_new_person": "{count, plural, other {# elem}} hozzárendelve egy új személyhez", @@ -1604,6 +1652,7 @@ "regenerating_thumbnails": "Bélyegképek újragenerálása folyamatban", "remote": "Távoli", "remote_assets": "Távoli Elemek", + "remote_media_summary": "Távoli médiaösszefoglaló", "remove": "Eltávolítás", "remove_assets_album_confirmation": "Biztosan el szeretnél távolítani {count, plural, one {# elemet} other {# elemet}} az albumból?", "remove_assets_shared_link_confirmation": "Biztosan el szeretnél távolítani {count, plural, one {# elemet} other {# elemet}} ebből a megosztott linkből?", @@ -1648,6 +1697,7 @@ "reset_sqlite_confirmation": "Biztosan vissza szeretnéd állítani az SQLite adatbázist? Az adatok újraszinkronizálásához ki kell jelentkezed, majd újra be kell lépned", "reset_sqlite_success": "SQLite adatbázis sikeresen visszaállítva", "reset_to_default": "Visszaállítás alapállapotba", + "resolution": "Felbontás", "resolve_duplicates": "Duplikátumok feloldása", "resolved_all_duplicates": "Minden duplikátum feloldása", "restore": "Visszaállít", @@ -1656,6 +1706,7 @@ "restore_user": "Felhasználó visszaállítása", "restored_asset": "Visszaállított elem", "resume": "Folytatás", + "resume_paused_jobs": "Folytatás {count, plural, one {# paused job} other {# paused jobs}}", "retry_upload": "Feltöltés újrapróbálása", "review_duplicates": "Duplikátumok áttekintése", "review_large_files": "Nagy fájlok áttekintése", @@ -1681,6 +1732,9 @@ "search_by_description_example": "Túrázós nap Szapában", "search_by_filename": "Keresés fájlnév vagy kiterjesztés alapján", "search_by_filename_example": "például IMG_1234.JPG vagy PNG", + "search_by_ocr": "Keresés szövegfelismeréssel (OCR)", + "search_by_ocr_example": "Latte", + "search_camera_lens_model": "Keresés objektívmodell alapján...", "search_camera_make": "Kameragyártó keresése...", "search_camera_model": "Kameramodell keresése...", "search_city": "Város keresése...", @@ -1697,6 +1751,7 @@ "search_filter_location_title": "Válassz helyet", "search_filter_media_type": "Média Típus", "search_filter_media_type_title": "Válassz média típust", + "search_filter_ocr": "Keresés szövegfelismeréssel (OCR)", "search_filter_people_title": "Válassz embereket", "search_for": "Keresés", "search_for_existing_person": "Már meglévő személy keresése", @@ -1749,6 +1804,7 @@ "select_user_for_sharing_page_err_album": "Az album létrehozása sikertelen", "selected": "Kiválasztott", "selected_count": "{count, plural, other {# kiválasztva}}", + "selected_gps_coordinates": "Kiválasztott GPS Kordináták", "send_message": "Üzenet küldése", "send_welcome_email": "Üdvözlő email küldése", "server_endpoint": "Szerver Végpont", @@ -1758,6 +1814,7 @@ "server_online": "Szerver Elérhető", "server_privacy": "Szerver biztonság", "server_stats": "Szerver Statisztikák", + "server_update_available": "Szerverfrissítés érhető el", "server_version": "Szerver Verzió", "set": "Beállít", "set_as_album_cover": "Beállítás albumborítóként", @@ -1786,9 +1843,11 @@ "setting_notifications_subtitle": "Értesítési beállítások módosítása", "setting_notifications_total_progress_subtitle": "Átfogó feltöltési folyamat (kész/összes elem)", "setting_notifications_total_progress_title": "Mutassa a háttérben történő mentés teljes folyamatát", + "setting_video_viewer_auto_play_subtitle": "A videók automatikus lejátszása megnyitáskor", + "setting_video_viewer_auto_play_title": "Videók automatikus lejátszása", "setting_video_viewer_looping_title": "Ismétlés", "setting_video_viewer_original_video_subtitle": "A szerverről történő videólejátszás során az eredeti videó lejátszása még akkor is, ha van optimalizált, átkódolt verzió. Akadozó lejátszást eredményezhet. A helyi eszközön eleve elérhető videókat mindenképpen eredeti minőségben játszuk le.", - "setting_video_viewer_original_video_title": "Eredeti videó lejátszása", + "setting_video_viewer_original_video_title": "Mindig az eredeti videó lejátszása", "settings": "Beállítások", "settings_require_restart": "Ennek a beállításnak az érvénybe lépéséhez indítsd újra az Immich-et", "settings_saved": "Beállítások elmentve", @@ -1877,6 +1936,7 @@ "show_slideshow_transition": "Vetítés áttűnési effekt mutatása", "show_supporter_badge": "Támogató jelvény", "show_supporter_badge_description": "Támogató jelvény mutatása", + "show_text_search_menu": "Mutasd a szövegkeresési menüt", "shuffle": "Véletlenszerű", "sidebar": "Oldalsáv", "sidebar_display_description": "Nézet link megjelenítése az oldalsávban", @@ -1907,6 +1967,7 @@ "stacktrace": "Hiba leírása", "start": "Elindít", "start_date": "Kezdő dátum", + "start_date_before_end_date": "A kezdeti dátumnak a befejezési dátum előtt kell lennie", "state": "Megye/Állam", "status": "Állapot", "stop_casting": "Vetítés megszüntetése", @@ -1963,6 +2024,7 @@ "theme_setting_three_stage_loading_title": "Háromlépcsős betöltés engedélyezése", "they_will_be_merged_together": "Egyesítve lesznek", "third_party_resources": "Harmadik Féltől Származó Források", + "time": "Idő", "time_based_memories": "Emlékek idő alapján", "timeline": "Idővonal", "timezone": "Időzóna", @@ -1970,7 +2032,9 @@ "to_change_password": "Jelszó megváltoztatása", "to_favorite": "Kedvenc", "to_login": "Bejelentkezés", + "to_multi_select": "több elem kiválasztásához", "to_parent": "Egy szinttel feljebb", + "to_select": "a kiválasztáshoz", "to_trash": "Lomtárba helyezés", "toggle_settings": "Beállítások átállítása", "total": "Összesen", @@ -1990,8 +2054,10 @@ "trash_page_select_assets_btn": "Elemek kiválasztása", "trash_page_title": "Lomtár ({count})", "trashed_items_will_be_permanently_deleted_after": "A lomtárban lévő elemek véglegesen törlésre kerülnek {days, plural, other {# nap}} múlva.", + "troubleshoot": "Hibaelhárítás", "type": "Típus", "unable_to_change_pin_code": "Sikertelen PIN kód változtatás", + "unable_to_check_version": "Az alkalmazás vagy a szerver verziója nem ellenőrizhető", "unable_to_setup_pin_code": "Sikertelen PIN kód beállítás", "unarchive": "Archívumból kivesz", "unarchive_action_prompt": "{count} eltávolítva az Archívumból", @@ -2106,5 +2172,6 @@ "yes": "Igen", "you_dont_have_any_shared_links": "Nincsenek megosztott linkjeid", "your_wifi_name": "A Wi-Fi hálózatod neve", - "zoom_image": "Kép Nagyítása" + "zoom_image": "Kép Nagyítása", + "zoom_to_bounds": "Nagyítás a határokhoz" } diff --git a/i18n/id.json b/i18n/id.json index 40a4ddbef4..906a0f8ce0 100644 --- a/i18n/id.json +++ b/i18n/id.json @@ -28,10 +28,12 @@ "add_to_album": "Tambahkan ke album", "add_to_album_bottom_sheet_added": "Ditambahkan ke {album}", "add_to_album_bottom_sheet_already_exists": "Sudah ada di {album}", + "add_to_album_bottom_sheet_some_local_assets": "Beberapa aset lokal tidak dapat ditambahkan ke album", "add_to_album_toggle": "Masukkan ke {album} / Batalkan dari {album}", "add_to_albums": "Tambahkan ke album", "add_to_albums_count": "Tambahkan ke album ({count})", "add_to_shared_album": "Tambahkan ke album terbagi", + "add_upload_to_stack": "Tambahkan unggahan ke tumpukan", "add_url": "Tambahkan URL", "added_to_archive": "Ditambahkan ke arsip", "added_to_favorites": "Ditambahkan ke favorit", @@ -118,11 +120,18 @@ "library_settings_description": "Kelola pengaturan pustaka eksternal", "library_tasks_description": "Pindai pustaka eksternal untuk aset baru dan/atau berubah", "library_watching_enable_description": "Pantau perubahan berkas dalam pustaka eksternal", - "library_watching_settings": "Pemantauan pustaka (UJI COBA)", + "library_watching_settings": "Pemantauan pustaka [UJI COBA]", "library_watching_settings_description": "Pantau berkas yang telah diubah secara otomatis", "logging_enable_description": "Aktifkan log", "logging_level_description": "Ketika diaktifkan, tingkat log apa yang digunakan.", "logging_settings": "Penulisan log", + "machine_learning_availability_checks": "Pemeriksaan ketersediaan", + "machine_learning_availability_checks_description": "Secara otomatis mendeteksi dan memprioritaskan server machine learning yang tersedia", + "machine_learning_availability_checks_enabled": "Aktifkan pemeriksaan ketersediaan", + "machine_learning_availability_checks_interval": "Interval pemeriksaan", + "machine_learning_availability_checks_interval_description": "Interval dalam milidetik antar pemeriksaan ketersediaan", + "machine_learning_availability_checks_timeout": "Batas waktu permintaan", + "machine_learning_availability_checks_timeout_description": "Batas waktu dalam milidetik untuk pemeriksaan ketersediaan", "machine_learning_clip_model": "Model CLIP", "machine_learning_clip_model_description": "Nama model CLIP yang didaftarkan di sini. Anda harus menjalankan ulang tugas 'Pencarian Otomatis' untuk semua gambar ketika mengganti model.", "machine_learning_duplicate_detection": "Deteksi Duplikat", @@ -145,6 +154,11 @@ "machine_learning_min_detection_score_description": "Nilai keyakinan minimum untuk sebuah wajah untuk dideteksi dari 0 sampai 1. Nilai yang lebih rendah akan mendeteksi lebih banyak wajah tetapi dapat mengakibatkan positif palsu.", "machine_learning_min_recognized_faces": "Wajah terkenal minimum", "machine_learning_min_recognized_faces_description": "Jumlah minimum wajah yang dikenal untuk seseorang untuk dibuat. Meningkatkan ini membuat Pengenalan Wajah lebih tepat dengan kemungkinan bahwa sebuah wajah tidak dikaitkan dengan seseorang.", + "machine_learning_ocr": "OCR", + "machine_learning_ocr_description": "Gunakan pembelajaran mesin untuk mengenali teks di dalam gambar", + "machine_learning_ocr_enabled": "Aktfikan OCR", + "machine_learning_ocr_enabled_description": "Jika dinonaktifkan, gambar-gambar tidak akan mengalami pengenalan teks.", + "machine_learning_ocr_max_resolution": "Resolusi maksimum", "machine_learning_settings": "Pengaturan Pembelajaran Mesin", "machine_learning_settings_description": "Keola fitur dan pengaturan pembelajaran mesin", "machine_learning_smart_search": "Pencarian Pintar", @@ -423,6 +437,7 @@ "album_remove_user_confirmation": "Apakah Anda yakin ingin mengeluarkan {user}?", "album_search_not_found": "Tidak ada album yang ditemukan sesuai pencarian Anda", "album_share_no_users": "Sepertinya Anda telah membagikan album ini dengan semua pengguna atau tidak memiliki pengguna siapa pun untuk dibagikan.", + "album_summary": "Ringkasan album", "album_updated": "Album diperbarui", "album_updated_setting_description": "Terima notifikasi surel ketika album terbagi memiliki aset baru", "album_user_left": "Keluar dari {album}", @@ -494,6 +509,8 @@ "asset_restored_successfully": "Aset telah berhasil dipulihkan", "asset_skipped": "Dilewati", "asset_skipped_in_trash": "Dalam sampah", + "asset_trashed": "Aset dibuang", + "asset_troubleshoot": "Troubleshoot Aset", "asset_uploaded": "Sudah diunggah", "asset_uploading": "Mengunggah…", "asset_viewer_settings_subtitle": "Kelola pengaturan penampil galeri Anda", @@ -527,8 +544,10 @@ "autoplay_slideshow": "Putar otomatis tayangan slide", "back": "Kembali", "back_close_deselect": "Kembali, tutup, atau batalkan pemilihan", + "background_backup_running_error": "Cadangan latar belakang sedang berjalan, tidak dapat memulai cadangan manual", "background_location_permission": "Izin lokasi latar belakang", "background_location_permission_content": "Untuk beralih jaringan saat berjalan di latar belakang, Immich harus selalu memiliki akses lokasi akurat agar aplikasi dapat membaca nama jaringan Wi-Fi", + "background_options": "Opsi Latar Belakang", "backup": "Cadangkan", "backup_album_selection_page_albums_device": "Album di perangkat ({count})", "backup_album_selection_page_albums_tap": "Sentuh untuk memilih, sentuh 2x untuk mengecualikan", @@ -536,6 +555,7 @@ "backup_album_selection_page_select_albums": "Pilih album", "backup_album_selection_page_selection_info": "Info Pilihan", "backup_album_selection_page_total_assets": "Total aset unik", + "backup_albums_sync": "Sinkronisasi cadangan album", "backup_all": "Semua", "backup_background_service_backup_failed_message": "Gagal mencadangkan aset. Mencoba lagi…", "backup_background_service_connection_failed_message": "Koneksi ke server gagal. Mencoba ulang…", @@ -585,6 +605,7 @@ "backup_controller_page_turn_on": "Aktifkan pencadangan latar depan", "backup_controller_page_uploading_file_info": "Mengunggah info file", "backup_err_only_album": "Tidak dapat menghapus album", + "backup_error_sync_failed": "Sinkronisasi gagal. Tidak dapat memproses cadangan.", "backup_info_card_assets": "aset", "backup_manual_cancelled": "Dibatalkan", "backup_manual_in_progress": "Dalam proses unggah. Coba lagi nanti", @@ -652,6 +673,8 @@ "change_pin_code": "Ubah kode PIN", "change_your_password": "Ubah kata sandi Anda", "changed_visibility_successfully": "Keterlihatan berhasil diubah", + "charging": "Mengisi daya", + "charging_requirement_mobile_backup": "Cadangan latar belakang memerlukan perangkat dalam keadaan mengisi daya", "check_corrupt_asset_backup": "Periksa cadangan aset yang rusak", "check_corrupt_asset_backup_button": "Lakukan pemeriksaan", "check_corrupt_asset_backup_description": "Jalankan pemeriksaan ini hanya melalui Wi-Fi dan setelah semua aset dicadangkan. Prosedur ini mungkin memerlukan waktu beberapa menit.", @@ -683,7 +706,6 @@ "comments_and_likes": "Komentar & suka", "comments_are_disabled": "Komentar dinonaktifkan", "common_create_new_album": "Buat album baru", - "common_server_error": "Koneksi gagal, pastikan server dapat diakses dan memiliki versi yang kompatibel.", "completed": "Selesai", "confirm": "Konfirmasi", "confirm_admin_password": "Konfirmasi Kata Sandi Admin", @@ -738,6 +760,7 @@ "create_user": "Buat pengguna", "created": "Dibuat", "created_at": "Dibuat", + "creating_linked_albums": "Membuat album tertaut...", "crop": "Pangkas", "curated_object_page_title": "Benda", "current_device": "Perangkat saat ini", @@ -864,7 +887,6 @@ "edit_tag": "Ubah tag", "edit_title": "Sunting Judul", "edit_user": "Sunting pengguna", - "edited": "Disunting", "editor": "Penyunting", "editor_close_without_save_prompt": "Perubahan tidak akan di simpan", "editor_close_without_save_title": "Tutup editor?", @@ -887,7 +909,9 @@ "error": "Eror", "error_change_sort_album": "Gagal mengubah urutan album", "error_delete_face": "Terjadi kesalahan menghapus wajah dari aset", + "error_getting_places": "Kesalahan saat mengambil lokasi", "error_loading_image": "Terjadi eror memuat gambar", + "error_loading_partners": "Kesalahan saat memuat partner: {error}", "error_saving_image": "Kesalahan: {error}", "error_tag_face_bounding_box": "Galat saat memberi tag wajah – tidak dapat memperoleh koordinat kotak pembatas", "error_title": "Eror - Ada yang salah", @@ -1052,6 +1076,7 @@ "favorites_page_no_favorites": "Tidak ada aset favorit", "feature_photo_updated": "Foto terfitur diperbarui", "features": "Fitur", + "features_in_development": "Fitur dalam Pengembangan", "features_setting_description": "Kelola fitur aplikasi", "file_name": "Nama berkas", "file_name_or_extension": "Nama berkas atau ekstensi", @@ -1094,11 +1119,10 @@ "hash_asset": "Aset Hash", "hashed_assets": "Aset yang di-hash", "hashing": "Proses Hash", - "header_settings_add_header_tip": "Tambahkan Header", + "header_settings_add_header_tip": "Tambahkan header", "header_settings_field_validator_msg": "Nilai tidak boleh kosong", - "header_settings_header_name_input": "Nama Header", - "header_settings_header_value_input": "Nilai Header", - "headers_settings_tile_subtitle": "Menentukan header proksi yang akan dikirimkan oleh aplikasi pada setiap permintaan jaringan", + "header_settings_header_name_input": "Nama header", + "header_settings_header_value_input": "Nilai header", "headers_settings_tile_title": "Header proksi kustom", "hi_user": "Hai {name} ({email})", "hide_all_people": "Sembunyikan semua orang", @@ -1216,6 +1240,7 @@ "local": "Lokal", "local_asset_cast_failed": "Tidak dapat melakukan cast aset yang belum diunggah ke server", "local_assets": "Aset Lokal", + "local_media_summary": "Ringkasan Media Lokal", "local_network": "Jaringan Lokal", "local_network_sheet_info": "Aplikasi akan terhubung ke server melalui URL ini saat menggunakan jaringan Wi-Fi yang ditentukan", "location_permission": "Izin lokasi", @@ -1227,6 +1252,7 @@ "location_picker_longitude_hint": "Masukkan bujur di sini", "lock": "Kunci", "locked_folder": "Folder Terkunci", + "log_detail_title": "Detail Log", "log_out": "Log keluar", "log_out_all_devices": "Keluar dari Semua Perangkat", "logged_in_as": "Masuk sebagai {user}", @@ -1257,6 +1283,7 @@ "login_password_changed_success": "Sandi berhasil diperbarui", "logout_all_device_confirmation": "Apakah Anda yakin ingin keluar dari semua perangkat?", "logout_this_device_confirmation": "Apakah Anda yakin ingin mengeluarkan perangkat ini?", + "logs": "Log", "longitude": "Bujur", "look": "Tampilan", "loop_videos": "Ulangi video", @@ -1299,6 +1326,7 @@ "mark_as_read": "Tandai sebagai telah dibaca", "marked_all_as_read": "Semua telah ditandai sebagai telah dibaca", "matches": "Cocokan", + "matching_assets": "Aset yang Cocok", "media_type": "Jenis media", "memories": "Kenangan", "memories_all_caught_up": "Semua telah dilihat", @@ -1339,6 +1367,7 @@ "name_or_nickname": "Nama atau nama panggilan", "network_requirement_photos_upload": "Gunakan data seluler untuk cadangkan foto", "network_requirement_videos_upload": "Gunakan data seluler untuk cadangkan video", + "network_requirements": "Persyaratan Jaringan", "network_requirements_updated": "Persyaratan jaringan telah berubah, antrean pencadangan diatur ulang", "networking_settings": "Jaringan", "networking_subtitle": "Kelola pengaturan Endpoint server", @@ -1349,6 +1378,7 @@ "new_person": "Orang baru", "new_pin_code": "Kode PIN baru", "new_pin_code_subtitle": "Ini adalah akses pertama Anda ke folder terkunci. Buat kode PIN untuk mengamankan akses ke halaman ini", + "new_timeline": "Linimasa Baru", "new_user_created": "Pengguna baru dibuat", "new_version_available": "VERSI BARU TERSEDIA", "newest_first": "Terkini dahulu", @@ -1362,20 +1392,25 @@ "no_assets_message": "KLIK UNTUK MENGUNGGAH FOTO PERTAMA ANDA", "no_assets_to_show": "Tidak ada aset", "no_cast_devices_found": "Tidak ada perangkat cast yang ditemukan", + "no_checksum_local": "Tidak ada checksum yang tersedia - tidak dapat mengambil aset lokal", + "no_checksum_remote": "Tidak ada checksum yang tersedia - tidak dapat mengambil aset jarak jauh", "no_duplicates_found": "Tidak ada duplikat yang ditemukan.", "no_exif_info_available": "Tidak ada info EXIF yang tersedia", "no_explore_results_message": "Unggah lebih banyak foto untuk menjelajahi koleksi Anda.", "no_favorites_message": "Tambahkan favorit untuk mencari foto dan video terbaik Anda dengan cepat", "no_libraries_message": "Buat pustaka eksternal untuk menampilkan foto dan video Anda", + "no_local_assets_found": "Tidak ada aset lokal yang ditemukan dengan checksum ini", "no_locked_photos_message": "Foto dan video di folder terkunci disembunyikan dan tidak akan muncul saat Anda menelusuri atau mencari di pustaka.", "no_name": "Tidak Ada Nama", "no_notifications": "Tidak ada notifikasi", "no_people_found": "Orang tidak ditemukan", "no_places": "Tidak ada tempat", + "no_remote_assets_found": "Tidak ada aset jarak jauh yang ditemukan dengan checksum ini", "no_results": "Tidak ada hasil", "no_results_description": "Coba sinonim atau kata kunci yang lebih umum", "no_shared_albums_message": "Buat sebuah album untuk membagikan foto dan video dengan orang-orang dalam jaringan Anda", "no_uploads_in_progress": "Tidak ada unggahan yang sedang berlangsung", + "not_available": "T/T", "not_in_any_album": "Tidak ada dalam album apa pun", "not_selected": "Belum dipilih", "note_apply_storage_label_to_previously_uploaded assets": "Catatan: Untuk menerapkan Label Penyimpanan pada aset yang sebelumnya telah diunggah, jalankan", @@ -1497,6 +1532,7 @@ "port": "Porta", "preferences_settings_subtitle": "Kelola preferensi aplikasi", "preferences_settings_title": "Preferensi", + "preparing": "Mempersiapkan", "preset": "Prasetel", "preview": "Pratinjau", "previous": "Sebelumnya", @@ -1509,13 +1545,9 @@ "privacy": "Privasi", "profile": "Profil", "profile_drawer_app_logs": "Log", - "profile_drawer_client_out_of_date_major": "Versi app seluler ini sudah kedaluwarsa. Silakan perbarui ke versi major terbaru.", - "profile_drawer_client_out_of_date_minor": "Versi app seluler ini sudah kedaluwarsa. Silakan perbarui ke versi minor terbaru.", "profile_drawer_client_server_up_to_date": "Klien dan server menjalankan versi terbaru", "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Mode baca-saja aktif. Ketuk dua kali ikon avatar pengguna untuk keluar.", - "profile_drawer_server_out_of_date_major": "Versi server ini telah kedaluwarsa. Silakan perbarui ke versi major terbaru.", - "profile_drawer_server_out_of_date_minor": "Versi server ini telah kedaluwarsa. Silakan perbarui ke versi minor terbaru.", + "profile_drawer_readonly_mode": "Mode baca-saja aktif. Tekan lama ikon avatar pengguna untuk keluar.", "profile_image_of_user": "Foto profil dari {user}", "profile_picture_set": "Foto profil ditetapkan.", "public_album": "Album publik", @@ -1562,6 +1594,7 @@ "read_changelog": "Baca Log Perubahan", "readonly_mode_disabled": "Mode baca-saja dimatikan", "readonly_mode_enabled": "Mode baca-saja diaktifkan", + "ready_for_upload": "Siap untuk mengunggah", "reassign": "Tetapkan ulang", "reassigned_assets_to_existing_person": "Menetapkan ulang {count, plural, one {# aset} other {# aset}} kepada {name, select, null {orang yang sudah ada} other {{name}}}", "reassigned_assets_to_new_person": "Menetapkan ulang {count, plural, one {# aset} other {# aset}} kepada orang baru", @@ -1586,6 +1619,7 @@ "regenerating_thumbnails": "Membuat ulang gambar kecil", "remote": "Jarak Jauh", "remote_assets": "Aset Jarak Jauh", + "remote_media_summary": "Ringkasan Media Jarak Jauh", "remove": "Hapus", "remove_assets_album_confirmation": "Apakah Anda yakin ingin menghapus {count, plural, one {# aset} other {# aset}} dari album?", "remove_assets_shared_link_confirmation": "Apakah Anda yakin ingin menghapus {count, plural, one {# aset} other {# aset}} dari tautan terbagi ini?", @@ -1638,6 +1672,7 @@ "restore_user": "Pulihkan pengguna", "restored_asset": "Aset dipulihkan", "resume": "Lanjutkan", + "resume_paused_jobs": "Lanjutkan {count, plural, one {# pekerjaan yang dijeda} other {# pekerjaan yang dijeda}}", "retry_upload": "Ulangi pengunggahan", "review_duplicates": "Pratinjau duplikat", "review_large_files": "Meninjau berkas berukuran besar", @@ -1860,6 +1895,7 @@ "show_slideshow_transition": "Tampilkan transisi salindia", "show_supporter_badge": "Lencana suporter", "show_supporter_badge_description": "Tampilkan lencana suporter", + "show_text_search_menu": "Tampilkan menu pencarian teks", "shuffle": "Acak", "sidebar": "Bilah sisi", "sidebar_display_description": "Menampilkan tautan ke tampilan di bilah sisi", @@ -1890,6 +1926,7 @@ "stacktrace": "Jejak tumpukan", "start": "Mulai", "start_date": "Tanggal mulai", + "start_date_before_end_date": "Tanggal mulai harus sebelum tanggal akhir", "state": "Keadaan", "status": "Status", "stop_casting": "Hentikan cast", @@ -1914,6 +1951,8 @@ "sync_albums_manual_subtitle": "Melakukan sinkronisasi semua video dan foto yang telah diunggah ke album cadangan yang dipilih", "sync_local": "Sinkronkan lokal", "sync_remote": "Sinkronkan jarak jauh", + "sync_status": "Status Sinkronisasi", + "sync_status_subtitle": "Lihat dan atur sistem sinkronisasi", "sync_upload_album_setting_subtitle": "Membuat dan mengunggah foto serta video Anda ke album yang telah dipilih pada Immich", "tag": "Label", "tag_assets": "Tag aset", @@ -1973,6 +2012,7 @@ "trash_page_select_assets_btn": "Pilih aset", "trash_page_title": "Sampah ({count})", "trashed_items_will_be_permanently_deleted_after": "Item yang dibuang akan dihapus secara permanen setelah {days, plural, one {# hari} other {# hari}}.", + "troubleshoot": "Pemecahan Masalah", "type": "Jenis", "unable_to_change_pin_code": "Tidak dapat mengubah kode PIN", "unable_to_setup_pin_code": "Tidak dapat memasang kode PIN", @@ -2089,5 +2129,6 @@ "yes": "Ya", "you_dont_have_any_shared_links": "Anda tidak memiliki tautan terbagi", "your_wifi_name": "Nama Wi-Fi Anda", - "zoom_image": "Perbesar Gambar" + "zoom_image": "Perbesar Gambar", + "zoom_to_bounds": "Perbesar ke batas" } diff --git a/i18n/it.json b/i18n/it.json index 044e86c6df..0429f92d0b 100644 --- a/i18n/it.json +++ b/i18n/it.json @@ -14,7 +14,7 @@ "add_a_location": "Aggiungi una posizione", "add_a_name": "Aggiungi un nome", "add_a_title": "Aggiungi un titolo", - "add_birthday": "Aggiungi un compleanno", + "add_birthday": "Aggiungi compleanno", "add_endpoint": "Aggiungi un endpoint", "add_exclusion_pattern": "Aggiungi un pattern di esclusione", "add_import_path": "Aggiungi un percorso per l’importazione", @@ -28,10 +28,12 @@ "add_to_album": "Aggiungi all'album", "add_to_album_bottom_sheet_added": "Aggiunto in {album}", "add_to_album_bottom_sheet_already_exists": "Già presente in {album}", + "add_to_album_bottom_sheet_some_local_assets": "Alcune risorse locali non possono essere aggiunte all'album", "add_to_album_toggle": "Attiva/disattiva selezione per {album}", "add_to_albums": "Aggiungi ad album", "add_to_albums_count": "Aggiungi ad album ({count})", "add_to_shared_album": "Aggiungi ad album condiviso", + "add_upload_to_stack": "Aggiungi caricamento allo stack", "add_url": "Aggiungi URL", "added_to_archive": "Aggiunto all'archivio", "added_to_favorites": "Aggiunto ai preferiti", @@ -118,11 +120,18 @@ "library_settings_description": "Gestisci le impostazioni della libreria esterna", "library_tasks_description": "Scansiona le librerie esterne per risorse nuove o modificate", "library_watching_enable_description": "Osserva le librerie esterne per cambiamenti", - "library_watching_settings": "Osserva librerie (SPERIMENTALE)", + "library_watching_settings": "Osserva librerie [SPERIMENTALE]", "library_watching_settings_description": "Osserva automaticamente i cambiamenti dei file", "logging_enable_description": "Attiva il logging", "logging_level_description": "Quando attivato, che livello di log utilizzare.", "logging_settings": "Registro dei Log", + "machine_learning_availability_checks": "Verifiche di disponibilità", + "machine_learning_availability_checks_description": "Rileva automaticamente e usa i server di machine learning disponibili", + "machine_learning_availability_checks_enabled": "Attiva verifiche di disponibilità", + "machine_learning_availability_checks_interval": "Intervallo di verifica", + "machine_learning_availability_checks_interval_description": "Intervallo (ms) tra le verifiche di disponibilità", + "machine_learning_availability_checks_timeout": "Timeout richiesta", + "machine_learning_availability_checks_timeout_description": "Timeout (ms) per le verifiche di disponibilità", "machine_learning_clip_model": "Modello CLIP", "machine_learning_clip_model_description": "Il nome del modello CLIP mostrato qui. Nota che devi rieseguire il processo 'Ricerca Intelligente' per tutte le immagini al cambio del modello.", "machine_learning_duplicate_detection": "Rilevamento Duplicati", @@ -145,6 +154,18 @@ "machine_learning_min_detection_score_description": "Punteggio di confidenza minimo per rilevare un volto, da 0 a 1. Valori più bassi rileveranno più volti, ma potrebbero generare risultati fasulli.", "machine_learning_min_recognized_faces": "Minimo numero di volti rilevati", "machine_learning_min_recognized_faces_description": "Il numero minimo di volti riconosciuti per creare una persona. Aumentando questo valore si rende il riconoscimento facciale più preciso, ma aumenta la possibilità che un volto non venga assegnato a una persona.", + "machine_learning_ocr": "OCR", + "machine_learning_ocr_description": "Utilizza il machine learning per riconoscere il testo nelle immagini", + "machine_learning_ocr_enabled": "Attiva OCR", + "machine_learning_ocr_enabled_description": "Se disattivato, le immagini non saranno sottoposte al riconoscimento del testo.", + "machine_learning_ocr_max_resolution": "Massima risoluzione", + "machine_learning_ocr_max_resolution_description": "L'anteprima maggiore di questa risoluzione verrà ridimensionata preservando le proporzioni. Valori maggiori sono più accurati, ma impiegano più tempo per essere processati e usano più memoria.", + "machine_learning_ocr_min_detection_score": "Punteggio minimo di rilevamento", + "machine_learning_ocr_min_detection_score_description": "Punteggio minimo di affidabilità per il rilevamento del testo da 0 a 1. Valori più bassi rileveranno più testo, ma potrebbero generare falsi positivi.", + "machine_learning_ocr_min_recognition_score": "Punteggio minimo di riconoscimento", + "machine_learning_ocr_min_score_recognition_description": "Punteggio minimo di affidabilità per il riconoscimento del testo da 0 a 1. Valori più bassi rileveranno più testo, ma potrebbero generare falsi positivi.", + "machine_learning_ocr_model": "Modello OCR", + "machine_learning_ocr_model_description": "I modelli server sono più accurati dei modelli mobile, ma impiegano più tempo nel processo e utilizzano più memoria.", "machine_learning_settings": "Impostazioni Machine Learning", "machine_learning_settings_description": "Gestisci le impostazioni e le funzionalità del machine learning", "machine_learning_smart_search": "Ricerca Intelligente", @@ -169,7 +190,7 @@ "map_style_description": "URL per un tema della mappa style.json", "memory_cleanup_job": "Pulizia dei vecchi Ricordi", "memory_generate_job": "Generazione dei Ricordi", - "metadata_extraction_job": "Estrazione Metadata", + "metadata_extraction_job": "Estrazione Metadati", "metadata_extraction_job_description": "Estrai informazioni dai metadati di ciascuna risorsa, come coordinate GPS, volti e risoluzione", "metadata_faces_import_setting": "Abilita l'importazione dei volti", "metadata_faces_import_setting_description": "Importa i volti dai dati EXIF dell'immagine e dai file sidecar", @@ -202,6 +223,8 @@ "notification_email_ignore_certificate_errors_description": "Ignora errori TLS di validazione del certificato (sconsigliato)", "notification_email_password_description": "Password da usare per l'autenticazione con il server email", "notification_email_port_description": "Porta del server email (es. 25, 465, 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "Usa SMTPS (SMTP over TLS)", "notification_email_sent_test_email_button": "Invia email di prova e salva", "notification_email_setting_description": "Impostazioni per le notifiche via email", "notification_email_test_email": "Invia email di prova", @@ -234,6 +257,7 @@ "oauth_storage_quota_default_description": "Limite in GiB da usare quanto nessuna dichiarazione di ambito(claim) è stata fornita.", "oauth_timeout": "Timeout Richiesta", "oauth_timeout_description": "Timeout per le richieste, espresso in millisecondi", + "ocr_job_description": "Utilizza il machine learning per riconoscere il testo nelle immagini", "password_enable_description": "Login con email e password", "password_settings": "Login con password", "password_settings_description": "Gestisci impostazioni del login con password", @@ -300,11 +324,11 @@ "transcoding_acceleration_rkmpp": "RKMPP (Solo per SOC Rockchip)", "transcoding_acceleration_vaapi": "VAAPI", "transcoding_accepted_audio_codecs": "Codifiche audio accettate", - "transcoding_accepted_audio_codecs_description": "Seleziona quali codifiche audio non devono essere trascodificate. Solo usato per alcune politiche di trascodifica.", + "transcoding_accepted_audio_codecs_description": "Seleziona quali codifiche audio non devono essere transcodificate. Solo usato per alcune politiche di transcodifica.", "transcoding_accepted_containers": "Contenitori accettati", "transcoding_accepted_containers_description": "Seleziona quali formati non hanno bisogno di essere remuxati in MP4. Usato solo per certe politiche di transcodifica.", "transcoding_accepted_video_codecs": "Codifiche video accettate", - "transcoding_accepted_video_codecs_description": "Seleziona quali codifiche video non devono essere trascodificate. Usato solo per alcune politiche di trascodifica.", + "transcoding_accepted_video_codecs_description": "Seleziona quali codifiche video non devono essere transcodificate. Usato solo per alcune politiche di transcodifica.", "transcoding_advanced_options_description": "Impostazioni che la maggior parte degli utenti non dovrebbero cambiare", "transcoding_audio_codec": "Codifica Audio", "transcoding_audio_codec_description": "Opus è l'opzione con la qualità più alta, ma è meno compatibile con dispositivi o software vecchi.", @@ -324,7 +348,7 @@ "transcoding_max_b_frames": "B-frames Massimi", "transcoding_max_b_frames_description": "Valori più alti migliorano l'efficienza di compressione, ma rallentano l'encoding. Potrebbero non essere compatibili con l'accelerazione hardware su dispositivi più vecchi. 0 disabilita i B-frames, mentre -1 imposta questo valore automaticamente.", "transcoding_max_bitrate": "Bitrate massimo", - "transcoding_max_bitrate_description": "Impostare un bitrate massimo può rendere le dimensioni dei file più prevedibili a un costo minore per la qualità. A 720p, i valori tipici sono 2600 kbit/s per VP9 o HEVC, o 4500 kbit/s per H.264. Disabilitato se impostato su 0.", + "transcoding_max_bitrate_description": "Impostare un bitrate massimo può rendere le dimensioni dei file più prevedibili a un costo minore per la qualità. A 720p, i valori tipici sono 2600 kbit/s per VP9 o HEVC, o 4500 kbit/s per H.264. Disabilitato se impostato su 0. Quando non viene specificata alcuna unità, si presume k (per kbit/s); pertanto 5000, 5000k e 5M (per Mbit/s) sono equivalenti.", "transcoding_max_keyframe_interval": "Intervallo massimo dei keyframe", "transcoding_max_keyframe_interval_description": "Imposta la distanza massima tra i keyframe. Valori più bassi peggiorano l'efficienza di compressione, però migliorano i tempi di ricerca e possono migliorare la qualità nelle scene con movimenti rapidi. 0 imposta questo valore automaticamente.", "transcoding_optimal_description": "Video con risoluzione più alta rispetto alla risoluzione desiderata o in formato non accettato", @@ -337,22 +361,22 @@ "transcoding_reference_frames": "Frame di riferimento", "transcoding_reference_frames_description": "Il numero di frame da prendere in considerazione nel comprimere un determinato frame. Valori più alti migliorano l'efficienza di compressione, ma rallentano la codifica. 0 imposta questo valore automaticamente.", "transcoding_required_description": "Solo video che non sono in un formato accettato", - "transcoding_settings": "Impostazioni Trascodifica Video", + "transcoding_settings": "Impostazioni Transcodifica Video", "transcoding_settings_description": "Gestisci quali video transcodificare e come processarli", "transcoding_target_resolution": "Risoluzione desiderata", "transcoding_target_resolution_description": "Risoluzioni più elevate possono preservare più dettagli ma richiedono più tempo per la codifica, producono file di dimensioni maggiori e possono ridurre la reattività dell'applicazione.", "transcoding_temporal_aq": "AQ temporale", - "transcoding_temporal_aq_description": "Si applica solo a NVENC. Aumenta la qualità delle scene con molto dettaglio e poco movimento. Potrebbe non essere compatibile con dispositivi più vecchi.", + "transcoding_temporal_aq_description": "Si applica solo a NVENC. La Quantizzazione Adattiva Temporale aumenta la qualità delle scene con molto dettaglio e poco movimento. Potrebbe non essere compatibile con dispositivi più vecchi.", "transcoding_threads": "Thread", "transcoding_threads_description": "Valori più alti portano a una codifica più veloce, ma lasciano meno spazio al server per elaborare altre attività durante l'attività. Questo valore non dovrebbe essere superiore al numero di core CPU. Massimizza l'utilizzo se impostato su 0.", "transcoding_tone_mapping": "Mappatura della tonalità", "transcoding_tone_mapping_description": "Tenta di preservare l'aspetto dei video HDR quando convertiti in SDR. Ciascun algoritmo fa diversi compromessi per colore, dettaglio e luminosità. Hable conserva il dettaglio, Mobius conserva il colore e Reinhard conserva la luminosità.", "transcoding_transcode_policy": "Politica di transcodifica", - "transcoding_transcode_policy_description": "Politica che determina quando un video deve essere trascodificato. I video HDR verranno sempre trascodificati (eccetto quando la trascodifica è disabilitata).", + "transcoding_transcode_policy_description": "Politica che determina quando un video deve essere transcodificato. I video HDR verranno sempre transcodificati (eccetto quando la transcodifica è disabilitata).", "transcoding_two_pass_encoding": "Codifica a due passaggi", - "transcoding_two_pass_encoding_setting_description": "Trascodifica in due passaggi per produrre video codificati migliori. Quando il bitrate massimo è abilitato (necessario affinché funzioni con H.264 e HEVC), questa modalità utilizza un intervallo di bitrate basato sul bitrate massimo e ignora CRF. Per VP9, CRF può essere utilizzato se il bitrate massimo è disabilitato.", + "transcoding_two_pass_encoding_setting_description": "Transcodifica in due passaggi per produrre video codificati migliori. Quando il bitrate massimo è abilitato (necessario affinché funzioni con H.264 e HEVC), questa modalità utilizza un intervallo di bitrate basato sul bitrate massimo e ignora CRF. Per VP9, CRF può essere utilizzato se il bitrate massimo è disabilitato.", "transcoding_video_codec": "Codec video", - "transcoding_video_codec_description": "VP9 ha alta efficienza e compatibilità web, ma richiede più tempo per la trascodifica. HEVC ha prestazioni simili, ma una minore compatibilità web. H.264 è ampiamente compatibile e veloce da transcodificare, ma produce file molto più grandi. AV1 è il codec più efficiente, ma non è supportato sui dispositivi più vecchi.", + "transcoding_video_codec_description": "VP9 ha alta efficienza e compatibilità web, ma richiede più tempo per la transcodifica. HEVC ha prestazioni simili, ma una minore compatibilità web. H.264 è ampiamente compatibile e veloce da transcodificare, ma produce file molto più grandi. AV1 è il codec più efficiente, ma non è supportato sui dispositivi più vecchi.", "trash_enabled_description": "Abilita Funzionalità Cestino", "trash_number_of_days": "Numero di giorni", "trash_number_of_days_description": "Numero di giorni per cui mantenere gli asset nel cestino prima di rimuoverli definitivamente", @@ -380,8 +404,8 @@ "version_check_implications": "La funzione di controllo della versione fa uso di una comunicazione periodica con github.com", "version_check_settings": "Controllo Versione", "version_check_settings_description": "Abilita/disabilita la notifica per nuove versioni", - "video_conversion_job": "Trascodifica video", - "video_conversion_job_description": "Trascodifica video per maggiore compatibilità con browser e dispositivi" + "video_conversion_job": "Transcodifica video", + "video_conversion_job_description": "Transcodifica video per maggiore compatibilità con browser e dispositivi" }, "admin_email": "Email Amministratore", "admin_password": "Password Amministratore", @@ -393,11 +417,11 @@ "advanced_settings_prefer_remote_subtitle": "Alcuni dispositivi sono estremamente lenti a caricare le miniature da risorse locali. Attiva questa impostazione per caricare invece le immagini remote.", "advanced_settings_prefer_remote_title": "Preferisci immagini remote", "advanced_settings_proxy_headers_subtitle": "Definisci gli header per i proxy che Immich dovrebbe inviare con ogni richiesta di rete", - "advanced_settings_proxy_headers_title": "Header Proxy", + "advanced_settings_proxy_headers_title": "Header Proxy Personalizzato [SPERIMENTALE]", "advanced_settings_readonly_mode_subtitle": "Abilita la modalità di sola lettura in cui le foto possono essere solo visualizzate, mentre funzioni come la selezione di più immagini, la condivisione, la trasmissione e l'eliminazione sono tutte disabilitate. Abilita/Disabilita la sola lettura tramite l'avatar dell'utente dalla schermata principale", "advanced_settings_readonly_mode_title": "Modalità di sola lettura", "advanced_settings_self_signed_ssl_subtitle": "Salta la verifica dei certificati SSL del server. Richiesto con l'uso di certificati self-signed.", - "advanced_settings_self_signed_ssl_title": "Consenti certificati SSL self-signed", + "advanced_settings_self_signed_ssl_title": "Consenti certificati SSL self-signed [SPERIMENTALE]", "advanced_settings_sync_remote_deletions_subtitle": "Rimuovi o ripristina automaticamente un elemento su questo dispositivo quando l'azione è stata fatta via web", "advanced_settings_sync_remote_deletions_title": "Sincronizza le cancellazioni remote [SPERIMENTALE]", "advanced_settings_tile_subtitle": "Impostazioni avanzate dell'utente", @@ -457,10 +481,14 @@ "api_key_description": "Questo valore verrà mostrato una sola volta. Assicurati di copiarlo prima di chiudere la finestra.", "api_key_empty": "Il nome della chiave API non dovrebbe essere vuoto", "api_keys": "Chiavi API", + "app_architecture_variant": "Variante (Architettura)", "app_bar_signout_dialog_content": "Sei sicuro di volerti disconnettere?", "app_bar_signout_dialog_ok": "Si", "app_bar_signout_dialog_title": "Disconnetti", + "app_download_links": "Link per il download dell'app", "app_settings": "Impostazioni Applicazione", + "app_stores": "App Stores", + "app_update_available": "Aggiornamento App disponibile", "appears_in": "Compare in", "apply_count": "Applica ({count, number})", "archive": "Archivio", @@ -533,6 +561,7 @@ "background_backup_running_error": "Il backup in background è attualmente in esecuzione, impossibile avviare il backup manuale", "background_location_permission": "Permesso di localizzazione in background", "background_location_permission_content": "Per fare in modo che sia possibile cambiare rete quando è in esecuzione in background, Immich deve *sempre* avere accesso alla tua posizione precisa in modo da poter leggere il nome della rete Wi-Fi", + "background_options": "Opzioni sfondo", "backup": "Backup", "backup_album_selection_page_albums_device": "Album sul dispositivo ({count})", "backup_album_selection_page_albums_tap": "Tap per includere, doppio tap per escludere", @@ -540,8 +569,10 @@ "backup_album_selection_page_select_albums": "Seleziona gli album", "backup_album_selection_page_selection_info": "Informazioni sulla selezione", "backup_album_selection_page_total_assets": "Numero totale delle risorse", + "backup_albums_sync": "Sincronizzazione album di backup", "backup_all": "Tutti", "backup_background_service_backup_failed_message": "È stato impossibile fare il backup dei contenuti. Riprovo…", + "backup_background_service_complete_notification": "Backup completato", "backup_background_service_connection_failed_message": "Impossibile connettersi al server. Riprovo…", "backup_background_service_current_upload_notification": "Caricamento di {filename} in corso", "backup_background_service_default_notification": "Ricerca di nuovi contenuti…", @@ -589,6 +620,7 @@ "backup_controller_page_turn_on": "Attiva backup", "backup_controller_page_uploading_file_info": "Caricamento informazioni file", "backup_err_only_album": "Non è possibile rimuovere l'unico album", + "backup_error_sync_failed": "Sincronizzazione non riuscita. Impossibile elaborare il backup.", "backup_info_card_assets": "risorse", "backup_manual_cancelled": "Annullato", "backup_manual_in_progress": "Caricamento già in corso. Riprova più tardi", @@ -608,7 +640,7 @@ "blurred_background": "Sfondo sfocato", "bugs_and_feature_requests": "Bug & Richieste di nuove funzionalità", "build": "Compilazione", - "build_image": "Compila Immagine", + "build_image": "Immagine Compilata", "bulk_delete_duplicates_confirmation": "Sei sicuro di voler cancellare {count, plural, one {# asset duplicato} other {# assets duplicati}}? Questa operazione manterrà l'asset più pesante di ogni gruppo e cancellerà permanentemente tutti gli altri duplicati. Non puoi annullare questa operazione!", "bulk_keep_duplicates_confirmation": "Sei sicuro di voler tenere {count, plural, one {# asset duplicato} other {# assets duplicati}}? Questa operazione risolverà tutti i gruppi duplicati senza cancellare nulla.", "bulk_trash_duplicates_confirmation": "Sei davvero sicuro di voler cancellare {count, plural, one {# asset duplicato} other {# assets duplicati}}? Questa operazione manterrà l'asset più pesante di ogni gruppo e cancellerà permanentemente tutti gli altri duplicati.", @@ -650,12 +682,16 @@ "change_password_description": "È stato richiesto di cambiare la password (oppure è la prima volta che accedi). Inserisci la tua nuova password qui sotto.", "change_password_form_confirm_password": "Conferma Password", "change_password_form_description": "Ciao {name},\n\nQuesto è la prima volta che accedi al sistema oppure è stato fatto una richiesta di cambiare la password. Per favore inserisca la nuova password qui sotto.", + "change_password_form_log_out": "Log out da tutti gli altri dispositivi", + "change_password_form_log_out_description": "È consigliato il log out da tutti gli altri dispositivi", "change_password_form_new_password": "Nuova Password", "change_password_form_password_mismatch": "Le password non coincidono", "change_password_form_reenter_new_password": "Inserisci ancora la nuova password", "change_pin_code": "Cambia il codice PIN", "change_your_password": "Modifica la tua password", "changed_visibility_successfully": "Visibilità modificata con successo", + "charging": "In carica", + "charging_requirement_mobile_backup": "Il backup in background richiede che il dispositivo sia in carica", "check_corrupt_asset_backup": "Verifica la presenza di backup di asset corrotti", "check_corrupt_asset_backup_button": "Effettua controllo", "check_corrupt_asset_backup_description": "Effettua questo controllo solo sotto rete Wi-Fi e quando tutti gli asset sono stati sottoposti a backup. La procedura potrebbe impiegare qualche minuto.", @@ -675,7 +711,7 @@ "client_cert_invalid_msg": "File certificato invalido o password errata", "client_cert_remove_msg": "Certificato client rimosso", "client_cert_subtitle": "Supporta solo il formato PKCS12 (.p12, .pfx). L'importazione/rimozione del certificato è disponibile solo prima del login", - "client_cert_title": "Certificato Client SSL", + "client_cert_title": "Certificato Client SSL [SPERIMENTALE]", "clockwise": "Senso orario", "close": "Chiudi", "collapse": "Restringi", @@ -687,7 +723,6 @@ "comments_and_likes": "Commenti & mi piace", "comments_are_disabled": "I commenti sono disabilitati", "common_create_new_album": "Crea nuovo Album", - "common_server_error": "Verifica la connessione di rete, assicurati che il server sia raggiungibile e che le versioni dell’app e del server siano compatibili.", "completed": "Completato", "confirm": "Conferma", "confirm_admin_password": "Conferma password dell'amministratore", @@ -722,10 +757,11 @@ "copy_to_clipboard": "Copia negli appunti", "country": "Nazione", "cover": "Riempi la finestra", - "covers": "Copre", + "covers": "Copertine", "create": "Crea", "create_album": "Crea album", "create_album_page_untitled": "Senza titolo", + "create_api_key": "Crea chiave API", "create_library": "Crea libreria", "create_link": "Crea link", "create_link_to_share": "Crea link da condividere", @@ -755,9 +791,10 @@ "daily_title_text_date_year": "E, dd MMM, yyyy", "dark": "Scuro", "dark_theme": "Imposta tema scuro", - "date_after": "Data dopo", + "date": "Data", + "date_after": "Dopo la data", "date_and_time": "Data e ora", - "date_before": "Data prima", + "date_before": "Prima della data", "date_format": "E, d LLL, y • hh:mm", "date_of_birth_saved": "Data di nascita salvata con successo", "date_range": "Intervallo di date", @@ -869,7 +906,6 @@ "edit_tag": "Modifica tag", "edit_title": "Modifica Titolo", "edit_user": "Modifica utente", - "edited": "Modificato", "editor": "Editor", "editor_close_without_save_prompt": "Le modifiche non verranno salvate", "editor_close_without_save_title": "Vuoi chiudere l'editor?", @@ -1025,6 +1061,7 @@ "exif_bottom_sheet_description_error": "Errore durante l'aggiornamento della descrizione", "exif_bottom_sheet_details": "DETTAGLI", "exif_bottom_sheet_location": "POSIZIONE", + "exif_bottom_sheet_no_description": "Nessuna descrizione", "exif_bottom_sheet_people": "PERSONE", "exif_bottom_sheet_person_add_person": "Aggiungi nome", "exit_slideshow": "Esci dalla presentazione", @@ -1063,6 +1100,7 @@ "features_setting_description": "Gestisci le funzionalità dell'app", "file_name": "Nome file", "file_name_or_extension": "Nome file o estensione", + "file_size": "Dimensione del file", "filename": "Nome file", "filetype": "Tipo file", "filter": "Filtro", @@ -1102,11 +1140,10 @@ "hash_asset": "Risorsa hash", "hashed_assets": "Risorse hash", "hashing": "Hashing", - "header_settings_add_header_tip": "Aggiungi Header", + "header_settings_add_header_tip": "Aggiungi header", "header_settings_field_validator_msg": "Il valore non può essere vuoto", "header_settings_header_name_input": "Nome header", "header_settings_header_value_input": "Valore header", - "headers_settings_tile_subtitle": "Definisci gli header per i proxy che l'app deve inviare con ogni richiesta di rete", "headers_settings_tile_title": "Header proxy personalizzati", "hi_user": "Ciao {name} ({email})", "hide_all_people": "Nascondi tutte le persone", @@ -1161,7 +1198,7 @@ "in_archive": "In archivio", "include_archived": "Includi Archiviati", "include_shared_albums": "Includi album condivisi", - "include_shared_partner_assets": "Includi asset condivisi del compagno", + "include_shared_partner_assets": "Includi elementi condivisi dai compagni", "individual_share": "Condivisione individuale", "individual_shares": "Condivisioni individuali", "info": "Info", @@ -1227,6 +1264,7 @@ "local_media_summary": "Riepilogo dei Media Locali", "local_network": "Rete locale", "local_network_sheet_info": "L'app si collegherà al server tramite questo URL quando è in uso la rete Wi-Fi specificata", + "location": "Posizione", "location_permission": "Permesso di localizzazione", "location_permission_content": "Per usare la funzione di cambio automatico, Immich necessita del permesso di localizzazione così da poter leggere il nome della rete Wi-Fi in uso", "location_picker_choose_on_map": "Scegli una mappa", @@ -1331,6 +1369,8 @@ "minute": "Minuto", "minutes": "Minuti", "missing": "Mancanti", + "mobile_app": "App Cellulare", + "mobile_app_download_onboarding_note": "Scarica l’app mobile dedicata utilizzando una delle seguenti opzioni", "model": "Modello", "month": "Mese", "monthly_title_text_date_format": "MMMM y", @@ -1349,14 +1389,18 @@ "my_albums": "I miei album", "name": "Nome", "name_or_nickname": "Nome o soprannome", + "navigate": "Naviga", + "navigate_to_time": "Navigazione alla data", "network_requirement_photos_upload": "Utilizza la connessione dati per il backup delle foto", "network_requirement_videos_upload": "Utilizza la connessione dati per il backup dei video", + "network_requirements": "Requisiti di rete", "network_requirements_updated": "Requisiti di rete modificati, coda di backup reimpostata", "networking_settings": "Rete", "networking_subtitle": "Gestisci le impostazioni riguardanti gli endpoint del server", "never": "Mai", "new_album": "Nuovo Album", "new_api_key": "Nuova Chiave di API", + "new_date_range": "Nuovo intervallo di date", "new_password": "Nuova password", "new_person": "Nuova persona", "new_pin_code": "Nuovo codice PIN", @@ -1407,6 +1451,9 @@ "notifications": "Notifiche", "notifications_setting_description": "Gestisci notifiche", "oauth": "OAuth", + "obtainium_configurator": "Configuratore Obtainium", + "obtainium_configurator_instructions": "Utilizza Obtainium per installare e aggiornare l'app Android direttamente dalla versione rilasciata su GitHub da Immich. Crea una chiave API e seleziona una variante per creare il tuo link di configurazione Obtainium", + "ocr": "OCR", "official_immich_resources": "Risorse Ufficiali Immich", "offline": "Offline", "offset": "Offset", @@ -1511,10 +1558,14 @@ "play_memories": "Riproduci ricordi", "play_motion_photo": "Riproduci foto in movimento", "play_or_pause_video": "Avvia o metti in pausa il video", + "play_original_video": "Riproduci il video originale", + "play_original_video_setting_description": "Preferisci la riproduzione dei video originali anzichè ricodificarli. Se l'originale non è compatibile non sarà riprodotto correttamente.", + "play_transcoded_video": "Riproduci video ricodificato", "please_auth_to_access": "Autenticati per accedere", "port": "Porta", "preferences_settings_subtitle": "Gestisci le preferenze dell'app", "preferences_settings_title": "Preferenze", + "preparing": "Preparando", "preset": "Preimpostazione", "preview": "Anteprima", "previous": "Precedente", @@ -1527,13 +1578,9 @@ "privacy": "Privacy", "profile": "Profilo", "profile_drawer_app_logs": "Registri", - "profile_drawer_client_out_of_date_major": "L’app non è aggiornata. Aggiorna all’ultima versione principale.", - "profile_drawer_client_out_of_date_minor": "L'applicazione non è aggiornata. Aggiorna all'ultima versione minore.", "profile_drawer_client_server_up_to_date": "Client e server sono aggiornati", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Modalità di sola lettura abilitata. Tieni premuto sull'avatar dell'utente per disabilitarla.", - "profile_drawer_server_out_of_date_major": "Il server non è aggiornato. Aggiorna all'ultima versione principale.", - "profile_drawer_server_out_of_date_minor": "Il server non è aggiornato. Aggiorna all'ultima versione minore.", "profile_image_of_user": "Immagine profilo di {user}", "profile_picture_set": "Foto profilo impostata.", "public_album": "Album pubblico", @@ -1580,6 +1627,7 @@ "read_changelog": "Leggi Riepilogo Modifiche", "readonly_mode_disabled": "Modalità di sola lettura disabilitata", "readonly_mode_enabled": "Modalità di sola lettura abilitata", + "ready_for_upload": "Pronto per il caricamento", "reassign": "Riassegna", "reassigned_assets_to_existing_person": "{count, plural, one {Riassegnato # asset} other {Riassegnati # assets}} {name, select, null {ad una persona esistente} other {a {name}}}", "reassigned_assets_to_new_person": "{count, plural, one {Riassegnato # asset} other {Riassegnati # assets}} ad una nuova persona", @@ -1649,6 +1697,7 @@ "reset_sqlite_confirmation": "Vuoi davvero reimpostare il database SQLite? Dovrai disconnetterti e riconnetterti per risincronizzare i dati", "reset_sqlite_success": "Database SQLite reimpostato correttamente", "reset_to_default": "Ripristina i valori predefiniti", + "resolution": "Risoluzione", "resolve_duplicates": "Risolvi duplicati", "resolved_all_duplicates": "Tutti i duplicati sono stati risolti", "restore": "Ripristina", @@ -1683,6 +1732,9 @@ "search_by_description_example": "Giornata di escursioni a Sapa", "search_by_filename": "Cerca per nome del file o estensione", "search_by_filename_example": "es. IMG_1234.JPG o PNG", + "search_by_ocr": "Ricerca tramite OCR", + "search_by_ocr_example": "Caffè Latte", + "search_camera_lens_model": "Cerca il modello del'obiettivo...", "search_camera_make": "Cerca produttore fotocamera...", "search_camera_model": "Cerca modello fotocamera...", "search_city": "Cerca città...", @@ -1699,6 +1751,7 @@ "search_filter_location_title": "Seleziona posizione", "search_filter_media_type": "Tipo di media", "search_filter_media_type_title": "Seleziona il tipo di media", + "search_filter_ocr": "Cerca tramite OCR", "search_filter_people_title": "Seleziona persone", "search_for": "Cerca per", "search_for_existing_person": "Cerca per persona esistente", @@ -1706,11 +1759,11 @@ "search_no_people": "Nessuna persona", "search_no_people_named": "Nessuna persona chiamate \"{name}\"", "search_no_result": "Nessun risultato trovato, prova con un termine o combinazione diversi", - "search_options": "Opzioni Ricerca", + "search_options": "Opzioni di ricerca", "search_page_categories": "Categoria", "search_page_motion_photos": "Foto in movimento", - "search_page_no_objects": "Nessuna informazione relativa all'oggetto disponibile", - "search_page_no_places": "Nessun informazione sul luogo disponibile", + "search_page_no_objects": "Nessuna informazione sugli oggetti disponibile", + "search_page_no_places": "Nessuna informazione sui luoghi disponibile", "search_page_screenshots": "Screenshot", "search_page_search_photos_videos": "Ricerca le tue foto e i tuoi video", "search_page_selfies": "Selfie", @@ -1761,6 +1814,7 @@ "server_online": "Server Online", "server_privacy": "Privacy del Server", "server_stats": "Statistiche Server", + "server_update_available": "Aggiornamento Server disponibile", "server_version": "Versione Server", "set": "Imposta", "set_as_album_cover": "Imposta come copertina album", @@ -1789,6 +1843,8 @@ "setting_notifications_subtitle": "Cambia le impostazioni di notifica", "setting_notifications_total_progress_subtitle": "Avanzamento complessivo del caricamento (completati/risorse totali)", "setting_notifications_total_progress_title": "Mostra avanzamento del backup in background", + "setting_video_viewer_auto_play_subtitle": "Avvia automaticamente la riproduzione dei video quando vengono aperti", + "setting_video_viewer_auto_play_title": "Riproduci video automaticamente", "setting_video_viewer_looping_title": "Looping", "setting_video_viewer_original_video_subtitle": "Quando riproduci un video dal server, riproduci l'originale anche se è disponibile una versione transcodificata. Questo potrebbe portare a buffering. I video disponibili localmente sono sempre riprodotti a qualità originale indipendentemente da questa impostazione.", "setting_video_viewer_original_video_title": "Forza video originale", @@ -1906,11 +1962,12 @@ "stack_action_prompt": "{count} elementi raggruppati", "stack_duplicates": "Raggruppa i duplicati", "stack_select_one_photo": "Seleziona una foto principale per il gruppo", - "stack_selected_photos": "Impila foto selezionate", + "stack_selected_photos": "Raggruppa foto selezionate", "stacked_assets_count": "{count, plural, one {Raggruppato # asset} other {Raggruppati # asset}}", "stacktrace": "Traccia dell'errore", "start": "Avvia", "start_date": "Data di inizio", + "start_date_before_end_date": "La data di inizio deve essere precedente alla data di fine", "state": "Provincia", "status": "Stato", "stop_casting": "Interrompi trasmissione", @@ -1967,6 +2024,7 @@ "theme_setting_three_stage_loading_title": "Abilita il caricamento a tre stage", "they_will_be_merged_together": "Verranno uniti insieme", "third_party_resources": "Risorse di Terze Parti", + "time": "Orario", "time_based_memories": "Ricordi basati sul tempo", "timeline": "Linea temporale", "timezone": "Fuso orario", @@ -1999,6 +2057,7 @@ "troubleshoot": "Risoluzione dei problemi", "type": "Tipo", "unable_to_change_pin_code": "Impossibile cambiare il codice PIN", + "unable_to_check_version": "Impossibile controllare la versione del server o dell'app", "unable_to_setup_pin_code": "Impossibile configurare il codice PIN", "unarchive": "Annulla l'archiviazione", "unarchive_action_prompt": "{count} elementi rimossi dall'Archivio", @@ -2022,7 +2081,7 @@ "unselect_all": "Deseleziona tutto", "unselect_all_duplicates": "Deseleziona tutti i duplicati", "unselect_all_in": "Deseleziona tutto in {group}", - "unstack": "Rimuovi dal gruppo", + "unstack": "Separa dal gruppo", "unstack_action_prompt": "{count} separati", "unstacked_assets_count": "{count, plural, one {Separato # asset} other {Separati # asset}}", "untagged": "Senza tag", @@ -2097,9 +2156,9 @@ "view_similar_photos": "Visualizza le foto simili", "view_stack": "Visualizza Raggruppamento", "view_user": "Visualizza Utente", - "viewer_remove_from_stack": "Rimuovi dalla pila", + "viewer_remove_from_stack": "Rimuovi dal gruppo", "viewer_stack_use_as_main_asset": "Usa come risorsa principale", - "viewer_unstack": "Rimuovi dal gruppo", + "viewer_unstack": "Separa dal gruppo", "visibility_changed": "Visibilità modificata per {count, plural, one {# persona} other {# persone}}", "waiting": "In Attesa", "warning": "Attenzione", @@ -2110,8 +2169,8 @@ "wrong_pin_code": "Codice PIN errato", "year": "Anno", "years_ago": "{years, plural, one {# anno} other {# anni}} fa", - "yes": "Si", - "you_dont_have_any_shared_links": "Non è presente alcun link condiviso", + "yes": "Sì", + "you_dont_have_any_shared_links": "Non hai nessun link condiviso", "your_wifi_name": "Nome della tua rete Wi-Fi", "zoom_image": "Ingrandisci immagine", "zoom_to_bounds": "Ingrandisci fino ai bordi" diff --git a/i18n/ja.json b/i18n/ja.json index ccee08fa01..c3e53082e8 100644 --- a/i18n/ja.json +++ b/i18n/ja.json @@ -28,6 +28,7 @@ "add_to_album": "アルバムに追加", "add_to_album_bottom_sheet_added": "{album}に追加", "add_to_album_bottom_sheet_already_exists": "{album}に追加済み", + "add_to_album_bottom_sheet_some_local_assets": "いくつかの項目はまだサーバーへアップロードされていないためアルバムに追加できませんでした", "add_to_album_toggle": "{album}の選択を切り替え", "add_to_albums": "アルバムに追加", "add_to_albums_count": "{count}つのアルバムへ追加", @@ -123,6 +124,8 @@ "logging_enable_description": "ログの有効化", "logging_level_description": "有効な場合に使用されるログ レベル。", "logging_settings": "ログ", + "machine_learning_availability_checks": "可用性の確認", + "machine_learning_availability_checks_description": "利用可能な機械学習のサーバーを自動で検知し優先的に使用します", "machine_learning_availability_checks_enabled": "可用性チェックを有効にする", "machine_learning_availability_checks_interval": "チェックの間隔", "machine_learning_availability_checks_interval_description": "可用性チェックの間隔(ミリ秒単位)", @@ -428,6 +431,7 @@ "album_remove_user_confirmation": "本当に{user}を削除しますか?", "album_search_not_found": "検索に一致するアルバムがありません", "album_share_no_users": "このアルバムを全てのユーザーと共有したか、共有するユーザーがいないようです。", + "album_summary": "アルバムのまとめ", "album_updated": "アルバム更新", "album_updated_setting_description": "共有アルバムに新しいアセットが追加されたとき通知を受け取る", "album_user_left": "{album} を去りました", @@ -499,6 +503,8 @@ "asset_restored_successfully": "復元できました", "asset_skipped": "スキップ済", "asset_skipped_in_trash": "ゴミ箱の中", + "asset_trashed": "項目が削除されました", + "asset_troubleshoot": "項目をトラブルシューㇳ", "asset_uploaded": "アップロード済", "asset_uploading": "アップロード中…", "asset_viewer_settings_subtitle": "ギャラリービューアーに関する設定", @@ -532,8 +538,10 @@ "autoplay_slideshow": "スライドショーを自動再生", "back": "戻る", "back_close_deselect": "戻る、閉じる、選択解除", + "background_backup_running_error": "バックグラウンドのバックアップがすでに行われている最中です。そのため、マニュアルでのバックアップを開始することはできません。", "background_location_permission": "バックグラウンド位置情報アクセス", "background_location_permission_content": "正常にWi-Fiの名前(SSID)を獲得するにはアプリが常に詳細な位置情報にアクセスできる必要があります", + "background_options": "バックグラウンドの動作オプション", "backup": "バックアップ", "backup_album_selection_page_albums_device": "デバイス上のアルバム({count})", "backup_album_selection_page_albums_tap": "タップで選択、ダブルタップで除外", @@ -541,6 +549,7 @@ "backup_album_selection_page_select_albums": "アルバムを選択", "backup_album_selection_page_selection_info": "選択・除外中のアルバム", "backup_album_selection_page_total_assets": "選択されたアルバムの写真と動画の数", + "backup_albums_sync": "アルバム同期状態をバックアップ", "backup_all": "すべて", "backup_background_service_backup_failed_message": "アップロードに失敗しました。リトライ中…", "backup_background_service_connection_failed_message": "サーバーに接続できません。リトライ中…", @@ -590,6 +599,7 @@ "backup_controller_page_turn_on": "バックアップをオンにする", "backup_controller_page_uploading_file_info": "アップロード中のファイル", "backup_err_only_album": "最低1つのアルバムを選択してください", + "backup_error_sync_failed": "同期に失敗しました。バックアップができません。", "backup_info_card_assets": "写真と動画", "backup_manual_cancelled": "キャンセルされました", "backup_manual_in_progress": "アップロードが進行中です。後でもう一度試してください", @@ -657,6 +667,8 @@ "change_pin_code": "PINコードを変更", "change_your_password": "パスワードを変更します", "changed_visibility_successfully": "非表示設定を正常に変更しました", + "charging": "充電中", + "charging_requirement_mobile_backup": "バックグラウンドでのバックアップを行うためには、デバイスが充電中である必要があります", "check_corrupt_asset_backup": "破損されている項目を探す", "check_corrupt_asset_backup_button": "チェックを行う", "check_corrupt_asset_backup_description": "写真や動画などが全てアップロードし終えてからWi-Fiに接続時のみチェックを行なってください。作業が完了するには数分かかる場合があります", @@ -688,7 +700,6 @@ "comments_and_likes": "コメントといいね", "comments_are_disabled": "コメントは無効化されています", "common_create_new_album": "アルバムを作成", - "common_server_error": "ネットワーク接続を確認し、サーバーが接続できる状態にあるか確認してください。アプリとサーバーのバージョンが一致しているかも確認してください。", "completed": "完了", "confirm": "確認", "confirm_admin_password": "管理者パスワードを確認", @@ -743,6 +754,7 @@ "create_user": "ユーザーを作成", "created": "作成", "created_at": "作成:", + "creating_linked_albums": "リンクされたアルバムを作成中・・・", "crop": "クロップ", "curated_object_page_title": "被写体", "current_device": "現在のデバイス", @@ -869,7 +881,6 @@ "edit_tag": "タグを編集する", "edit_title": "タイトルを編集", "edit_user": "ユーザーを編集", - "edited": "編集しました", "editor": "編集画面", "editor_close_without_save_prompt": "変更は破棄されます", "editor_close_without_save_title": "編集画面を閉じますか?", @@ -892,7 +903,9 @@ "error": "エラー", "error_change_sort_album": "アルバムの表示順の変更に失敗しました", "error_delete_face": "アセットから顔の削除ができませんでした", + "error_getting_places": "場所の取得に失敗しました", "error_loading_image": "画像の読み込みエラー", + "error_loading_partners": "パートナーの読み込みに失敗しました: {error}", "error_saving_image": "エラー: {error}", "error_tag_face_bounding_box": "顔の登録に失敗しました - 顔を囲む四角形の座標取得に失敗", "error_title": "エラー - 問題が発生しました", @@ -1057,6 +1070,7 @@ "favorites_page_no_favorites": "お気に入り登録された項目がありません", "feature_photo_updated": "人物画像が更新されました", "features": "機能", + "features_in_development": "開発中の機能", "features_setting_description": "アプリの機能を管理する", "file_name": "ファイル名", "file_name_or_extension": "ファイル名または拡張子", @@ -1103,7 +1117,6 @@ "header_settings_field_validator_msg": "ヘッダを空白にはできません", "header_settings_header_name_input": "ヘッダの名前", "header_settings_header_value_input": "ヘッダのバリュー", - "headers_settings_tile_subtitle": "プロキシヘッダを設定する", "headers_settings_tile_title": "カスタムプロキシヘッダ", "hi_user": "こんにちは、{name}( {email})さん", "hide_all_people": "全ての人物を非表示", @@ -1221,6 +1234,7 @@ "local": "ローカル", "local_asset_cast_failed": "サーバーにアップロードされていない項目はキャストできません", "local_assets": "ローカルの項目", + "local_media_summary": "ローカルメディアのまとめ", "local_network": "ローカルネットワーク", "local_network_sheet_info": "アプリは指定されたWi-Fiに繋がっている時サーバーへの接続を下記のURLで行います", "location_permission": "位置情報権限", @@ -1232,6 +1246,7 @@ "location_picker_longitude_hint": "経度を入力", "lock": "ロック", "locked_folder": "鍵付きフォルダー", + "log_detail_title": "ログの詳細", "log_out": "ログアウト", "log_out_all_devices": "全てのデバイスからログアウト", "logged_in_as": "{user}としてログイン中", @@ -1262,6 +1277,7 @@ "login_password_changed_success": "パスワードの変更に成功", "logout_all_device_confirmation": "本当に全てのデバイスからログアウトしますか?", "logout_this_device_confirmation": "本当にこのデバイスからログアウトしますか?", + "logs": "ログ", "longitude": "経度", "look": "見た目", "loop_videos": "動画をループ", @@ -1304,6 +1320,7 @@ "mark_as_read": "既読にする", "marked_all_as_read": "すべて既読にしました", "matches": "マッチ", + "matching_assets": "一致する項目", "media_type": "メディアタイプ", "memories": "メモリー", "memories_all_caught_up": "これで全部です", @@ -1344,6 +1361,7 @@ "name_or_nickname": "名前またはニックネーム", "network_requirement_photos_upload": "モバイル通信を使用して写真のバックアップを行う", "network_requirement_videos_upload": "モバイル通信を使用して動画のバックアップを行う", + "network_requirements": "ネットワークの要件", "network_requirements_updated": "ネットワークの条件が変更されたため、バックアップの順番待ちをリセットします", "networking_settings": "ネットワーク", "networking_subtitle": "サーバーエンドポイントに関する設定", @@ -1354,6 +1372,7 @@ "new_person": "新しい人物", "new_pin_code": "新しいPINコード", "new_pin_code_subtitle": "鍵付きフォルダーを利用するのが初めてのようです。PINコードを作成してください", + "new_timeline": "新たなタイムライン", "new_user_created": "新しいユーザーが作成されました", "new_version_available": "新しいバージョンが利用可能", "newest_first": "最新順", @@ -1367,20 +1386,25 @@ "no_assets_message": "クリックして最初の写真をアップロード", "no_assets_to_show": "表示する項目がありません", "no_cast_devices_found": "キャスト先のデバイスが見つかりません", + "no_checksum_local": "チェックサムが見つかりません - デバイス上の項目を取得できないようです", + "no_checksum_remote": "チェックサムが見つかりません - サーバー上の項目を取得できないようです", "no_duplicates_found": "重複は見つかりませんでした。", "no_exif_info_available": "exif情報が利用できません", "no_explore_results_message": "コレクションを探索するにはさらに写真をアップロードしてください。", "no_favorites_message": "お気に入り登録すると好きな写真や動画をすぐに見つけられます", "no_libraries_message": "あなたの写真や動画を表示するための外部ライブラリを作成しましょう", + "no_local_assets_found": "このチェックサムの項目はデバイス上に存在しません", "no_locked_photos_message": "鍵付きフォルダー内の写真や動画は通常のライブラリに表示されなくなります。", "no_name": "名前なし", "no_notifications": "通知なし", "no_people_found": "一致する人物が見つかりません", "no_places": "場所なし", + "no_remote_assets_found": "このチェックサムの項目はサーバー上に存在しません", "no_results": "結果がありません", "no_results_description": "同義語やより一般的なキーワードを試してください", "no_shared_albums_message": "アルバムを作成して写真や動画を共有しましょう", "no_uploads_in_progress": "アップロードは行われていません", + "not_available": "適用なし", "not_in_any_album": "どのアルバムにも入っていない", "not_selected": "選択なし", "note_apply_storage_label_to_previously_uploaded assets": "注意: 以前にアップロードしたアセットにストレージラベルを適用するには以下を実行してください", @@ -1515,13 +1539,9 @@ "privacy": "プライバシー", "profile": "プロフィール", "profile_drawer_app_logs": "ログ", - "profile_drawer_client_out_of_date_major": "アプリが更新されてません。最新のバージョンに更新してください", - "profile_drawer_client_out_of_date_minor": "アプリが更新されてません。最新のバージョンに更新してください", "profile_drawer_client_server_up_to_date": "すべて最新版です", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "読み取り専用モードが有効です。ユーザーのアイコンを長押しして読み取り専用モードを解除してください。", - "profile_drawer_server_out_of_date_major": "サーバーが更新されてません。最新のバージョンに更新してください", - "profile_drawer_server_out_of_date_minor": "サーバーが更新されてません。最新のバージョンに更新してください", "profile_image_of_user": "{user} のプロフィール画像", "profile_picture_set": "プロフィール画像が設定されました。", "public_album": "公開アルバム", @@ -1593,6 +1613,7 @@ "regenerating_thumbnails": "サムネイルを再生成中", "remote": "リモート", "remote_assets": "リモートの項目", + "remote_media_summary": "サーバー上のメディアまとめ", "remove": "削除", "remove_assets_album_confirmation": "本当に{count, plural, one {#個} other {#個}}のアセットをアルバムから削除しますか?", "remove_assets_shared_link_confirmation": "本当にこの共有リンクから{count, plural, one {#個} other {#個}}のアセットを削除しますか?", @@ -1868,6 +1889,7 @@ "show_slideshow_transition": "スライドショーのトランジションを表示", "show_supporter_badge": "サポーターバッジ", "show_supporter_badge_description": "サポーターバッジを表示", + "show_text_search_menu": "テキスト検索メニューを表示", "shuffle": "ランダム", "sidebar": "サイドバー", "sidebar_display_description": "サイドバーにビューへのリンクを表示", @@ -2101,5 +2123,6 @@ "yes": "はい", "you_dont_have_any_shared_links": "共有リンクはありません", "your_wifi_name": "Wi-Fiの名前(SSID)", - "zoom_image": "画像を拡大" + "zoom_image": "画像を拡大", + "zoom_to_bounds": "画面端までズーム" } diff --git a/i18n/ka.json b/i18n/ka.json index 13b0e1d065..063419db11 100644 --- a/i18n/ka.json +++ b/i18n/ka.json @@ -14,6 +14,7 @@ "add_a_location": "დაამატე ადგილი", "add_a_name": "დაამატე სახელი", "add_a_title": "დაასათაურე", + "add_birthday": "დაბადების დღის დამატება", "add_exclusion_pattern": "დაამატე გამონაკლისი ნიმუში", "add_import_path": "დაამატე საიმპორტო მისამართი", "add_location": "დაამატე ადგილი", @@ -21,16 +22,20 @@ "add_partner": "დაამატე პარტნიორი", "add_path": "დაამატე მისამართი", "add_photos": "დაამატე ფოტოები", + "add_tag": "დაამატე თეგი", "add_to": "დაამატე ...ში", "add_to_album": "დაამატე ალბომში", "add_to_album_bottom_sheet_added": "დამატებულია {album}-ში", "add_to_album_bottom_sheet_already_exists": "{album}-ში უკვე არსებობს", + "add_to_albums": "დაამატე ალბომებში", + "add_to_albums_count": "დაამატე ალბომში ({count})", "add_to_shared_album": "დაამატე საზიარო ალბომში", "add_url": "დაამატე URL", "added_to_archive": "დაარქივდა", "added_to_favorites": "დაამატე რჩეულებში", "added_to_favorites_count": "{count, number} დაემატა რჩეულებში", "admin": { + "admin_user": "ადმინ მომხმარებელი", "asset_offline_description": "ეს საგარეო ბიბლიოთეკის აქტივი დისკზე ვერ მოიძებნა და სანაგვეში იქნა მოთავსებული. თუ ფაილი ბიბლიოთეკის შიგნით მდებარეობს, შეამოწმეთ შესაბამისი აქტივი ტაიმლაინზე. ამ აქტივის აღსადგენად, დარწმუნდით რომ ქვემოთ მოცემული ფაილის მისამართი Immich-ის მიერ წვდომადია და დაასკანერეთ ბიბლიოთეკა.", "authentication_settings": "ავთენტიკაციის პარამეტრები", "authentication_settings_description": "პაროლის, OAuth-ის და სხვა ავტენთიფიკაციის პარამეტრების მართვა", @@ -41,7 +46,7 @@ "backup_database_enable_description": "ბაზის დამპების ჩართვა", "backup_keep_last_amount": "წინა დამპების შესანარჩუნებელი რაოდენობა", "backup_settings": "მონაცემთა ბაზის დამპის მორგება", - "backup_settings_description": "მონაცემთა ბაზის პარამეტრების ამრთვა. შენიშვნა: ამ დავალებების მონიტორინგი არ ხდება და თქვენ არ მოგივათ შეტყობინება, თუ ის ჩავარდება.", + "backup_settings_description": "მონაცემთა ბაზის ასლის შექმნის პარამეტრების მრთვა.", "cleared_jobs": "დავალებები {job}-ისათვის გაწმენდილია", "config_set_by_file": "მიმდინარე კონფიგურაცია ფაილის მიერ არის დაყენებული", "confirm_delete_library": "ნამდვილად გინდა {library} ბიბლიოთეკის წაშლა?", @@ -58,6 +63,7 @@ "image_format_description": "WebP ფორმატი JPEG-ზე პატარა ფაილებს აწარმოებს, მაგრამ მის დამზადებას უფრო მეტი დრო სჭირდება.", "image_fullsize_title": "სრული ზომის გამოსახულების პარამეტრები", "image_prefer_wide_gamut": "უპირატესობა მიენიჭოს ფერის ფართე დიაპაზონს", + "image_preview_title": "გამოსახულების გადახედვის პარამეტრები", "image_quality": "ხარისხი", "image_resolution": "გაფართოება", "image_settings": "გამოსახულების პარამეტრები", @@ -68,6 +74,7 @@ "library_created": "შეიქმნა ბიბლიოთეკა: {library}", "library_deleted": "ბიბლიოთეკა წაიშალა", "library_import_path_description": "აირჩიე დასაიმპორტებელი საქაღალდე. ფოტოები და ვიდეოები მოიძებნება ამ საქაღალდესა და მასში არსებულ საქაღალდეებში.", + "library_settings": "გარე ბიბლიოთეკა", "library_settings_description": "გარე ბიბლიოთეკების პარამეტრების მართვა", "logging_settings": "ჟურნალი", "map_settings": "რუკა", @@ -125,7 +132,6 @@ "duplicates": "დუბლიკატები", "duration": "ხანგრძლივობა", "edit": "ჩასწორება", - "edited": "ჩასწორებულია", "editor": "რედაქტორი", "editor_crop_tool_h2_rotation": "ტრიალი", "email": "ელფოსტა", diff --git a/i18n/kn.json b/i18n/kn.json index 388f06704c..111c802a1e 100644 --- a/i18n/kn.json +++ b/i18n/kn.json @@ -8,11 +8,13 @@ "actions": "ಕ್ರಿಯೆಗಳು", "active": "ಸಕ್ರಿಯ", "activity": "ಚಟುವಟಿಕೆ", + "activity_changed": "ಚಟುವಟಿಕೆ {enabled, select, true{ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ} other {ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ}}", "add": "ಸೇರಿಸಿ", "add_a_description": "ವಿವರಣೆಯನ್ನು ಸೇರಿಸಿ", "add_a_location": "ಸ್ಥಳವನ್ನು ಸೇರಿಸಿ", "add_a_name": "ಹೆಸರನ್ನು ಸೇರಿಸಿ", "add_a_title": "ಶೀರ್ಷಿಕೆಯನ್ನು ಸೇರಿಸಿ", + "add_birthday": "ಜನ್ಮದಿನ ಸೇರಿಸಿ", "add_endpoint": "ಎಂಡ್‌ಪಾಯಿಂಟ್ ಸೇರಿಸಿ", "add_exclusion_pattern": "ಹೊರಗಿಡುವಿಕೆ ಮಾದರಿಯನ್ನು ಸೇರಿಸಿ", "add_import_path": "ಆಮದು ಮಾರ್ಗವನ್ನು ಸೇರಿಸಿ", @@ -22,5 +24,6 @@ "add_path": "ಹಾದಿಯನ್ನು ಸೇರಿಸಿ", "add_photos": "ಫೋಟೋಗಳನ್ನು ಸೇರಿಸಿ", "add_to": "ಸೇರಿಸಿ…", - "add_to_album": "ಆಲ್ಬಮ್‌ಗೆ ಸೇರಿಸಿ" + "add_to_album": "ಆಲ್ಬಮ್‌ಗೆ ಸೇರಿಸಿ", + "add_to_album_bottom_sheet_added": "{album}ಗೆ ಸೇರಿಸಿದೆ" } diff --git a/i18n/ko.json b/i18n/ko.json index 815f1b2b2f..8a4094c4b0 100644 --- a/i18n/ko.json +++ b/i18n/ko.json @@ -28,10 +28,12 @@ "add_to_album": "앨범에 추가", "add_to_album_bottom_sheet_added": "{album}에 추가됨", "add_to_album_bottom_sheet_already_exists": "이미 {album}에 있음", + "add_to_album_bottom_sheet_some_local_assets": "일부 로컬 항목이 앨범에 추가되지 않았습니다.", "add_to_album_toggle": "{album} 선택/해제", "add_to_albums": "여러 앨범에 추가", "add_to_albums_count": "여러 앨범에 추가 ({count})", "add_to_shared_album": "공유 앨범에 추가", + "add_upload_to_stack": "스택에 업로드 추가", "add_url": "URL 추가", "added_to_archive": "보관함으로 이동되었습니다.", "added_to_favorites": "즐겨찾기에 추가되었습니다.", @@ -123,6 +125,13 @@ "logging_enable_description": "로그 기록 활성화", "logging_level_description": "활성화 시 사용할 로그 레벨을 선택합니다.", "logging_settings": "로깅", + "machine_learning_availability_checks": "가용성 확인", + "machine_learning_availability_checks_description": "사용 가능한 기계 학습 서버를 자동으로 감지하고 우선적으로 선택합니다.", + "machine_learning_availability_checks_enabled": "가용성 확인 활성화", + "machine_learning_availability_checks_interval": "확인 주기", + "machine_learning_availability_checks_interval_description": "가용성 확인 주기 (밀리초 단위)", + "machine_learning_availability_checks_timeout": "요청 타임아웃", + "machine_learning_availability_checks_timeout_description": "가용성 확인 요청 타임아웃 (밀리초 단위)", "machine_learning_clip_model": "CLIP 모델", "machine_learning_clip_model_description": "CLIP 모델의 종류는 이곳을 참조하세요. 한국어 등 여러 언어로 검색하려면 Multilingual CLIP 모델을 선택하세요. 모델을 변경한 경우 모든 이미지의 '스마트 검색' 작업을 다시 실행해야 합니다.", "machine_learning_duplicate_detection": "비슷한 항목 감지", @@ -145,6 +154,10 @@ "machine_learning_min_detection_score_description": "감지된 얼굴의 최소 신뢰도 점수를 0에서 1 사이로 설정합니다. 값을 낮추면 더 많은 얼굴을 감지하지만 잘못 감지될 가능성도 높아집니다.", "machine_learning_min_recognized_faces": "최소 인식 얼굴", "machine_learning_min_recognized_faces_description": "인물을 생성하기 위해 인식할 얼굴 수의 최솟값을 설정합니다. 값이 높으면 얼굴 인식이 정확해지지만 감지된 얼굴이 인물에 할당되지 않을 가능성이 증가합니다.", + "machine_learning_ocr": "OCR", + "machine_learning_ocr_description": "기계 학습으로 이미지에서 텍스트를 인식합니다.", + "machine_learning_ocr_enabled": "OCR 활성화", + "machine_learning_ocr_min_detection_score": "최소 신뢰도 점수", "machine_learning_settings": "기계 학습 설정", "machine_learning_settings_description": "기계 학습 시 사용할 모델과 세부 설정을 관리합니다.", "machine_learning_smart_search": "스마트 검색", @@ -202,6 +215,8 @@ "notification_email_ignore_certificate_errors_description": "TLS 인증서 유효성 검사 오류 무시 (권장되지 않음)", "notification_email_password_description": "이메일 서버 인증 시 사용할 비밀번호", "notification_email_port_description": "이메일 서버 포트 (예: 25, 465 또는 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "SMTPS 사용 (SMTP over TLS)", "notification_email_sent_test_email_button": "테스트 이메일 전송 및 저장", "notification_email_setting_description": "이메일 알림 전송 설정", "notification_email_test_email": "테스트 이메일 전송", @@ -324,7 +339,7 @@ "transcoding_max_b_frames": "최대 B-프레임", "transcoding_max_b_frames_description": "값을 높이면 압축 효율이 향상되지만 인코딩 속도가 느려집니다. 오래된 장치의 하드웨어 가속과 호환되지 않을 수 있습니다. 0을 입력하면 B-프레임을 비활성화하고, -1을 입력하면 자동으로 설정합니다.", "transcoding_max_bitrate": "최대 비트레이트", - "transcoding_max_bitrate_description": "최대 비트레이트를 지정하면 파일 크기를 일정하게 조절할 수 있지만 품질이 다소 저하될 수 있습니다. 일반적으로 720p 기준 VP9와 HEVC는 2600kbit/s를, H.264는 4500kbit/s를 사용합니다. 0을 입력하면 비활성화됩니다.", + "transcoding_max_bitrate_description": "최대 비트레이트를 지정하면 파일 크기가 예측 가능해지지만 품질이 다소 저하될 수 있습니다. 일반적으로 720p 해상도에서는 VP9, HEVC가 2600kbit/s, H.264는 4500kbit/s를 사용하며, 0으로 설정하면 비활성화됩니다. 단위를 생략하면 k(kbit/s)로 간주되며 5000, 5000k, 5M(Mbit/s)은 같은 값으로 처리됩니다.", "transcoding_max_keyframe_interval": "최대 키프레임 간격", "transcoding_max_keyframe_interval_description": "키프레임 간 최대 프레임 간격을 설정합니다. 값을 낮추면 압축 효율은 떨어지지만 탐색 속도가 빨라지고 움직임이 많은 장면에서 품질이 향상될 수 있습니다. 0을 입력하면 자동으로 설정합니다.", "transcoding_optimal_description": "목표 해상도를 초과하거나 허용되지 않은 포맷의 동영상", @@ -342,7 +357,7 @@ "transcoding_target_resolution": "목표 해상도", "transcoding_target_resolution_description": "해상도를 높이면 세부 정보가 더 많이 보존되지만, 인코딩 시간이 늘어나고 파일 크기가 커져 앱 반응 속도가 느려질 수 있습니다.", "transcoding_temporal_aq": "Temporal AQ", - "transcoding_temporal_aq_description": "(NVENC인 경우) 디테일이 많고 정적인 장면의 품질이 향상됩니다. 오래된 기기에서 호환되지 않을 수 있습니다.", + "transcoding_temporal_aq_description": "NVENC에만 적용됩니다. Temporal Adaptive Quantization은 디테일이 많고 정적인 장면의 품질이 향상됩니다. 오래된 기기에서 호환되지 않을 수 있습니다.", "transcoding_threads": "스레드 수", "transcoding_threads_description": "값을 높이면 인코딩 속도가 빨라지지만, 서버가 다른 작업을 처리할 여유가 줄어듭니다. 입력한 값은 CPU 코어 수를 초과하지 않아야 하며, 0으로 설정하면 CPU를 최대한 활용합니다.", "transcoding_tone_mapping": "톤 매핑", @@ -393,10 +408,11 @@ "advanced_settings_prefer_remote_subtitle": "일부 기기의 경우 로컬 항목에서 섬네일을 로드하는 속도가 매우 느립니다. 서버 이미지를 대신 로드하려면 이 설정을 활성화하세요.", "advanced_settings_prefer_remote_title": "서버 이미지 선호", "advanced_settings_proxy_headers_subtitle": "Immich가 네트워크 요청 시 사용할 프록시 헤더를 정의합니다.", - "advanced_settings_proxy_headers_title": "프록시 헤더", + "advanced_settings_proxy_headers_title": "커스텀 프록시 헤더 (실험적)", + "advanced_settings_readonly_mode_subtitle": "읽기 전용 모드를 활성화하면 이미지 선택, 공유, 캐스트, 삭제 등의 동작이 비활성화됩니다. 메인 화면에서 사용자 아이콘을 길게 눌러 읽기 전용 모드를 활성화/비활성화하세요.", "advanced_settings_readonly_mode_title": "읽기 전용 모드", "advanced_settings_self_signed_ssl_subtitle": "서버 엔드포인트의 SSL 인증서 검증을 건너뜁니다. 자체 서명 인증서를 사용하는 경우 활성화하세요.", - "advanced_settings_self_signed_ssl_title": "자체 서명된 SSL 인증서 허용", + "advanced_settings_self_signed_ssl_title": "자체 서명된 SSL 인증서 허용 (실험적)", "advanced_settings_sync_remote_deletions_subtitle": "웹에서 삭제하거나 복원한 항목을 이 기기에서도 자동으로 처리하도록 설정", "advanced_settings_sync_remote_deletions_title": "원격 삭제 동기화 (실험적)", "advanced_settings_tile_subtitle": "고급 사용자 설정", @@ -422,6 +438,7 @@ "album_remove_user_confirmation": "{user}님을 앨범에서 제거하시겠습니까?", "album_search_not_found": "검색 결과에 해당하는 앨범이 없습니다.", "album_share_no_users": "이미 모든 사용자와 앨범을 공유했거나 공유할 사용자가 없습니다.", + "album_summary": "앨범 요약", "album_updated": "항목 추가 알림", "album_updated_setting_description": "공유 앨범에 항목이 추가된 경우 이메일 알림 받기", "album_user_left": "{album} 앨범에서 나옴", @@ -455,10 +472,14 @@ "api_key_description": "이 값은 한 번만 표시됩니다. 창을 닫기 전 반드시 복사해주세요.", "api_key_empty": "API 키 이름은 비워둘 수 없습니다.", "api_keys": "API 키", + "app_architecture_variant": "변형 (아키텍처)", "app_bar_signout_dialog_content": "정말 로그아웃하시겠습니까?", "app_bar_signout_dialog_ok": "네", "app_bar_signout_dialog_title": "로그아웃", + "app_download_links": "앱 다운로드 링크", "app_settings": "앱 설정", + "app_stores": "앱 스토어", + "app_update_available": "앱 업데이트 가능", "appears_in": "다음 앨범에 포함됨", "apply_count": "적용 ({count, number})", "archive": "보관함", @@ -493,6 +514,8 @@ "asset_restored_successfully": "항목이 복원되었습니다.", "asset_skipped": "건너뜀", "asset_skipped_in_trash": "휴지통의 항목", + "asset_trashed": "항목 삭제됨", + "asset_troubleshoot": "항목 트러블슈팅", "asset_uploaded": "업로드 완료", "asset_uploading": "업로드 중…", "asset_viewer_settings_subtitle": "갤러리 보기 설정을 관리합니다.", @@ -526,8 +549,10 @@ "autoplay_slideshow": "슬라이드 쇼 자동 재생", "back": "뒤로", "back_close_deselect": "뒤로, 닫기 또는 선택 해제", + "background_backup_running_error": "백그라운드 백업이 진행 중입니다. 수동 백업을 시작할 수 없습니다.", "background_location_permission": "백그라운드 위치 권한", "background_location_permission_content": "Immich가 백그라운드에서 실행 중일 때 네트워크를 전환하려면 Wi-Fi 네트워크 이름을 확인해야 하며, 이를 위해 '정확한 위치' 권한을 항상 허용해야 합니다.", + "background_options": "백그라운드 옵션", "backup": "백업", "backup_album_selection_page_albums_device": "기기의 앨범 ({count})", "backup_album_selection_page_albums_tap": "탭하여 포함, 두 번 탭하여 제외", @@ -535,8 +560,10 @@ "backup_album_selection_page_select_albums": "앨범 선택", "backup_album_selection_page_selection_info": "선택한 앨범", "backup_album_selection_page_total_assets": "전체 항목", + "backup_albums_sync": "앨범 동기화 백업", "backup_all": "모두", "backup_background_service_backup_failed_message": "항목 백업에 실패했습니다. 다시 시도하는 중…", + "backup_background_service_complete_notification": "항목 백업 완료", "backup_background_service_connection_failed_message": "서버 연결에 실패했습니다. 다시 시도하는 중…", "backup_background_service_current_upload_notification": "{filename} 업로드 중", "backup_background_service_default_notification": "새로운 항목을 확인하는 중…", @@ -584,6 +611,7 @@ "backup_controller_page_turn_on": "활성화", "backup_controller_page_uploading_file_info": "파일 정보 업로드 중", "backup_err_only_album": "유일한 앨범은 삭제할 수 없습니다.", + "backup_error_sync_failed": "동기화에 실패했습니다. 백업을 진행할 수 없습니다.", "backup_info_card_assets": "항목", "backup_manual_cancelled": "취소됨", "backup_manual_in_progress": "업로드가 이미 진행 중입니다. 잠시 후 다시 시도하세요", @@ -651,6 +679,8 @@ "change_pin_code": "PIN 코드 변경", "change_your_password": "사용자 계정의 비밀번호를 변경합니다.", "changed_visibility_successfully": "숨김 여부가 변경되었습니다.", + "charging": "충전 중", + "charging_requirement_mobile_backup": "백그라운드 백업은 기기가 충전 중일 때 진행됩니다.", "check_corrupt_asset_backup": "백업된 항목의 손상 여부 확인", "check_corrupt_asset_backup_button": "확인 수행", "check_corrupt_asset_backup_description": "이 검사는 모든 항목이 백업된 후 Wi-Fi가 연결된 상태에서만 실행하세요. 이 작업은 몇 분 정도 소요될 수 있습니다.", @@ -670,7 +700,7 @@ "client_cert_invalid_msg": "인증서가 유효하지 않거나 비밀번호가 올바르지 않음", "client_cert_remove_msg": "클라이언트 인증서 제거됨", "client_cert_subtitle": "인증서 가져오기/제거는 로그인 전에만 가능하며, PKCS12 (.p12, .pfx) 형식만 지원합니다.", - "client_cert_title": "SSL 클라이언트 인증서", + "client_cert_title": "SSL 클라이언트 인증서 (실험적)", "clockwise": "시계 방향", "close": "닫기", "collapse": "접기", @@ -682,7 +712,6 @@ "comments_and_likes": "댓글 및 좋아요", "comments_are_disabled": "댓글이 비활성화되었습니다.", "common_create_new_album": "앨범 생성", - "common_server_error": "네트워크 연결 상태를 확인하고, 서버에 접속할 수 있는지, 앱/서버 버전이 호환되는지 확인해주세요.", "completed": "완료됨", "confirm": "확인", "confirm_admin_password": "관리자 비밀번호 확인", @@ -721,6 +750,7 @@ "create": "생성", "create_album": "앨범 생성", "create_album_page_untitled": "제목 없음", + "create_api_key": "API 키 생성", "create_library": "새 라이브러리", "create_link": "링크 생성", "create_link_to_share": "공유 링크 생성", @@ -737,6 +767,7 @@ "create_user": "사용자 계정 생성", "created": "생성됨", "created_at": "생성됨", + "creating_linked_albums": "연결된 앨범 생성 중...", "crop": "자르기", "curated_object_page_title": "사물", "current_device": "현재 기기", @@ -863,7 +894,6 @@ "edit_tag": "태그 수정", "edit_title": "제목 변경", "edit_user": "사용자 수정", - "edited": "수정되었습니다.", "editor": "편집자", "editor_close_without_save_prompt": "변경 사항이 저장되지 않습니다.", "editor_close_without_save_title": "편집을 종료하시겠습니까?", @@ -886,7 +916,9 @@ "error": "오류", "error_change_sort_album": "앨범 표시 순서 변경 실패", "error_delete_face": "항목에서 얼굴 삭제 중 오류 발생", + "error_getting_places": "장소 로드 오류", "error_loading_image": "이미지를 불러오는 중 오류 발생", + "error_loading_partners": "파트너 불러오기 실패: {error}", "error_saving_image": "오류: {error}", "error_tag_face_bounding_box": "얼굴 태그 실패 - 얼굴의 위치를 가져올 수 없습니다.", "error_title": "오류 - 문제가 발생했습니다", @@ -1017,6 +1049,7 @@ "exif_bottom_sheet_description_error": "설명 변경 중 오류 발생", "exif_bottom_sheet_details": "상세 정보", "exif_bottom_sheet_location": "위치", + "exif_bottom_sheet_no_description": "설명 없음", "exif_bottom_sheet_people": "인물", "exif_bottom_sheet_person_add_person": "이름 추가", "exit_slideshow": "슬라이드 쇼 종료", @@ -1051,6 +1084,7 @@ "favorites_page_no_favorites": "즐겨찾기된 항목 없음", "feature_photo_updated": "대표 사진 업데이트됨", "features": "기능", + "features_in_development": "개발 중인 기능", "features_setting_description": "사진 및 동영상 관리 기능을 설정합니다.", "file_name": "파일 이름", "file_name_or_extension": "파일명 또는 확장자", @@ -1069,8 +1103,9 @@ "forgot_pin_code_question": "PIN 번호를 잊어버렸나요?", "forward": "앞으로", "gcast_enabled": "구글 캐스트", - "gcast_enabled_description": "이 기능은 Google의 외부 리소스를 사용하여 실행됩니다.", + "gcast_enabled_description": "이 기능은 Google의 외부 리소스를 사용합니다.", "general": "일반", + "geolocation_instruction_location": "GPS 좌표가 포함된 항목을 클릭해 위치를 사용하거나, 지도에서 직접 위치를 선택하세요.", "get_help": "도움 얻기", "get_wifiname_error": "Wi-Fi 이름을 가져올 수 없습니다. 필수 권한이 부여되었는지, Wi-Fi 네트워크에 연결되어 있는지 확인하세요.", "getting_started": "시작하기", @@ -1096,7 +1131,6 @@ "header_settings_field_validator_msg": "값은 비워둘 수 없습니다.", "header_settings_header_name_input": "헤더 이름", "header_settings_header_value_input": "헤더 값", - "headers_settings_tile_subtitle": "네트워크 요청 전송에 포함할 프록시 헤더를 정의합니다.", "headers_settings_tile_title": "사용자 지정 프록시 헤더", "hi_user": "안녕하세요 {name}님, ({email})", "hide_all_people": "모든 인물 숨기기", @@ -1214,6 +1248,7 @@ "local": "로컬", "local_asset_cast_failed": "서버에 업로드되지 않은 항목을 캐스팅할 수 없음", "local_assets": "로컬 항목", + "local_media_summary": "로컬 미디어 요약", "local_network": "로컬 네트워크", "local_network_sheet_info": "지정된 Wi-Fi를 사용할 때 앱이 아래 URL로 서버에 연결합니다.", "location_permission": "위치 권한", @@ -1225,6 +1260,7 @@ "location_picker_longitude_hint": "여기에 경도를 입력하세요", "lock": "잠금", "locked_folder": "잠금 폴더", + "log_detail_title": "상세 로그", "log_out": "로그아웃", "log_out_all_devices": "모든 기기에서 로그아웃", "logged_in_as": "{user}로 로그인됨", @@ -1255,6 +1291,7 @@ "login_password_changed_success": "비밀번호가 변경되었습니다.", "logout_all_device_confirmation": "모든 기기에서 로그아웃하시겠습니까?", "logout_this_device_confirmation": "이 기기에서 로그아웃하시겠습니까?", + "logs": "로그", "longitude": "경도", "look": "보기", "loop_videos": "동영상 반복", @@ -1297,6 +1334,7 @@ "mark_as_read": "읽음으로 표시", "marked_all_as_read": "모두 읽음으로 표시했습니다.", "matches": "일치", + "matching_assets": "일치하는 항목", "media_type": "미디어 종류", "memories": "추억", "memories_all_caught_up": "모두 확인함", @@ -1317,6 +1355,8 @@ "minute": "분", "minutes": "분", "missing": "누락", + "mobile_app": "모바일 앱", + "mobile_app_download_onboarding_note": "다음 옵션 중 하나를 사용해 모바일 앱을 다운로드하세요.", "model": "모델", "month": "월", "monthly_title_text_date_format": "yyyy년 M월", @@ -1335,18 +1375,23 @@ "my_albums": "내 앨범", "name": "이름", "name_or_nickname": "이름 또는 닉네임", + "navigate": "탐색", + "navigate_to_time": "시간으로 탐색", "network_requirement_photos_upload": "사진 백업에 모바일 데이터 사용", "network_requirement_videos_upload": "동영상 백업에 모바일 데이터 사용", + "network_requirements": "네트워크 요구사항", "network_requirements_updated": "네트워크 상태가 변경되었습니다. 백업 대기열을 초기화합니다.", "networking_settings": "연결", "networking_subtitle": "서버 엔드포인트 설정을 관리합니다.", "never": "없음", "new_album": "새 앨범", "new_api_key": "새 API 키", + "new_date_range": "새 날짜 범위", "new_password": "새 비밀번호", "new_person": "새 인물 생성", "new_pin_code": "새 PIN 코드", "new_pin_code_subtitle": "잠금 폴더에 처음 접근하셨습니다. 이곳에 안전하게 접근하기 위한 PIN 코드를 설정하세요.", + "new_timeline": "새 타임라인", "new_user_created": "사용자 계정이 생성되었습니다.", "new_version_available": "새 버전 사용 가능", "newest_first": "최신순", @@ -1360,20 +1405,25 @@ "no_assets_message": "여기를 클릭해 첫 사진을 업로드하세요.", "no_assets_to_show": "표시할 항목 없음", "no_cast_devices_found": "캐스트 기기 없음", + "no_checksum_local": "체크섬이 없습니다. 로컬 항목을 불러올 수 없습니다.", + "no_checksum_remote": "체크섬이 없습니다. 원격 항목을 불러올 수 없습니다.", "no_duplicates_found": "비슷한 항목이 없습니다.", "no_exif_info_available": "EXIF 정보 없음", "no_explore_results_message": "더 많은 사진을 업로드하여 탐색 기능을 사용하세요.", "no_favorites_message": "즐겨찾기에서 사진과 동영상을 빠르게 찾기", "no_libraries_message": "외부 라이브러리로 다른 경로의 사진과 동영상을 확인하세요.", + "no_local_assets_found": "체크섬과 일치하는 로컬 항목을 찾을 수 없습니다.", "no_locked_photos_message": "잠금 폴더의 사진 및 동영상은 숨겨지며 라이브러리를 탐색할 때 표시되지 않습니다.", "no_name": "이름 없음", "no_notifications": "알림 없음", "no_people_found": "일치하는 인물 없음", "no_places": "장소 없음", + "no_remote_assets_found": "체크섬과 일치하는 원격 항목을 찾을 수 없습니다.", "no_results": "결과 없음", "no_results_description": "동의어 또는 더 일반적인 단어를 사용해 보세요.", "no_shared_albums_message": "앨범을 만들어 주변 사람들과 사진 및 동영상을 공유하세요.", "no_uploads_in_progress": "진행 중인 업로드 없음", + "not_available": "없음", "not_in_any_album": "앨범에 없음", "not_selected": "선택되지 않음", "note_apply_storage_label_to_previously_uploaded assets": "참고: 이전에 업로드한 항목에도 스토리지 레이블을 적용하려면 다음을 실행합니다,", @@ -1387,6 +1437,8 @@ "notifications": "알림", "notifications_setting_description": "알림 전송 설정을 관리합니다.", "oauth": "OAuth", + "obtainium_configurator": "Obtainium 구성", + "obtainium_configurator_instructions": "Obtainium으로 Immich GitHub 릴리스에서 직접 안드로이드 앱을 설치하고 업데이트하세요. API 키를 생성하고 변형을 선택해 Obtanium 설정 링크를 생성하세요.", "official_immich_resources": "Immich 공식 리소스", "offline": "오프라인", "offset": "오프셋", @@ -1398,8 +1450,8 @@ "onboarding_privacy_description": "다음 선택적 기능은 외부 서비스를 사용하며 설정에서 언제든 비활성화할 수 있습니다.", "onboarding_server_welcome_description": "몇 가지 일반적인 설정을 진행하겠습니다.", "onboarding_theme_description": "사용할 테마를 선택하세요. 설정에서 언제든 변경할 수 있습니다.", - "onboarding_user_welcome_description": "시작해 보겠습니다!", - "onboarding_welcome_user": "{user}님, 환영합니다", + "onboarding_user_welcome_description": "기본 설정을 시작하겠습니다!", + "onboarding_welcome_user": "환영합니다, {user}님.", "online": "온라인", "only_favorites": "즐겨찾기만", "open": "열기", @@ -1408,6 +1460,8 @@ "open_the_search_filters": "검색 필터 열기", "options": "옵션", "or": "또는", + "organize_into_albums": "앨범으로 정리하기", + "organize_into_albums_description": "현재 동기화 설정을 사용하여 기존 사진을 앨범으로 정리합니다", "organize_your_library": "라이브러리 정리", "original": "원본", "other": "기타", @@ -1489,10 +1543,14 @@ "play_memories": "추억 재생", "play_motion_photo": "모션 포토 재생", "play_or_pause_video": "동영상 재생/일시 정지", + "play_original_video": "원본 동영상 재생", + "play_original_video_setting_description": "트랜스코딩된 영상보다 원본 영상을 우선 재생합니다. 원본이 호환되지 않는 형식인 경우 정상적으로 재생되지 않을 수 있습니다.", + "play_transcoded_video": "트랜스코딩 동영상 재생", "please_auth_to_access": "계속 진행하려면 인증하세요.", "port": "포트", "preferences_settings_subtitle": "앱 개인 설정을 관리합니다.", "preferences_settings_title": "개인 설정", + "preparing": "준비 중", "preset": "프리셋", "preview": "미리 보기", "previous": "이전", @@ -1505,12 +1563,9 @@ "privacy": "개인정보", "profile": "프로필", "profile_drawer_app_logs": "로그", - "profile_drawer_client_out_of_date_major": "모바일 앱이 최신 버전이 아닙니다. 최신 버전으로 업데이트하세요.", - "profile_drawer_client_out_of_date_minor": "모바일 앱이 최신 버전이 아닙니다. 최신 버전으로 업데이트하세요.", "profile_drawer_client_server_up_to_date": "클라이언트와 서버가 최신 상태입니다.", "profile_drawer_github": "Github", - "profile_drawer_server_out_of_date_major": "서버 버전이 최신이 아닙니다. 최신 버전으로 업데이트하세요.", - "profile_drawer_server_out_of_date_minor": "서버 버전이 최신이 아닙니다. 최신 버전으로 업데이트하세요.", + "profile_drawer_readonly_mode": "읽기 전용 모드 활성화. 사용자 아이콘을 길게 눌러 해제할 수 있습니다.", "profile_image_of_user": "{user}님의 프로필 이미지", "profile_picture_set": "프로필 사진이 설정되었습니다.", "public_album": "공개 앨범", @@ -1547,6 +1602,7 @@ "purchase_server_description_2": "서포터 배지", "purchase_server_title": "서버", "purchase_settings_server_activated": "서버 제품 키는 관리자가 제어합니다.", + "query_asset_id": "쿼리 항목 ID", "queue_status": "전체 {total}, {count} 대기 중", "rating": "등급", "rating_clear": "등급 초기화", @@ -1554,6 +1610,9 @@ "rating_description": "상세 정보 패널에 EXIF 등급 태그 표시", "reaction_options": "반응 옵션", "read_changelog": "변경 내역 보기", + "readonly_mode_disabled": "읽기 전용 모드 비활성화", + "readonly_mode_enabled": "읽기 전용 모드 활성화", + "ready_for_upload": "업로드 준비 완료", "reassign": "다시 할당", "reassigned_assets_to_existing_person": "{count, plural, one {항목 #개} other {항목 #개}}를 {name, select, null {기존 인물} other {기존 인물 {name}}}에게 재지정했습니다.", "reassigned_assets_to_new_person": "{count, plural, one {항목 #개} other {항목 #개}}를 새 인물에게 재지정했습니다.", @@ -1578,6 +1637,7 @@ "regenerating_thumbnails": "섬네일을 다시 생성하는 중...", "remote": "원격", "remote_assets": "원격 항목", + "remote_media_summary": "원격 미디어 요약", "remove": "제거", "remove_assets_album_confirmation": "앨범에서 항목 {count, plural, one {#개} other {#개}}를 제거하시겠습니까?", "remove_assets_shared_link_confirmation": "공유 링크에서 항목 {count, plural, one {#개} other {#개}}를 제거하시겠습니까?", @@ -1630,6 +1690,7 @@ "restore_user": "사용자 복원", "restored_asset": "항목이 복원되었습니다.", "resume": "재개", + "resume_paused_jobs": "일시 중지된 작업 {count, plural, one {#개} other {#개}} 재개", "retry_upload": "다시 시도", "review_duplicates": "비슷한 항목 확인", "review_large_files": "용량이 큰 파일 확인", @@ -1650,11 +1711,12 @@ "scanning_for_album": "앨범을 스캔하는 중...", "search": "검색", "search_albums": "앨범 검색", - "search_by_context": "문맥으로 검색", + "search_by_context": "문맥 기반 검색", "search_by_description": "설명으로 검색", "search_by_description_example": "동해안에서 맞이한 새해 일출", "search_by_filename": "파일명 또는 확장자로 검색", "search_by_filename_example": "예: IMG_1234.JPG 또는 PNG", + "search_camera_lens_model": "렌즈 모델 검색...", "search_camera_make": "카메라 제조사 검색...", "search_camera_model": "카메라 모델명 검색...", "search_city": "도시 검색...", @@ -1671,6 +1733,7 @@ "search_filter_location_title": "위치 선택", "search_filter_media_type": "미디어 종류", "search_filter_media_type_title": "미디어 종류 선택", + "search_filter_ocr": "OCR 검색", "search_filter_people_title": "인물 선택", "search_for": "검색", "search_for_existing_person": "존재하는 인물 검색", @@ -1723,6 +1786,7 @@ "select_user_for_sharing_page_err_album": "앨범을 생성하지 못했습니다.", "selected": "선택됨", "selected_count": "{count, plural, other {#개 선택됨}}", + "selected_gps_coordinates": "선택한 GPS 좌표", "send_message": "메시지 전송", "send_welcome_email": "환영 이메일 전송", "server_endpoint": "서버 엔드포인트", @@ -1732,6 +1796,7 @@ "server_online": "온라인", "server_privacy": "개인정보", "server_stats": "서버 통계", + "server_update_available": "서버 업데이트 가능", "server_version": "서버 버전", "set": "설정", "set_as_album_cover": "앨범 커버로 설정", @@ -1744,7 +1809,7 @@ "setting_image_viewer_help": "상세 보기에서는 작은 섬네일, (활성화된 경우) 중간 섬네일, 원본 순으로 불러옵니다.", "setting_image_viewer_original_subtitle": "원본 고해상도 이미지를 불러옵니다. 데이터 사용량 및 캐시 크기를 줄이려면 비활성화하세요.", "setting_image_viewer_original_title": "원본 이미지 로드", - "setting_image_viewer_preview_subtitle": "원본 고해상도 이미지를 불러옵니다. 비활성화하는 경우 원본 또는 섬네일만 불러옵니다.", + "setting_image_viewer_preview_subtitle": "중간 해상도 이미지를 불러옵니다. 비활성화하는 경우 원본 또는 섬네일만 불러옵니다.", "setting_image_viewer_preview_title": "미리보기 이미지 로드", "setting_image_viewer_title": "이미지", "setting_languages_apply": "적용", @@ -1760,6 +1825,8 @@ "setting_notifications_subtitle": "알림 기본 설정 조정", "setting_notifications_total_progress_subtitle": "전체 업로드 진행률 (완료/총 항목)", "setting_notifications_total_progress_title": "백그라운드 백업 전체 진행률 표시", + "setting_video_viewer_auto_play_subtitle": "동영상을 열면 자동으로 재생", + "setting_video_viewer_auto_play_title": "동영상 자동 재생", "setting_video_viewer_looping_title": "반복", "setting_video_viewer_original_video_subtitle": "동영상 스트리밍 시 트랜스코딩된 파일 대신 원본을 재생합니다. 재생 시 버퍼링이 발생할 수 있습니다. 로컬에 있는 영상은 항상 원본 화질로 재생됩니다.", "setting_video_viewer_original_video_title": "원본 동영상 강제 사용", @@ -1851,6 +1918,7 @@ "show_slideshow_transition": "슬라이드 전환 표시", "show_supporter_badge": "서포터 배지", "show_supporter_badge_description": "서포터 배지 표시", + "show_text_search_menu": "텍스트 검색 메뉴 표시", "shuffle": "셔플", "sidebar": "사이드바", "sidebar_display_description": "보기 링크를 사이드바에 표시", @@ -1881,6 +1949,7 @@ "stacktrace": "스택 추적", "start": "시작", "start_date": "시작일", + "start_date_before_end_date": "시작일은 종료일보다 이전이어야 합니다", "state": "지역", "status": "상태", "stop_casting": "캐스팅 중단", @@ -1905,6 +1974,8 @@ "sync_albums_manual_subtitle": "업로드한 모든 동영상과 사진을 선택한 백업 앨범에 동기화", "sync_local": "로컬 동기화", "sync_remote": "원격 동기화", + "sync_status": "동기화 상태", + "sync_status_subtitle": "동기화 시스템 확인 및 관리", "sync_upload_album_setting_subtitle": "선택한 앨범을 Immich에 생성하고 사진 및 동영상 업로드", "tag": "태그", "tag_assets": "항목 태그", @@ -1964,8 +2035,10 @@ "trash_page_select_assets_btn": "항목 선택", "trash_page_title": "휴지통 ({count})", "trashed_items_will_be_permanently_deleted_after": "휴지통으로 이동된 항목은 {days, plural, one {#일} other {#일}} 후 영구적으로 삭제됩니다.", + "troubleshoot": "문제 해결", "type": "형식", "unable_to_change_pin_code": "PIN 코드를 변경할 수 없음", + "unable_to_check_version": "앱 또는 서버 버전을 확인할 수 없음", "unable_to_setup_pin_code": "PIN 코드를 설정할 수 없음", "unarchive": "보관함에서 제거", "unarchive_action_prompt": "보관함에서 항목 {count}개 제거됨", @@ -1994,6 +2067,7 @@ "unstacked_assets_count": "항목 {count, plural, one {#개} other {#개}}의 스택을 풀었습니다.", "untagged": "태그 해제됨", "up_next": "다음", + "update_location_action_prompt": "선택한 {count}개 항목 위치 업데이트:", "updated_at": "업데이트됨", "updated_password": "비밀번호가 변경되었습니다.", "upload": "업로드", @@ -2060,6 +2134,7 @@ "view_next_asset": "다음 항목 보기", "view_previous_asset": "이전 항목 보기", "view_qr_code": "QR 코드 보기", + "view_similar_photos": "비슷한 사진 보기", "view_stack": "스택 보기", "view_user": "사용자 보기", "viewer_remove_from_stack": "스택에서 제거", @@ -2078,5 +2153,6 @@ "yes": "네", "you_dont_have_any_shared_links": "공유 링크가 없습니다.", "your_wifi_name": "Wi-Fi 네트워크 이름", - "zoom_image": "이미지 확대" + "zoom_image": "이미지 확대", + "zoom_to_bounds": "화면에 맞춰 확대" } diff --git a/i18n/lt.json b/i18n/lt.json index c66b57cfa0..56f8333bef 100644 --- a/i18n/lt.json +++ b/i18n/lt.json @@ -123,6 +123,13 @@ "logging_enable_description": "Įjungti žurnalo vedimą", "logging_level_description": "Įjungus, kokį žurnalo vedimo lygį naudot.", "logging_settings": "Žurnalo vedimas", + "machine_learning_availability_checks": "Prieinamumo patikrinimai", + "machine_learning_availability_checks_description": "Automatiškai aptikti ir teikti pirmenybę prieinamiems mašininio mokymosi serveriams", + "machine_learning_availability_checks_enabled": "Įjungti prieinamumo patikrinimus", + "machine_learning_availability_checks_interval": "Patikros intervalas", + "machine_learning_availability_checks_interval_description": "Intervalas milisekundėmis tarp prieinamumo patikrinimų", + "machine_learning_availability_checks_timeout": "Užklausos laiko limitas", + "machine_learning_availability_checks_timeout_description": "Laiko limitas milisekundėmis prieinamumo patikrinimams", "machine_learning_clip_model": "CLIP modelis", "machine_learning_clip_model_description": "Pavadinimas CLIP modelio įvardintio here. Dėmesio, keičiant modelį jūs privalote iš naujo paleisti 'Išmaniosios Paieškos' užduotį visiems vaizdams.", "machine_learning_duplicate_detection": "Dublikatų aptikimas", @@ -264,8 +271,8 @@ "storage_template_date_time_description": "Elemento sukūrimo laiko žymė yra naudojama laiko informacijai", "storage_template_date_time_sample": "Pavyzdinis laikas {date}", "storage_template_enable_description": "Aktyvuoti saugyklos šabloną", - "storage_template_hash_verification_enabled": "Aktyvuoti Hash tikrinimą", - "storage_template_hash_verification_enabled_description": "Aktyvuojamas Hash tikrinimas, neišjungti nebent gerai suprantate galimas pasekmes", + "storage_template_hash_verification_enabled": "Aktyvuoti failo parašo tikrinimą", + "storage_template_hash_verification_enabled_description": "Aktyvuojamas failo parašo tikrinimas, neišjungti nebent gerai suprantate galimas pasekmes", "storage_template_migration": "Saugyklos tvarkymas pagal šabloną", "storage_template_migration_description": "Taikyti dabartinį {template} anksčiau įkeltiems duomenims", "storage_template_migration_info": "Saugyklos tvarkyklė konvertuos visus plėtinius mažosiomis raidėmis. Šablonas bus taikomas tik naujiems duomenims. Taikyti šabloną retroaktyviai anksčiau įkeltiems duomenims, paleiskite šią {job}.", @@ -481,7 +488,7 @@ "asset_description_updated": "Elemento aprašymas buvo atnaujintas", "asset_filename_is_offline": "Elementas {filename} nepasiekiamas", "asset_has_unassigned_faces": "Elementas turi nepriskirtų veidų", - "asset_hashing": "Maišoma…", + "asset_hashing": "Kuriami bylų parašai…", "asset_list_group_by_sub_title": "Grupuoti pagal", "asset_list_layout_settings_dynamic_layout_title": "Dinaminis išdėstymas", "asset_list_layout_settings_group_automatically": "Automatiškai", @@ -590,6 +597,7 @@ "backup_controller_page_turn_on": "Įjungti foninį atsarginį kopijavimą", "backup_controller_page_uploading_file_info": "Įkeliama failo info", "backup_err_only_album": "Negalima pašalinti vienintelio albumo", + "backup_error_sync_failed": "Sinchronizavimas nepavyko. Atsarginė kopija negali būti apdorota.", "backup_info_card_assets": "elementai", "backup_manual_cancelled": "Atšaukta", "backup_manual_in_progress": "Jau įkeliama, bandykite dar kartą vėliau", @@ -688,8 +696,7 @@ "comments_and_likes": "Komentarai ir patiktukai", "comments_are_disabled": "Komentarai yra išjungti", "common_create_new_album": "Sukurti naują albumą", - "common_server_error": "Prašome patikrinti tinklo prisijungimą ir įsitikinti, kad serveris pasiekiamas ir programos/serverio versija sutampa.", - "completed": "Atlikta", + "completed": "Užbaigta", "confirm": "Patvirtinti", "confirm_admin_password": "Patvirtinti administratoriaus slaptažodį", "confirm_delete_face": "Ar tikrai norite ištrinti {name} veidą iš elementų?", @@ -870,7 +877,6 @@ "edit_tag": "Redaguoti žymą", "edit_title": "Redaguoti antraštę", "edit_user": "Redaguoti naudotoją", - "edited": "Redaguota", "editor": "Redaktorius", "editor_close_without_save_prompt": "Pakeitimai nebus išsaugoti", "editor_close_without_save_title": "Uždaryti redaktorių?", @@ -1026,6 +1032,7 @@ "exif_bottom_sheet_description_error": "Klaida atnaujinant aprašymą", "exif_bottom_sheet_details": "DETALĖS", "exif_bottom_sheet_location": "VIETOVĖ", + "exif_bottom_sheet_no_description": "Nėra aprašymo", "exif_bottom_sheet_people": "ŽMONĖS", "exif_bottom_sheet_person_add_person": "Pridėti vardą", "exit_slideshow": "Išeiti iš skaidrių peržiūros", @@ -1100,14 +1107,13 @@ "haptic_feedback_switch": "Įjungti haptinį grįžtamąjį ryšį", "haptic_feedback_title": "Haptinis grįžtamasis ryšys", "has_quota": "Turi kvotą", - "hash_asset": "Maišymo elementas", - "hashed_assets": "Sumaišyti elementai", - "hashing": "Maišoma", + "hash_asset": "Kurti bylos parašą elementui", + "hashed_assets": "Elementai su bylų parašais", + "hashing": "Bylų parašo kūrimas", "header_settings_add_header_tip": "Pridėti antraštę", "header_settings_field_validator_msg": "Reikšmė negali būti tuščia", "header_settings_header_name_input": "Antraštės pavadinimas", "header_settings_header_value_input": "Antraštės reikšmė", - "headers_settings_tile_subtitle": "Apibrėžkite tarpinio serverio antraštes, kurias programa turėtų siųsti su kiekviena tinklo užklausa", "headers_settings_tile_title": "Pasirinktinės tarpinio serverio antraštės", "hi_user": "Labas {name} ({email})", "hide_all_people": "Slėpti visus asmenis", @@ -1529,13 +1535,9 @@ "privacy": "Privatumas", "profile": "Profilis", "profile_drawer_app_logs": "Logai", - "profile_drawer_client_out_of_date_major": "Mobili aplikacija jau pasenusios versijos. Prašome atsinaujinti į paskutinę didžiąją versiją.", - "profile_drawer_client_out_of_date_minor": "Mobili aplikacija jau pasenusios versijos. Prašome atsinaujinti į paskutinę mažąją versiją.", "profile_drawer_client_server_up_to_date": "Klientas ir Serveris yra atnaujinti", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Tik skaitymo rėžimas įgalintas. Ilgai paspauskite vartotojo ikoną išėjimui.", - "profile_drawer_server_out_of_date_major": "Serveris jau yra pasenusios versijos. Prašome atsinaujinti į paskutinę didžiąją versiją.", - "profile_drawer_server_out_of_date_minor": "Serveris jau yra pasenusios versijos. Prašome atsinaujinti į paskutinę mažąją versiją.", "profile_image_of_user": "{user} profilio nuotrauka", "profile_picture_set": "Profilio nuotrauka nustatyta.", "public_album": "Viešas albumas", @@ -1575,6 +1577,11 @@ "rating": "Įvertinimas žvaigždutėmis", "rating_count": "{count, plural, one {# įvertinimas} few {# įvertinimai} other {# įvertinimų}}", "rating_description": "Rodyti EXIF įvertinimus informacijos skydelyje", + "read_changelog": "Skaityti pakeitimų sąrašą", + "recent-albums": "Naujausi albumai", + "recent_searches": "Naujausios paieškos", + "recently_added": "Neseniai pridėta", + "recently_added_page_title": "Neseniai pridėta", "recently_taken": "Neseniai sukurti", "recently_taken_page_title": "Neseniai sukurti", "refresh": "Atnaujinti", @@ -1609,11 +1616,14 @@ "repair": "Pataisyti", "repair_no_results_message": "Nesekami ir trūkstami failai bus rodomi čia", "replace_with_upload": "Pakeisti naujai įkeltu failu", + "repository": "Repozitoriumas", "require_password": "Reikalauti slaptažodžio", "rescan": "Perskenuoti", "reset": "Atstatyti", "reset_password": "Atstayti slaptažodį", "reset_pin_code": "Atsatyti PIN kodą", + "reset_pin_code_description": "Jei pamiršote PIN kodą, galite susisiekti su serverio administratoriumi, kad jis jį atstatytų", + "reset_pin_code_with_password": "PIN kodą visada galite atkurti naudodami savo slaptažodį", "reset_to_default": "Atkurti numatytuosius", "resolve_duplicates": "Sutvarkyti dublikatus", "resolved_all_duplicates": "Sutvarkyti visi dublikatai", @@ -1721,8 +1731,11 @@ "shared_intent_upload_button_progress_text": "{current} / {total} Įkelta", "shared_link_clipboard_copied_massage": "Nukopijuota į iškarpinę", "shared_link_clipboard_text": "Nuoroda: {link}\nSlaptažodis: {password}", + "shared_link_edit_expire_after_option_day": "1 diena", "shared_link_edit_expire_after_option_days": "{count} dienų", + "shared_link_edit_expire_after_option_hour": "1 valanda", "shared_link_edit_expire_after_option_hours": "{count} valandų", + "shared_link_edit_expire_after_option_minute": "1 minutė", "shared_link_edit_expire_after_option_minutes": "{count} minučių", "shared_link_edit_expire_after_option_months": "{count} mėnesių", "shared_link_edit_expire_after_option_year": "{count} metų", @@ -1778,6 +1791,7 @@ "sort_created": "Sukūrimo data", "sort_items": "Elementų skaičių", "sort_modified": "Keitimo data", + "sort_newest": "Naujausia nuotrauka", "sort_oldest": "Seniausia nuotrauka", "sort_people_by_similarity": "Rikiuoti žmonės pagal panašumą", "sort_recent": "Naujausia nuotrauka", @@ -1790,8 +1804,11 @@ "stacked_assets_count": "{count, plural, one {Sugrupuotas # elementas} few {Sugrupuoti # elementai} other {Sugrupuota # elementų}}", "start": "Pradėti", "start_date": "Pradžios data", + "start_date_before_end_date": "Pradžios data turi būti ankstesnė už pabaigos datą", "status": "Statusas", "stop_casting": "Nutraukti transliavimą", + "stop_photo_sharing": "Nustoti dalytis savo nuotraukomis?", + "stop_sharing_photos_with_user": "Nustoti dalintis savo nuotraukomis su šiuo vartotoju", "storage": "Saugykla", "storage_label": "Saugyklos Žyma", "storage_usage": "Naudojama {used} iš {available}", @@ -1802,6 +1819,7 @@ "support_and_feedback": "Palaikymas ir atsiliepimai", "sync": "Sinchronizuoti", "sync_albums": "Sinchronizuoti albumus", + "sync_albums_manual_subtitle": "Sinchronizuoti visus įkeltus vaizdo įrašus ir nuotraukas su pasirinktomis atsarginėmis kopijomis", "sync_upload_album_setting_subtitle": "Sukurti ir įkelti jūsų nuotraukas ir vaizdo įrašus į pasirinktus Immich albumus", "tag": "Žyma", "tag_created": "Sukurta žyma: {tag}", @@ -1813,10 +1831,12 @@ "template": "Šablonas", "theme": "Tema", "theme_selection": "Temos pasirinkimas", + "theme_selection_description": "Automatiškai nustatykite šviesią arba tamsią temą pagal naršyklės sistemos nustatymus", "theme_setting_asset_list_tiles_per_row_title": "Elementų per eilutę ({count})", "theme_setting_primary_color_title": "Pagrindinė spalva", "theme_setting_system_primary_color_title": "Naudoti sistemos spalvą", "theme_setting_system_theme_switch": "Automatinė (Naudoti sistemos nustatymus)", + "theme_setting_three_stage_loading_subtitle": "Trijų etapų įkėlimas gali padidinti įkėlimo našumą, tačiau sukelia žymiai didesnę tinklo apkrovą", "time_based_memories": "Atsiminimai pagal laiką", "timeline": "Laiko skalė", "timezone": "Laiko juosta", @@ -1825,6 +1845,7 @@ "to_favorite": "Įtraukti prie mėgstamiausių", "to_login": "Prisijungti", "to_trash": "Išmesti", + "total": "Viso", "trash": "Šiukšliadėžė", "trash_all": "Perkelti visus į šiukšliadėžę", "trash_count": "Perkelti {count, number} į šiukšliadėžę", @@ -1838,6 +1859,7 @@ "trash_page_title": "Šiukšlių ({count})", "trashed_items_will_be_permanently_deleted_after": "Į šiukšliadėžę perkelti elementai bus visam laikui ištrinti po {days, plural, one {# dienos} other {# dienų}}.", "type": "Tipas", + "unable_to_change_pin_code": "Negalima pakeisti PIN kodo", "unarchive": "Išarchyvuoti", "unarchived_count": "{count, plural, other {# išarchyvuota}}", "unfavorite": "Pašalinti iš mėgstamiausių", diff --git a/i18n/lv.json b/i18n/lv.json index f4522a80c8..6ff1c4e4ad 100644 --- a/i18n/lv.json +++ b/i18n/lv.json @@ -28,96 +28,171 @@ "add_to_album": "Pievienot albumam", "add_to_album_bottom_sheet_added": "Pievienots {album}", "add_to_album_bottom_sheet_already_exists": "Jau pievienots {album}", + "add_to_album_bottom_sheet_some_local_assets": "Dažus lokālos failus albumam nevarēja pievienot", "add_to_album_toggle": "Pārslēgt izvēli {album}", "add_to_albums": "Pievienot albumiem", "add_to_albums_count": "Pievienot albumiem ({count})", "add_to_shared_album": "Pievienot koplietotam albumam", + "add_upload_to_stack": "Pievienot augšupielādi kaudzei", "add_url": "Pievienot URL", "added_to_archive": "Pievienots arhīvam", "added_to_favorites": "Pievienots izlasei", "added_to_favorites_count": "{count, number} pievienoti izlasei", "admin": { - "add_exclusion_pattern_description": "Pievienojiet izlaišanas shēmas. Aizstājējzīmju izmantoša *, **, un ? tiek atbalstīta. Lai ignorētu visus failus jebkurā direktorijā ar nosaukumu “RAW”, izmantojiet “**/RAW/**”. Lai ignorētu visus failus, kas beidzas ar “. tif”, izmantojiet “**/*. tif”. Lai ignorētu absolūto ceļu, izmantojiet “/path/to/ignore/**”.", + "add_exclusion_pattern_description": "Pievieno izslēgšanas šablonus. Tiek atbalstīta aizstājējzīmju *, **, un ? izmantošana. Lai ignorētu visus failus jebkurā direktorijā ar nosaukumu “RAW”, izmanto “**/RAW/**”. Lai ignorētu visus failus, kas beidzas ar “. tif”, izmanto “**/*. tif”. Lai ignorētu absolūto ceļu, izmanto “/kāds/ignorējamais/ceļš/**”.", "admin_user": "Administrators", - "asset_offline_description": "Šis ārējās bibliotēkas resurss vairs nav atrodams diskā un ir pārvietots uz atkritni. Ja fails tika pārvietots bibliotēkas ietvaros, pārbaudi, vai jūsu laika skalā ir jauns atbilstošais resurss. Lai atjaunotu šo resursu, pārliecinies, vai Immich var piekļūt tālāk norādītajam faila ceļam un uzsāc bibliotēkas skenēšanu.", + "asset_offline_description": "Šis ārējās bibliotēkas resurss vairs nav atrodams diskā un ir pārvietots uz atkritni. Ja fails tika pārvietots bibliotēkas ietvaros, pārbaudiet, vai jūsu laika skalā ir jauns atbilstošais resurss. Lai atjaunotu šo resursu, pārliecinieties, vai Immich var piekļūt tālāk norādītajam faila ceļam un uzsāc bibliotēkas skenēšanu.", "authentication_settings": "Autentifikācijas iestatījumi", "authentication_settings_description": "Paroļu, OAuth un citu autentifikācijas iestatījumu pārvaldība", "authentication_settings_disable_all": "Vai tiešām vēlaties atspējot visas pieteikšanās metodes? Pieteikšanās tiks pilnībā atspējota.", "authentication_settings_reenable": "Lai atkārtoti iespējotu, izmantojiet Servera Komandu.", - "background_task_job": "Fona Uzdevumi", + "background_task_job": "Fona uzdevumi", "backup_database": "Izveidot datu bāzes izrakstu", "backup_database_enable_description": "Iespējot datu bāzes izrakstus", "backup_keep_last_amount": "Iepriekšējo izrakstu daudzums, kas jāsaglabā", "backup_onboarding_1_description": "ārēja kopija mākonī vai citā fiziskā atrašanās vietā.", - "backup_onboarding_2_description": "vietējās kopijas citās ierīcēs. Tas ietver galvenos failus un šo failu vietējo rezerves kopiju.", + "backup_onboarding_2_description": "lokālās kopijas citās ierīcēs. Tas ietver galvenos failus un šo failu lokālo rezerves kopiju.", + "backup_onboarding_3_description": "kopiju skaits, ieskaitot oriģinālos failus. Tas ietver 1 ārējo kopiju un 2 lokālās kopijas.", + "backup_onboarding_description": "Lai aizsargātu savus datus, ieteicams izmantot 3-2-1 rezerves kopiju stratēģiju. Lai nodrošinātu visaptverošu dublēšanas risinājumu, vajadzētu veidot kopijas saviem augšupielādētajiem fotoattēliem/videoklipiem, kā arī Immich datubāzei.", + "backup_onboarding_footer": "Lai iegūtu vairāk informācijas par Immich rezerves kopiju veidošanu, lūdzu, apskatiet dokumentāciju.", + "backup_onboarding_parts_title": "3-2-1 rezerves kopija ietver:", "backup_onboarding_title": "Rezerves kopijas", "backup_settings": "Datubāzes izrakstu iestatījumi", "backup_settings_description": "Datubāzes izrakstu iestatījumu pārvaldība", "cleared_jobs": "Notīrīti uzdevumi priekš: {job}", "config_set_by_file": "Konfigurāciju pašlaik iestata konfigurācijas fails", "confirm_delete_library": "Vai tiešām vēlaties dzēst {library} bibliotēku?", + "confirm_delete_library_assets": "Vai tiešām vēlaties dzēst šo bibliotēku? Tas izdzēsīs {count, plural, one {# contained asset} other {all # contained assets}} no Immich un to nevar atsaukt. Faili paliks diskā.", "confirm_email_below": "Lai apstiprinātu, zemāk ierakstiet “{email}”", "confirm_reprocess_all_faces": "Vai tiešām vēlies atkārtoti apstrādāt visas sejas? Tas arī atiestatīs personas ar vārdiem.", "confirm_user_password_reset": "Vai tiešām vēlaties atiestatīt lietotāja {user} paroli?", + "confirm_user_pin_code_reset": "Vai tiešām vēlaties atiestatīt {user} PIN kodu?", "create_job": "Izveidot uzdevumu", "cron_expression": "Cron izteiksme", + "cron_expression_description": "Iestatiet skenēšanas intervālu, izmantojot cron formātu. Papildu informācijai skatiet, piemēram, Crontab Guru", + "cron_expression_presets": "Cron izteiksmju sagataves", "disable_login": "Atspējot pieteikšanos", - "duplicate_detection_job_description": "Palaidiet mašīnmācīšanos uz failiem, lai noteiktu līdzīgus attēlus. Paļaujas uz viedo meklēšanu", + "duplicate_detection_job_description": "Analizēt failus ar mašīnmācīšanos, lai noteiktu līdzīgus attēlus. Šī funkcija izmanto viedo meklēšanu", + "exclusion_pattern_description": "Izslēgšanas šabloni ļauj ignorēt failus un mapes, skenējot bibliotēku. Tas ir noderīgi, ja jums ir mapes, kas satur failus, kurus nevēlaties importēt, piemēram, RAW failus.", "external_library_management": "Ārējo bibliotēku pārvaldība", "face_detection": "Seju noteikšana", + "face_detection_description": "Atpazīt attēlos sejas, izmantojot mašīnmācīšanos. Video gadījumā tiek ņemta vērā tikai sīktēls. \"Atsvaidzināt\" atkārtoti apstrādā visus attēlus. \"Atiestatīt\" izdzēš visus pašreizējos seju datus. \"Trūkstošie\" ierindo attēlus, kas vēl nav apstrādāti. Pēc seju noteikšanas pabeigšanas atrastās sejas tiek ierindotas seju atpazīšanai, grupējot tās pēc esošas vai jauns personas.", + "facial_recognition_job_description": "Grupēt atpazītās sejas pēc cilvēkiem. Šis solis tiek veikts pēc seju noteikšanas pabeigšanas. \"Atiestatīt\" atkārtoti sagrupē visas sejas. \"Trūkstošie\" ierindo sejas, kurām nav piešķirta persona.", + "failed_job_command": "Kļūda, izpildot {job} komandu {command}", + "force_delete_user_warning": "BRĪDINĀJUMS: Tas uzreiz izdzēsīs lietotāju ar visiem failiem. Šo darbību nevar atcelt, un failus nevarēs atgūt.", "image_format": "Formāts", "image_format_description": "WebP veido mazākus failus nekā JPEG, taču to kodēšana ir lēnāka.", - "image_fullsize_enabled_description": "Ģenerēt pilna izmēra attēlu formātiem, kas nav piemēroti izmantošanai tīmeklī. Ja ir iespējota opcija \"Priekšroka iegultajam priekšskatījumam\", tiks izmantoti iegultie priekšskatījumi bez konvertēšanas. Neietekmē tīmeklim draudzīgus formātus, piemēram, JPEG.", + "image_fullsize_description": "Pilnizmēra attēls ar noņemtiem metadatiem, ko izmanto, kad attēls ir tuvināts", + "image_fullsize_enabled": "Iespējot pilnizmēra attēlu ģenerēšanu", + "image_fullsize_enabled_description": "Ģenerēt pilnizmēra attēlu formātiem, kas nav piemēroti izmantošanai tīmeklī. Ja ir iespējota opcija \"Priekšroka iegultajam priekšskatījumam\", tiks izmantoti iegultie priekšskatījumi bez konvertēšanas. Neietekmē tīmeklim draudzīgus formātus, piemēram, JPEG.", "image_fullsize_quality_description": "Pilnizmēra attēlu kvalitāte no 1-100. Augstāka vērtība dos labāku kvalitāti, taču faili būs lielāka izmēra.", "image_fullsize_title": "Pilnizmēra attēlu iestatījumi", "image_prefer_embedded_preview": "Priekšroka iegultajam priekšskatījumam", "image_prefer_embedded_preview_setting_description": "Izmanto RAW fotoattēlos iestrādātos priekšskatījumus, ja tādi ir pieejami, kā ievades datus attēlu apstrādei. Tādējādi dažiem attēliem var iegūt precīzākas krāsas, taču priekšskatījuma kvalitāte ir atkarīga no fotokameras un attēlam var būt vairāk saspiešanas artefaktu.", + "image_prefer_wide_gamut": "Dot priekšroku plašai krāsu gammai", "image_prefer_wide_gamut_setting_description": "Sīktēliem izmanto Display P3. Tas labāk saglabā attēlu dzīvīgumu ar plašu krāsu gammu, bet attēli var izskatīties atšķirīgi vecās ierīcēs ar vecu pārlūka versiju. sRGB attēli tiek saglabāti kā sRGB, lai izvairītos no krāsu izmaiņām.", + "image_preview_description": "Vidēja izmēra attēls ar noņemtiem metadatiem, ko izmanto, skatot vienu failu un mašīnmācīšanās apmācībai", + "image_preview_quality_description": "Priekšskatījuma kvalitāte no 1 līdz 100. Augstāka kvalitāte ir labāka, bet veido lielākus failus un var samazināt lietotnes reaģēšanas ātrumu. Zemas vērtības iestatīšana var ietekmēt mašīnmācīšanās kvalitāti.", "image_preview_title": "Priekšskatījuma iestatījumi", "image_quality": "Kvalitāte", "image_resolution": "Izšķirtspēja", + "image_resolution_description": "Augstāka izšķirtspēja ļauj saglabāt vairāk detaļu, taču kodēšana aizņem vairāk laika, failu izmērs ir lielāks un var samazināties lietotnes reaģēšanas ātrums.", "image_settings": "Attēlu iestatījumi", "image_settings_description": "Ģenerēto attēlu kvalitātes un izšķirtspējas pārvaldība", "image_thumbnail_description": "Neliels sīktēls bez metadatiem, ko izmanto, lai apskatītu vairākus fotoattēlus, piemēram, galvenajā laika skalā", + "image_thumbnail_quality_description": "Sīktēlu kvalitāte no 1 līdz 100. Augstāka kvalitāte ir labāka, bet veido lielākus failus un var samazināt lietotnes reaģēšanas ātrumu.", "image_thumbnail_title": "Sīktēlu iestatījumi", "job_concurrency": "{job} vienlaicīgi", "job_created": "Uzdevums izveidots", + "job_not_concurrency_safe": "Šis uzdevums nav drošs vienlaicīgai izpildei.", "job_settings": "Uzdevumu iestatījumi", "job_settings_description": "Uzdevumu izpildes vienlaicīguma pārvaldība", "job_status": "Uzdevumu statuss", "library_created": "Izveidoja bibliotēku: {library}", "library_deleted": "Bibliotēka dzēsta", + "library_import_path_description": "Norādi importējamo mapi. Šī mape un tās apakšmapes tiks pārbaudīta, lai atrastu attēlus un videoklipus.", "library_scanning": "Periodiska skenēšana", "library_scanning_description": "Konfigurē periodisku bibliotēku skenēšanu", "library_scanning_enable_description": "Iespējot periodisku bibliotēku skenēšanu", - "library_settings": "Ārējā bibliotēka", + "library_settings": "Ārējās bibliotēkas", "library_settings_description": "Ārējo bibliotēku iestatījumu pārvaldība", - "library_watching_settings": "Bibliotēku uzraudzīšana (EKSPERIMENTĀLA)", + "library_tasks_description": "Pārbaudīt ārējās bibliotēkas, lai atrastu jaunus un/vai mainītus failus", + "library_watching_enable_description": "Uzraudzīt ārējo bibliotēku failu izmaiņas", + "library_watching_settings": "Bibliotēku uzraudzīšana [EKSPERIMENTĀLA]", "library_watching_settings_description": "Automātiski uzraudzīt, vai ir mainīti faili", + "logging_level_description": "Ja iespējots, kādu žurnāla līmeni izmantot.", + "logging_settings": "Žurnalēšana", + "machine_learning_availability_checks": "Pieejamības pārbaudes", + "machine_learning_availability_checks_description": "Automātiski atklāt un dod priekšroku pieejamajiem mašīnmācīšanās serveriem", + "machine_learning_availability_checks_enabled": "Iespējot pieejamības pārbaudes", + "machine_learning_availability_checks_interval": "Pārbaudes intevāls", + "machine_learning_availability_checks_interval_description": "Intervāls milisekundēs starp pieejamības pārbaudēm", + "machine_learning_availability_checks_timeout": "Pieprasījumu noildze", + "machine_learning_availability_checks_timeout_description": "Pieejamības pārbaužu noildze milisekundēs", "machine_learning_clip_model": "CLIP modelis", + "machine_learning_clip_model_description": "Sarakstā norādītais CLIP modeļa nosaukums. Ņem vērā, ka, mainot modeli, visiem attēliem ir vēlreiz jāpalaiž \"Viedās meklēšanas\" uzdevums.", "machine_learning_duplicate_detection": "Dublikātu noteikšana", + "machine_learning_duplicate_detection_enabled": "Iespējot dublikātu noteikšanu", + "machine_learning_duplicate_detection_enabled_description": "Ja šī funkcija ir atspējota, joprojām tiks izlaisti identiski faili.", + "machine_learning_duplicate_detection_setting_description": "Izmantot CLIP iegultos elementus, lai atrastu iespējamos dublikātus", + "machine_learning_enabled": "Iespējot mašīnmācīšanos", + "machine_learning_enabled_description": "Ja funkcija ir atspējota, tiks atspējotas visas ML funkcijas neatkarīgi no zemāk esošajiem iestatījumiem.", "machine_learning_facial_recognition": "Seju atpazīšana", + "machine_learning_facial_recognition_description": "Noteikt, atpazīt un sagrupēt sejas attēlos", "machine_learning_facial_recognition_model": "Seju atpazīšanas modelis", + "machine_learning_facial_recognition_model_description": "Modeļi ir uzskaitīti pēc to izmēra dilstošā secībā. Lielāki modeļi ir lēnāki un izmanto vairāk atmiņas, bet nodrošina labākus rezultātus. Ņem vērā, ka, mainot modeli, ir atkārtoti jāpalaiž sejas atpazīšanas uzdevums visiem attēliem.", + "machine_learning_facial_recognition_setting": "Iespējot seju atpazīšanu", + "machine_learning_facial_recognition_setting_description": "Ja šī funkcija ir atspējota, attēli netiks kodēti sejas atpazīšanai un netiks parādīti sadaļā “Personas” lapā “Izpētīt”.", + "machine_learning_max_detection_distance": "Maksimālā noteikšanas distance", + "machine_learning_max_detection_distance_description": "Maksimālā distance starp diviem attēliem, lai tos uzskatītu par dublikātiem, ir no 0,001 līdz 0,1. Lielākas vērtības atklās vairāk dublikātu, taču var izraisīt kļūdaini pozitīvus rezultātus.", + "machine_learning_max_recognition_distance": "Maksimālā atpazīšanas distance", + "machine_learning_max_recognition_distance_description": "Maksimālā distance starp divām sejām, lai tās tiktu uzskatītas par vienu un to pašu personu, ir no 0 līdz 2. Samazinot šo distanci, var novērst divu cilvēku apzīmēšanu kā vienu un to pašu personu, savukārt palielinot to, var novērst vienas un tās pašas personas apzīmēšanu.", + "machine_learning_min_detection_score": "Minimālais atpazīšanas rezultāts", + "machine_learning_min_detection_score_description": "Minimālais sejas noteikšanas ticamības rādītājs no 0 līdz 1. Zemākas vērtības atklās vairāk seju, taču var rasties kļūdaini pozitīvi rezultāti.", + "machine_learning_min_recognized_faces": "Minimālais atpazīto seju skaits", + "machine_learning_min_recognized_faces_description": "Minimālais atpazīto seju skaits, kas nepieciešams, lai izveidotu personu. Palielinot šo skaitu, sejas atpazīšana kļūst precīzāka, taču palielinās iespēja, ka seja netiks piešķirta personai.", + "machine_learning_ocr": "OCR", + "machine_learning_ocr_description": "Izmantot mašīnmācīšanos, lai atpazītu tekstu attēlos", + "machine_learning_ocr_enabled": "Aktivizēt OCR", + "machine_learning_ocr_enabled_description": "Ja šī opcija ir atspējota, attēli netiks pakļauti teksta atpazīšanai.", + "machine_learning_ocr_max_resolution": "Maksimālā izšķirtspēja", + "machine_learning_ocr_max_resolution_description": "Priekšskatījumi, kuru izšķirtspēja ir lielāka par šo, tiks mainīti, saglabājot malu attiecību. Augstākas vērtības ir precīzākas, taču apstrāde aizņem ilgāku laiku un izmanto vairāk atmiņas.", + "machine_learning_ocr_min_detection_score": "Minimālais atpazīšanas rezultāts", + "machine_learning_ocr_min_detection_score_description": "Minimālais teksta noteikšanas ticamības rādītājs no 0 līdz 1. Zemākas vērtības noteiks vairāk teksta, taču var izraisīt kļūdaini pozitīvus rezultātus.", + "machine_learning_ocr_min_recognition_score": "Minimālais atpazīšanas rezultāts", + "machine_learning_ocr_model": "OCR modelis", + "machine_learning_ocr_model_description": "Serveru modeļi ir precīzāki nekā mobilie modeļi, bet apstrāde aizņem vairāk laika un tie izmanto vairāk atmiņas.", "machine_learning_settings": "Mašīnmācīšanās iestatījumi", "machine_learning_settings_description": "Mašīnmācīšanās funkciju un iestatījumu pārvaldība", "machine_learning_smart_search": "Viedā meklēšana", + "machine_learning_smart_search_description": "Meklēt attēlus semantiski, izmantojot CLIP iegultos elementus", + "machine_learning_smart_search_enabled": "Iespējot viedo meklēšanu", + "machine_learning_smart_search_enabled_description": "Ja funkcija ir atspējota, attēli netiks kodēti viedai meklēšanai.", "machine_learning_url_description": "Mašīnmācīšanās servera URL. Ja ir norādīts vairāk nekā viens URL, katrs serveris, sākot no pirmā līdz pēdējam, tiks pārbaudīts pa vienam, līdz kāds no tiem atbildēs veiksmīgi. Serveri, kas neatbild, tiks īslaicīgi ignorēti, līdz tie atkal būs pieejami tiešsaistē.", "manage_concurrency": "Vienlaicīgas darbības pārvaldība", "manage_log_settings": "Žurnāla iestatījumu pārvaldība", "map_dark_style": "Tumšais stils", + "map_enable_description": "Iespējot kartes funkcijas", "map_gps_settings": "Kartes un GPS iestatījumi", "map_gps_settings_description": "Karšu un GPS (apgrieztās ģeokodēšanas) iestatījumu pārvaldība", + "map_implications": "Kartes funkcija izmanto ārējo kartes fragmentu pakalpojumu (tiles.immich.cloud)", "map_light_style": "Gaišais stils", "map_manage_reverse_geocoding_settings": "Reversās ģeokodēšanas iestatījumu pārvaldība", "map_reverse_geocoding": "Reversā ģeokodēšana", + "map_reverse_geocoding_enable_description": "Iespējot apgriezto ģeokodēšanu", "map_reverse_geocoding_settings": "Reversās ģeokodēšanas iestatījumi", "map_settings": "Karte", "map_settings_description": "Kartes iestatījumu pārvaldība", "map_style_description": "URL uz style.json kartes tēmu", + "memory_generate_job": "Atmiņu ģenerēšana", "metadata_extraction_job": "Metadatu iegūšana", + "metadata_extraction_job_description": "Iegūt metadatu informāciju no katra faila, piemēram, GPS, sejas un izšķirtspēju", + "metadata_faces_import_setting": "Iespējot seju importēšanu", + "metadata_faces_import_setting_description": "Importēt sejas no attēla EXIF datiem un blakusfailiem", "metadata_settings": "Metadatu iestatījumi", "metadata_settings_description": "Metadatu iestatījumu pārvaldība", "migration_job": "Migrācija", + "migration_job_description": "Pārvietot failu un seju sīktēlus uz jaunāko mapju struktūru", "nightly_tasks_cluster_faces_setting_description": "Veikt sejas atpazīšanu jaunatklātajām sejām", "nightly_tasks_cluster_new_faces_setting": "Sagrupēt jaunās sejas", "nightly_tasks_database_cleanup_setting": "Datubāzes apkopes uzdevumi", @@ -134,15 +209,24 @@ "nightly_tasks_sync_quota_usage_setting_description": "Pārrēķināt lietotāja uzglabāšanas kvotu, pamatojoties uz pašreizējo izmantošanu", "no_paths_added": "Nav pievienots neviens ceļš", "no_pattern_added": "Nav pievienots neviens izslēgšanas šablons", + "note_apply_storage_label_previous_assets": "Piezīme: Lai piemērotu glabātuves nosaukumu iepriekš augšupielādētiem failiem, izpildiet", "note_cannot_be_changed_later": "PIEZĪME: Vēlāk to vairs nevar mainīt!", "notification_email_from_address": "No adreses", - "notification_email_from_address_description": "Sūtītāja e-pasta adrese, piemēram: “Immich foto serveris ”", + "notification_email_from_address_description": "Sūtītāja e-pasta adrese, piemēram: “Immich foto serveris ”. Pārliecinies, ka izmanto adresi, no kuras tev atļauts sūtīt e-pastus.", + "notification_email_host_description": "E-pasta servera nosaukums (piemēram, smtp.immich.app)", "notification_email_ignore_certificate_errors": "Ignorēt sertifikātu kļūdas", "notification_email_ignore_certificate_errors_description": "Ignorēt TLS sertifikāta apstiprināšanas kļūdas (nav ieteicams)", + "notification_email_password_description": "Parole, kas jāizmanto, autentificējoties ar e-pasta serveri", "notification_email_port_description": "e-pasta servera ports (piemēram, 25, 465 vai 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "Izmantot SMTPS (SMTP caur TLS)", "notification_email_sent_test_email_button": "Nosūtīt testa e-pastu un saglabāt", + "notification_email_setting_description": "E-pasta paziņojumu sūtīšanas iestatījumi", "notification_email_test_email": "Nosūtīt testa e-pastu", + "notification_email_test_email_failed": "Neizdevās nosūtīt pārbaudes e-pastu, pārbaudi ievadītās vērtības", "notification_email_test_email_sent": "Uz {email} ir nosūtīts testa e-pasts. Lūdzu, pārbaudi savu iesūtni.", + "notification_email_username_description": "Lietotājvārds, kas jāizmanto, autentificējoties ar e-pasta serveri", + "notification_enable_email_notifications": "Iespējot e-pasta paziņojumus", "notification_settings": "Paziņojumu iestatījumi", "notification_settings_description": "Paziņojumu iestatījumu, tostarp e-pasta, pārvaldība", "oauth_auto_launch": "Palaist automātiski", @@ -150,10 +234,18 @@ "oauth_auto_register": "Automātiska reģistrācija", "oauth_auto_register_description": "Pēc pieslēgšanās ar OAuth automātiski reģistrēt jaunus lietotājus", "oauth_button_text": "Pogas teksts", + "oauth_client_secret_description": "Nepieciešams, ja OAuth pakalpojuma sniedzējs neatbalsta PKCE (Proof Key for Code Exchange)", "oauth_enable_description": "Pieslēgties ar OAuth", + "oauth_role_claim": "Lomas pieteikums", + "oauth_role_claim_description": "Automātiski piešķirt administratora piekļuvi, pamatojoties uz šīs prasības klātbūtni. Prasība var būt vai nu \"user\", vai \"admin\".", "oauth_settings": "OAuth", "oauth_settings_description": "OAuth pieteikšanās iestatījumu pārvaldība", + "oauth_settings_more_details": "Plašāku informāciju par šo funkcionalitāti skatīt dokumentācijā.", + "oauth_storage_label_claim": "Glabātuves nosaukuma pieteikums", + "oauth_storage_label_claim_description": "Automātiski iestatīt lietotāja glabātuves nosaukumu uz šī pieteikuma vērtību.", "oauth_storage_quota_default": "Noklusējuma krātuves kvota (GiB)", + "oauth_timeout": "Pieprasījuma noildze", + "oauth_timeout_description": "Pieprasījumu laika limits milisekundēs", "password_enable_description": "Pieteikšanās ar e-pasta adresi un paroli", "password_settings": "Pieteikšanās ar paroli", "password_settings_description": "Pieteikšanās ar paroli iestatījumu pārvaldība", @@ -176,11 +268,22 @@ "server_settings_description": "Servera iestatījumu pārvaldība", "server_welcome_message": "Sveiciena ziņa", "server_welcome_message_description": "Ziņojums, kas tiek parādīts pieslēgšanās lapā.", + "sidecar_job": "Blakusfailu metadati", + "sidecar_job_description": "Atklāt vai sinhronizēt blakusfailu metadatus no failu sistēmas", + "slideshow_duration_description": "Katra attēla rādīšanas ilgums sekundēs", + "smart_search_job_description": "Analizēt failus ar mašīnmācīšanos lai sagatavotu datus viedajai meklēšanai", "storage_template_date_time_sample": "Laika paraugs {date}", + "storage_template_hash_verification_enabled": "Jaucējvērtību pārbaude ir iespējota", + "storage_template_hash_verification_enabled_description": "Iespējo jaucējvērtību pārbaudi, neatslēdz to, ja neapzinies sekas", "storage_template_migration": "Krātuves veidņu migrācija", - "storage_template_migration_job": "Krātuves veidņu migrācijas uzdevums", + "storage_template_migration_description": "Piemēro pašreizējo {template} iepriekš augšupielādētajiem failiem", + "storage_template_migration_info": "Krātuves veidne pārveidos visus failu paplašinājumus uz mazajiem burtiem. Veidnes izmaiņas attieksies tikai uz jauniem failiem. Lai veidni piemērotu ar atpakaļejošu efektu iepriekš augšupielādētiem failiem, palaidiet {job}.", + "storage_template_migration_job": "Krātuves veidņu migrācijas uzdevumu", + "storage_template_more_details": "Plašāku informāciju par šo funkcionalitāti skatīt sadaļā Krātuves veidne un tās sekas", "storage_template_path_length": "Aptuvenais ceļa garuma ierobežojums: {length, number}/{limit, number}", "storage_template_settings": "Krātuves veidne", + "storage_template_settings_description": "Pārvaldīt augšupielādēto failu mapju struktūru un faila nosaukumu", + "storage_template_user_label": "Lietotāja krātuves nosaukums ir {label}", "system_settings": "Sistēmas iestatījumi", "template_email_available_tags": "Sagatavē var izmantot šos mainīgos: {tags}", "template_email_if_empty": "Ja sagatave ir tukša, tiks izmantots noklusējuma e-pasts.", @@ -199,15 +302,20 @@ "transcoding_acceleration_qsv": "Quick Sync (nepieciešams 7. paaudzes vai jaunāks Intel procesors)", "transcoding_acceleration_rkmpp": "RKMPP (tikai Rockchip SOC)", "transcoding_acceleration_vaapi": "VAAPI", + "transcoding_accepted_video_codecs": "Akceptētie video kodeki", + "transcoding_accepted_video_codecs_description": "Izvēlies, kurus video kodekus nav nepieciešams transkodēt. Tiek izmantots tikai noteiktām transkodēšanas politikām.", "transcoding_advanced_options_description": "Lielākajai daļai lietotāju nevajadzētu mainīt šīs opcijas", "transcoding_audio_codec": "Audio kodeks", + "transcoding_audio_codec_description": "Opus ir augstākās kvalitātes izvēle, bet tā ir mazāk saderīga ar vecām ierīcēm vai programmatūru.", "transcoding_codecs_learn_more": "Lai uzzinātu vairāk par šeit lietoto terminoloģiju, skatiet FFmpeg dokumentāciju par H.264 kodeku, HEVC kodeku un VP9 kodeku.", + "transcoding_constant_quality_mode": "Nemainīgas kvalitātes režīms", "transcoding_constant_quality_mode_description": "ICQ ir labāks nekā CQP, bet dažas aparatūras paātrinājuma ierīces neatbalsta šo režīmu. Iestatot šo opciju, tiks izmantots norādītais režīms, ja tiek izmantota kvalitātē balstīta kodēšana. NVENC to ignorē, jo neatbalsta ICQ.", "transcoding_constant_rate_factor_description": "Video kvalitātes līmenis. Tipiskās vērtības ir 23 priekš H.264, 28 priekš HEVC, 31 priekš VP9 un 35 priekš AV1. Zemāka vērtība ir labāka, bet rada lielākus failus.", "transcoding_hardware_acceleration": "Aparatūras paātrinājums", "transcoding_required_description": "Tikai video, kas nav atbalstītā formātā", "transcoding_settings": "Video transkodēšanas iestatījumi", "transcoding_threads": "Pavedieni", + "transcoding_threads_description": "Augstākas vērtības nodrošina ātrāku kodēšanu, bet atstāj mazāk jaudas serverim, lai apstrādātu citus aktīvos uzdevumus. Šai vērtībai nevajadzētu pārsniegt CPU kodolu skaitu. Ja iestatīta uz 0, maksimizē izmantošanu.", "transcoding_video_codec": "Video kodeks", "trash_number_of_days": "Dienu skaits", "trash_settings": "Atkritnes iestatījumi", @@ -225,18 +333,19 @@ "user_settings_description": "Lietotāju iestatījumu pārvaldība", "version_check_enabled_description": "Ieslēgt versijas pārbaudi", "version_check_implications": "Versiju pārbaudes funkcija ir atkarīga no periodiskas saziņas ar github.com", - "version_check_settings": "Versijas pārbaude" + "version_check_settings": "Versijas pārbaude", + "version_check_settings_description": "Ieslēgt/izslēgt paziņojumus par jaunu versiju" }, "admin_email": "Administratora e-pasts", "admin_password": "Administratora parole", "administration": "Administrēšana", "advanced": "Papildu", "advanced_settings_log_level_title": "Žurnalēšanas līmenis: {level}", - "advanced_settings_prefer_remote_subtitle": "Dažās ierīcēs sīktēli no ierīcē esošajiem resursiem tiek ielādēti ļoti lēni. Aktivizējiet šo iestatījumu, lai tā vietā ielādētu attālus attēlus.", + "advanced_settings_prefer_remote_subtitle": "Dažās ierīcēs sīktēli no ierīces atmiņas ielādējas ļoti lēni. Aktivizējiet šo iestatījumu, lai tā vietā ielādētu attālus attēlus.", "advanced_settings_prefer_remote_title": "Dot priekšroku attāliem attēliem", - "advanced_settings_proxy_headers_title": "Starpniekservera galvenes", + "advanced_settings_proxy_headers_title": "Pielāgotas starpniekservera galvenes [EKSPERIMENTĀLAS]", "advanced_settings_self_signed_ssl_subtitle": "Izlaiž servera galapunkta SSL sertifikātu verifikāciju. Nepieciešams pašparakstītajiem sertifikātiem.", - "advanced_settings_self_signed_ssl_title": "Atļaut pašparakstītus SSL sertifikātus", + "advanced_settings_self_signed_ssl_title": "Atļaut pašparakstītus SSL sertifikātus [EKSPERIMENTĀLI]", "advanced_settings_tile_subtitle": "Lietotāja papildu iestatījumi", "advanced_settings_troubleshooting_subtitle": "Iespējot papildu aktīvus problēmu novēršanai", "advanced_settings_troubleshooting_title": "Problēmas novēršana", @@ -245,7 +354,7 @@ "age_years": "{years, plural, zero {# gadu} one {# gads} other {# gadi}}", "album_added": "Albums pievienots", "album_added_notification_setting_description": "Saņemt e-pasta paziņojumu, kad tevi pievieno kopīgam albumam", - "album_cover_updated": "Albuma attēls atjaunināts", + "album_cover_updated": "Albuma vāciņš atjaunināts", "album_delete_confirmation_description": "Ja šis albums tiek kopīgots, citi lietotāji vairs nevarēs tam piekļūt.", "album_deleted": "Albums dzēsts", "album_info_card_backup_album_excluded": "NEIEKĻAUTS", @@ -254,6 +363,7 @@ "album_leave": "Pamest albumu?", "album_name": "Albuma nosaukums", "album_remove_user": "Noņemt lietotāju?", + "album_summary": "Albuma kopsavilkums", "album_updated": "Albums atjaunināts", "album_user_left": "Pameta {album}", "album_user_removed": "Noņēma {user}", @@ -270,7 +380,7 @@ "albums_default_sort_order_description": "Sākotnējā failu kārtošanas secība, veidojot jaunus albumus.", "albums_feature_description": "Failu kolekcijas, kuras var koplietot ar citiem lietotājiem.", "albums_on_device_count": "Albumi ierīcē ({count})", - "all": "Viss", + "all": "Visi", "all_albums": "Visi albumi", "all_people": "Visas personas", "all_videos": "Visi video", @@ -283,10 +393,14 @@ "api_key": "API atslēga", "api_key_description": "Šī vērtība tiks parādīta tikai vienu reizi. Nokopējiet to pirms loga aizvēršanas.", "api_keys": "API atslēgas", + "app_architecture_variant": "Variants (arhitektūra)", "app_bar_signout_dialog_content": "Vai tiešām vēlaties izrakstīties?", "app_bar_signout_dialog_ok": "Jā", "app_bar_signout_dialog_title": "Izrakstīties", + "app_download_links": "Lietotņu lejupielādes saites", "app_settings": "Lietotnes iestatījumi", + "app_stores": "Lietotņu veikali", + "app_update_available": "Pieejams lietotnes atjauninājums", "appears_in": "Parādās iekš", "apply_count": "Pielietot ({count, number})", "archive": "Arhīvs", @@ -300,10 +414,11 @@ "asset_added_to_album": "Pievienots albumam", "asset_adding_to_album": "Pievieno albumam…", "asset_description_updated": "Faila apraksts ir atjaunināts", + "asset_hashing": "Veido jaucējvērtības…", "asset_list_group_by_sub_title": "Grupēt pēc", "asset_list_layout_settings_dynamic_layout_title": "Dinamiskais izkārtojums", "asset_list_layout_settings_group_automatically": "Automātiski", - "asset_list_layout_settings_group_by": "Grupēt aktīvus pēc", + "asset_list_layout_settings_group_by": "Grupēt failus pēc", "asset_list_layout_settings_group_by_month_day": "Mēnesis + diena", "asset_list_layout_sub_title": "Izvietojums", "asset_list_settings_subtitle": "Fotorežģa izkārtojuma iestatījumi", @@ -312,7 +427,7 @@ "asset_skipped_in_trash": "Atkritnē", "asset_uploaded": "Augšupielādēts", "asset_uploading": "Augšupielādē…", - "asset_viewer_settings_title": "Aktīvu Skatītājs", + "asset_viewer_settings_title": "Failu skatītājs", "assets": "Faili", "assets_added_count": "Pievienoja {count, plural, one {# failu} other {# failus}}", "assets_added_to_album_count": "Pievienoja albumam {count, plural, one {# failu} other {# failus}}", @@ -326,6 +441,7 @@ "automatic_endpoint_switching_title": "Automātiska URL pārslēgšana", "autoplay_slideshow": "Automātiska slaidrādes atskaņošana", "back": "Atpakaļ", + "background_backup_running_error": "Pašlaik darbojas dublēšana fonā, nevar uzsākt manuālu dublēšanu", "background_options": "Fona opcijas", "backup": "Dublēšana", "backup_album_selection_page_albums_device": "Albumi ierīcē ({count})", @@ -374,7 +490,7 @@ "backup_controller_page_remainder": "Atlikums", "backup_controller_page_remainder_sub": "Atlikušie fotoattēli un videoklipi, kurus dublēt no atlases", "backup_controller_page_server_storage": "Servera krātuve", - "backup_controller_page_start_backup": "Sākt Dublēšanu", + "backup_controller_page_start_backup": "Sākt dublēšanu", "backup_controller_page_status_off": "Automātiskā priekšplāna dublēšana ir izslēgta", "backup_controller_page_status_on": "Automātiskā priekšplāna dublēšana ir ieslēgta", "backup_controller_page_storage_format": "{used} no {total} tiek izmantots", @@ -384,6 +500,7 @@ "backup_controller_page_turn_on": "Ieslēgt priekšplāna dublēšanu", "backup_controller_page_uploading_file_info": "Faila informācijas augšupielāde", "backup_err_only_album": "Nevar noņemt vienīgo albumu", + "backup_error_sync_failed": "Sinhronizācija neizdevās. Nevar apstrādāt rezerves kopiju.", "backup_info_card_assets": "faili", "backup_manual_cancelled": "Atcelts", "backup_manual_in_progress": "Augšupielāde jau notiek. Mēģiniet pēc kāda laika atkārtoti", @@ -391,20 +508,22 @@ "backup_manual_title": "Augšupielādes statuss", "backup_options_page_title": "Dublēšanas iestatījumi", "backup_settings_subtitle": "Pārvaldīt augšupielādes iestatījumus", - "backward": "Atpakaļejoši", + "backward": "Atpakaļejoša", "biometric_auth_enabled": "Ieslēgta biometriskā autentifikācija", "biometric_locked_out": "Biometriskā autentifikācija tev ir bloķēta", "biometric_no_options": "Nav pieejamas biometriskās autentifikācijas iespējas", "biometric_not_available": "Biometriskā autentifikācija šajā ierīcē nav pieejama", "birthdate_saved": "Dzimšanas datums veiksmīgi saglabāts", "birthdate_set_description": "Dzimšanas datums tiek izmantots, lai aprēķinātu šīs personas vecumu fotogrāfijas uzņemšanas brīdī.", + "blurred_background": "Izpludināts fons", "bugs_and_feature_requests": "Kļūdas un funkciju pieprasījumi", "build": "Būvējums", "build_image": "Būvējuma attēls", + "buy": "Iegādāties Immich", "cache_settings_clear_cache_button": "Iztīrīt kešatmiņu", "cache_settings_clear_cache_button_title": "Iztīra aplikācijas kešatmiņu. Tas būtiski ietekmēs lietotnes veiktspēju, līdz kešatmiņa būs pārbūvēta.", "cache_settings_duplicated_assets_clear_button": "NOTĪRĪT", - "cache_settings_duplicated_assets_subtitle": "Fotoattēli un videoklipi, kurus lietotne ir iekļāvusi melnajā sarakstā", + "cache_settings_duplicated_assets_subtitle": "Fotoattēli un videoklipi, kurus lietotne ir iekļāvusi ignorējamo sarakstā", "cache_settings_duplicated_assets_title": "Dublicētie faili ({count})", "cache_settings_statistics_album": "Bibliotēkas sīktēli", "cache_settings_statistics_full": "Pilni attēli", @@ -413,13 +532,17 @@ "cache_settings_statistics_title": "Kešatmiņas lietojums", "cache_settings_subtitle": "Kontrolēt Immich mobilās lietotnes kešdarbi", "cache_settings_tile_subtitle": "Kontrolēt lokālās krātuves uzvedību", - "cache_settings_tile_title": "Lokālā Krātuve", + "cache_settings_tile_title": "Lokālā krātuve", "cache_settings_title": "Kešdarbes iestatījumi", "camera": "Fotokamera", + "camera_brand": "Fotokameras zīmols", + "camera_model": "Fotokameras modelis", "cancel": "Atcelt", + "cancel_search": "Atcelt meklēšanu", "canceled": "Atcelts", "canceling": "Atceļ", "cannot_merge_people": "Nevar apvienot personas", + "cannot_undo_this_action": "Šo darbību nevar atcelt!", "cast": "Pārraidīt", "cast_description": "Konfigurēt pieejamos pārraides galamērķus", "change_date": "Mainīt datumu", @@ -430,6 +553,7 @@ "change_name": "Mainīt nosaukumu", "change_name_successfully": "Vārds veiksmīgi nomainīts", "change_password": "Nomainīt paroli", + "change_password_description": "Vai nu šī ir pirmā reize, kad pieslēdzaties sistēmai, vai arī ir iesniegts pieprasījums mainīt paroli. Lūdzu, ievadiet jauno paroli zemāk.", "change_password_form_confirm_password": "Apstiprināt Paroli", "change_password_form_description": "Sveiki {name},\n\nŠī ir pirmā reize, kad pierakstāties sistēmā, vai arī ir iesniegts pieprasījums mainīt paroli. Lūdzu, zemāk ievadiet jauno paroli.", "change_password_form_new_password": "Jauna Parole", @@ -443,7 +567,9 @@ "city": "Pilsēta", "clear": "Notīrīt", "clear_all": "Notīrīt visu", + "clear_all_recent_searches": "Notīrīt visas pēdējās meklēšanas", "clear_file_cache": "Notīrīt failu kešatmiņu", + "clear_message": "Notīrīt paziņojumu", "clear_value": "Notīrīt vērtību", "client_cert_dialog_msg_confirm": "Labi", "client_cert_enter_password": "Ievadi paroli", @@ -452,7 +578,7 @@ "client_cert_invalid_msg": "Nederīgs sertifikāta fails vai nepareiza parole", "client_cert_remove_msg": "Klienta sertifikāts ir noņemts", "client_cert_subtitle": "Atbalsta tikai PKCS12 (.p12, .pfx) formātu. Sertifikātu importēšana/noņemšana ir pieejama tikai pirms pieslēgšanās", - "client_cert_title": "SSL klienta sertifikāts", + "client_cert_title": "SSL klienta sertifikāts [EKSPERIMENTĀLS]", "clockwise": "Pulksteņrādītāja virzienā", "close": "Aizvērt", "collapse": "Sakļaut", @@ -460,10 +586,13 @@ "color": "Krāsa", "color_theme": "Krāsu tēma", "comment_deleted": "Komentārs dzēsts", + "comment_options": "Komentāru iespējas", + "comments_and_likes": "Komentāri un tīkšķi", + "comments_are_disabled": "Komentāri ir atslēgti", "common_create_new_album": "Izveidot jaunu albumu", - "common_server_error": "Lūdzu, pārbaudiet tīkla savienojumu, pārliecinieties, vai serveris ir sasniedzams un aplikācijas/servera versijas ir saderīgas.", "completed": "Pabeigts", "confirm": "Apstiprināt", + "confirm_admin_password": "Administratora paroles apstiprinājums", "confirm_new_pin_code": "Apstiprināt jauno PIN kodu", "confirm_password": "Apstiprināt paroli", "confirm_tag_face": "Vai vēlies atzīmēt šo seju kā {name}?", @@ -473,20 +602,25 @@ "control_bottom_app_bar_create_new_album": "Izveidot jaunu albumu", "control_bottom_app_bar_delete_from_immich": "Dzēst no Immich", "control_bottom_app_bar_delete_from_local": "Dzēst no ierīces", - "control_bottom_app_bar_edit_location": "Rediģēt Atrašanās Vietu", - "control_bottom_app_bar_edit_time": "Rediģēt Datumu un Laiku", - "control_bottom_app_bar_share_to": "Kopīgot Uz", + "control_bottom_app_bar_edit_location": "Rediģēt atrašanās vietu", + "control_bottom_app_bar_edit_time": "Rediģēt datumu un laiku", + "control_bottom_app_bar_share_to": "Kopīgot uz", "control_bottom_app_bar_trash_from_immich": "Pārvietot uz Atkritni", "copy_error": "Kopēšanas kļūda", + "copy_to_clipboard": "Kopēt starpliktuvē", "country": "Valsts", + "cover": "Aizpildīts ekrāns", + "covers": "Vāciņi", "create": "Izveidot", "create_album": "Izveidot albumu", "create_album_page_untitled": "Bez nosaukuma", + "create_api_key": "Izveidot API atslēgu", "create_library": "Izveidot bibliotēku", "create_link": "Izveidot saiti", "create_link_to_share": "Izveidot kopīgošanas saiti", "create_new": "IZVEIDOT JAUNU", "create_new_person": "Izveidot jaunu personu", + "create_new_person_hint": "Piesaistīt izvēlētos failus jaunai personai", "create_new_user": "Izveidot jaunu lietotāju", "create_shared_album_page_share_add_assets": "PIEVIENOT AKTĪVUS", "create_shared_album_page_share_select_photos": "Fotoattēlu Izvēle", @@ -499,6 +633,7 @@ "custom_locale_description": "Formatēt datumus un skaitļus atbilstoši valodai un reģionam", "custom_url": "Pielāgots URL", "daily_title_text_date_year": "E, MMM dd, gggg", + "dark_theme": "Pārslēgt tumšo tēmu", "date_after": "Datums pēc", "date_and_time": "Datums un Laiks", "date_before": "Datums pirms", @@ -527,7 +662,7 @@ "delete_library": "Dzēst bibliotēku", "delete_link": "Dzēst saiti", "delete_local_action_prompt": "{count} dzēsti lokāli", - "delete_local_dialog_ok_backed_up_only": "Dzēst tikai Dublētos", + "delete_local_dialog_ok_backed_up_only": "Dzēst tikai dublētos", "delete_local_dialog_ok_force": "Tā pat dzēst", "delete_others": "Dzēst citus", "delete_shared_link": "Dzēst Kopīgošanas saiti", @@ -540,8 +675,11 @@ "details": "INFORMĀCIJA", "direction": "Secība", "discord": "Discord", + "discover": "Atklāt", + "discovered_devices": "Atrastās ierīces", "display_order": "Attēlošanas secība", "display_original_photos": "Rādīt oriģinālās fotogrāfijas", + "do_not_show_again": "Vairs nerādīt šo ziņojumu", "documentation": "Dokumentācija", "done": "Gatavs", "download": "Lejupielādēt", @@ -552,6 +690,8 @@ "download_error": "Lejupielādes kļūda", "download_failed": "Lejupielāde neizdevās", "download_finished": "Lejupielāde pabeigta", + "download_include_embedded_motion_videos": "Iegultie videoklipi", + "download_include_embedded_motion_videos_description": "Iekļaut video, kas iebūvēti kustīgos fotoattēlos, kā atsevišķu failu", "download_notfound": "Lejupielāde nav atrasta", "download_paused": "Lejupielāde nopauzēta", "download_settings": "Lejupielāde", @@ -572,8 +712,10 @@ "edit_birthday": "Labot dzimšanas dienu", "edit_date": "Labot datumu", "edit_date_and_time": "Labot datumu un laiku", + "edit_date_and_time_action_prompt": "{count} datums un laiks labots", "edit_description": "Labot aprakstu", "edit_description_prompt": "Lūdzu, izvēlies jaunu aprakstu:", + "edit_exclusion_pattern": "Labot izslēgšanas šablonu", "edit_faces": "Labot sejas", "edit_import_path": "Labot importa ceļu", "edit_import_paths": "Labot importa ceļus", @@ -585,7 +727,6 @@ "edit_people": "Labot profilu", "edit_title": "Labot nosaukumu", "edit_user": "Labot lietotāju", - "edited": "Labots", "editor": "Redaktors", "editor_close_without_save_prompt": "Izmaiņas netiks saglabātas", "editor_close_without_save_title": "Aizvērt redaktoru?", @@ -603,12 +744,17 @@ "enter_your_pin_code_subtitle": "Ievadi savu PIN kodu, lai piekļūtu slēgtajai mapei", "error": "Kļūda", "error_change_sort_album": "Neizdevās nomainīt albuma kārtošanas secību", + "error_loading_image": "Kļūda, ielādējot attēlu", "error_loading_partners": "Kļūda, ielādējot partnerus: {error}", "error_saving_image": "Kļūda: {error}", + "error_title": "Kļūda - kaut kas nogāja greizi", "errors": { + "cannot_navigate_next_asset": "Nevar pāriet uz nākamo resursu", + "cannot_navigate_previous_asset": "Nevar pāriet uz iepriekšējo resursu", + "cant_apply_changes": "Nevar piemērot izmaiņas", "cant_get_faces": "Nevar iegūt sejas", "cant_search_people": "Neizdevās veikt peronu meklēšanu", - "exclusion_pattern_already_exists": "Šāds izslēgšanas paraugs jau pastāv.", + "exclusion_pattern_already_exists": "Šāds izslēgšanas šablons jau pastāv.", "failed_to_create_album": "Neizdevās izveidot albumu", "failed_to_create_shared_link": "Neizdevās izvedot kopīgošanas saiti", "failed_to_edit_shared_link": "Neizdevās labot kopīgoto saiti", @@ -627,21 +773,31 @@ "incorrect_email_or_password": "Nepareizs e-pasts vai parole", "profile_picture_transparent_pixels": "Profila attēlos nevar būt caurspīdīgi pikseļi. Lūdzu, palielini un/vai pārvieto attēlu.", "something_went_wrong": "Kaut kas nogāja greizi", + "unable_to_add_exclusion_pattern": "Neizdevās pievienot izslēgšanas šablonu", "unable_to_change_description": "Neizdevās nomainīt aprakstu", + "unable_to_create_admin_account": "Nevar izveidot administratora kontu", + "unable_to_create_api_key": "Nevar izveidot jaunu API atslēgu", + "unable_to_create_library": "Nevar izveidot bibliotēku", "unable_to_create_user": "Neizdevās izveidot lietotāju", + "unable_to_delete_album": "Nevar izdzēst albumu", + "unable_to_delete_asset": "Nevar izdzēst failu", + "unable_to_delete_exclusion_pattern": "Neizdevās dzēst izslēgšanas šablonu", "unable_to_delete_user": "Neizdevās dzēst lietotāju", + "unable_to_edit_exclusion_pattern": "Neizdevās labot izslēgšanas šablonu", "unable_to_empty_trash": "Neizdevās iztukšot atkritni", "unable_to_hide_person": "Neizdevās paslēpt personu", "unable_to_restore_trash": "Neizdevās atjaunot failus no atkritnes", "unable_to_save_date_of_birth": "Neizdevās saglabāt dzimšanas datumu", "unable_to_scan_libraries": "Bibliotēku skenēšana neizdevās", "unable_to_scan_library": "Bibliotēkas skenēšana neizdevās", - "unable_to_trash_asset": "Neizdevās pārvietot failu uz atkritni" + "unable_to_trash_asset": "Neizdevās pārvietot failu uz atkritni", + "unable_to_update_album_cover": "Nevar atjaunināt albuma vāciņu" }, "exif": "Exif", "exif_bottom_sheet_description": "Pievienot Aprakstu...", "exif_bottom_sheet_details": "INFORMĀCIJA", "exif_bottom_sheet_location": "ATRAŠANĀS VIETA", + "exif_bottom_sheet_no_description": "Nav apraksta", "exif_bottom_sheet_people": "PERSONAS", "exif_bottom_sheet_person_add_person": "Pievienot vārdu", "exit_slideshow": "Iziet no slīdrādes", @@ -653,12 +809,14 @@ "expired": "Derīguma termiņš beidzās", "explore": "Izpētīt", "export": "Eksportēt", + "export_as_json": "Eksportēt kā JSON", "export_database": "Eksportēt datubāzi", "export_database_description": "Eksportēt SQLite datubāzi", "extension": "Paplašinājums", "external": "Ārējs", + "external_libraries": "Ārējas bibliotēkas", "external_network": "Ārējs tīkls", - "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", + "external_network_sheet_info": "Kad nav pieejams izvēlētais Wi-Fi tīkls, aplikācija pieslēgsies serverim lietojot pirmo strādājošo URL no saraksta, sākot ar augšējo", "face_unassigned": "Nepiešķirts", "failed": "Neizdevās", "failed_to_authenticate": "Neizdevās autentificēties", @@ -699,13 +857,16 @@ "group_owner": "Grupēt pēc īpašnieka", "group_places_by": "Grupēt vietas pēc...", "group_year": "Grupēt pēc gada", - "haptic_feedback_switch": "Iestatīt haptisku reakciju", + "haptic_feedback_switch": "Iespējot haptisku reakciju", "haptic_feedback_title": "Haptiska Reakcija", - "has_quota": "Ir kvota", + "has_quota": "Kvota", "hash_asset": "Veidot faila jaucējvērtību", - "hashed_assets": "Faili ar izveidotām jaucējvērtībām", + "hashed_assets": "Faili ar jaucējvērtībām", "hashing": "Veido jaucējvērtības", "header_settings_field_validator_msg": "Vērtība nevar būt tukša", + "header_settings_header_name_input": "Galvenes lauks", + "header_settings_header_value_input": "Galvenes vērtība", + "headers_settings_tile_title": "Pielāgotas starpniekservera galvenes", "hide_all_people": "Paslēpt visas personas", "hide_gallery": "Paslēpt galeriju", "hide_named_person": "Paslēpt personu {name}", @@ -713,20 +874,20 @@ "hide_person": "Paslēpt personu", "hide_unnamed_people": "Paslēpt nenosauktas personas", "home_page_add_to_album_conflicts": "Pievienoja {added} failus albumam {album}. {failed} faili jau ir albumā.", - "home_page_add_to_album_err_local": "Albumiem vēl nevar pievienot lokālos aktīvus, notiek izlaišana", + "home_page_add_to_album_err_local": "Albumiem vēl nevar pievienot lokālos failus, izlaiž", "home_page_add_to_album_success": "Pievienoja {added} aktīvus albumam {album}.", "home_page_album_err_partner": "Pagaidām nevar pievienot partnera aktīvus albumam, notiek izlaišana", - "home_page_archive_err_local": "Vēl nevar arhivēt lokālos aktīvus, notiek izlaišana", + "home_page_archive_err_local": "Vēl nevar arhivēt lokālos aktīvus, izlaiž", "home_page_archive_err_partner": "Nevarēja arhivēt partnera aktīvus, notiek izlaišana", "home_page_building_timeline": "Tiek izveidota laika skala", "home_page_delete_err_partner": "Nevarēja dzēst partnera aktīvus, notiek izlaišana", - "home_page_delete_remote_err_local": "Lokālie faili dzēšanai attālinātajā izvēlē, tiek izlaists", - "home_page_favorite_err_local": "Vēl nevar pievienot izlasei vietējos failus, izlaiž", + "home_page_delete_remote_err_local": "Lokālie faili dzēšanai attālinātajā izvēlē, izlaiž", + "home_page_favorite_err_local": "Vēl nevar pievienot izlasei lokālos failus, izlaiž", "home_page_favorite_err_partner": "Pagaidām nevar ievietot izlasē partnera failus, izlaiž", "home_page_first_time_notice": "Ja šī ir pirmā reize, kad izmanto lietotni, lūdzu, izvēlies dublējamo albumu, lai laika skalā varētu aizpildīt fotoattēlus un videoklipus", - "home_page_locked_error_local": "Nevar pārvietot vietējos failus uz slēgto mapi, izlaiž", + "home_page_locked_error_local": "Nevar pārvietot lokālos failus uz slēgto mapi, izlaiž", "home_page_locked_error_partner": "Nevar pārvietot partneru failus uz slēgto mapi, izlaiž", - "home_page_share_err_local": "Caur saiti nevarēja kopīgot lokālos aktīvus, notiek izlaišana", + "home_page_share_err_local": "Caur saiti nevarēja kopīgot lokālos aktīvus, izlaiž", "home_page_upload_err_limit": "Vienlaikus var augšupielādēt ne vairāk kā 30 aktīvus, notiek izlaišana", "hour": "Stunda", "hours": "Stundas", @@ -736,7 +897,7 @@ "ignore_icloud_photos_description": "iCloud uzglabātās fotogrāfijas netiks augšupielādētas Immich serverī", "image": "Attēls", "image_saved_successfully": "Attēls saglabāts", - "image_viewer_page_state_provider_download_started": "Lejupielāde Uzsākta", + "image_viewer_page_state_provider_download_started": "Lejupielāde uzsākta", "image_viewer_page_state_provider_download_success": "Lejupielāde izdevās", "image_viewer_page_state_provider_share_error": "Kopīgošanas Kļūda", "immich_logo": "Immich logo", @@ -747,6 +908,7 @@ "in_archive": "Arhīvā", "include_archived": "Iekļaut arhivētos", "include_shared_albums": "Iekļaut koplietotos albumus", + "include_shared_partner_assets": "Iekļaut partneru koplietotos failus", "info": "Informācija", "interval": { "day_at_onepm": "Katru dienu 13.00", @@ -761,6 +923,7 @@ "ios_debug_info_last_sync_at": "Pēdējā sinhronizācija {dateTime}", "ios_debug_info_no_processes_queued": "Nav ierindotu fona procesu", "ios_debug_info_processing_ran_at": "Apstrāde notika {dateTime}", + "items_count": "{count, plural, one {# vienums} other {# vienumi}}", "jobs": "Uzdevumi", "keep": "Paturēt", "keep_all": "Paturēt visus", @@ -790,11 +953,18 @@ "library_page_sort_last_modified": "Pēdējās izmaiņas", "library_page_sort_title": "Albuma virsraksts", "licenses": "Licences", + "like": "Patīk", + "like_deleted": "Tīkšķis dzēsts", "link_to_oauth": "Piesaistīt OAuth", "linked_oauth_account": "Piesaistītais OAuth konts", "list": "Saraksts", "loading": "Ielādē", + "local": "Lokāli", + "local_asset_cast_failed": "Nav iespējams pārraidīt resursu, kas nav augšupielādēts serverī", + "local_assets": "Lokālie faili", + "local_media_summary": "Lokālo mediju kopsavilkums", "local_network": "Lokālais tīkls", + "local_network_sheet_info": "Izmantojot norādīto Wi-Fi tīklu, lietotne veidos savienojumu ar serveri, izmantojot šo URL", "location_permission": "Atrašanās vietas atļauja", "location_permission_content": "Lai izmantotu automātiskās pārslēgšanās funkciju, Immich ir nepieciešama precīzas atrašanās vietas atļauja, lai varētu nolasīt pašreizējā Wi-Fi tīkla nosaukumu", "location_picker_choose_on_map": "Izvēlēties uz kartes", @@ -849,11 +1019,11 @@ "map_marker_with_image": "Kartes marķieris ar attēlu", "map_no_location_permission_content": "Atrašanās vietas atļauja ir nepieciešama, lai parādītu jūsu pašreizējās atrašanās vietas aktīvus. Vai vēlaties to atļaut tagad?", "map_no_location_permission_title": "Atrašanās vietas Atļaujas liegtas", - "map_settings": "Kartes Iestatījumi", + "map_settings": "Kartes iestatījumi", "map_settings_dark_mode": "Tumšais režīms", "map_settings_date_range_option_day": "Pēdējās 24 stundas", "map_settings_date_range_option_days": "Pēdējās {days} dienas", - "map_settings_date_range_option_year": "Pēdējo gadu", + "map_settings_date_range_option_year": "Pēdējais gads", "map_settings_date_range_option_years": "Pēdējie {years} gadi", "map_settings_dialog_title": "Kartes Iestatījumi", "map_settings_include_show_archived": "Iekļaut Arhivētos", @@ -862,7 +1032,7 @@ "map_settings_theme_settings": "Kartes Dizains", "map_zoom_to_see_photos": "Attāliniet, lai redzētu fotoattēlus", "matches": "Atbilstības", - "media_type": "Multivides veids", + "media_type": "Faila veids", "memories": "Atmiņas", "memories_all_caught_up": "Šobrīd, tas arī viss", "memories_check_back_tomorrow": "Atgriezies rīt, lai skatītu vairāk atmiņu", @@ -879,6 +1049,8 @@ "minute": "Minūte", "minutes": "Minūtes", "missing": "Trūkstošie", + "mobile_app": "Mobilā lietotne", + "mobile_app_download_onboarding_note": "Lejupielādē papildinošo mobilo lietotni, izmantojot šādas izvēles iespējas", "model": "Modelis", "month": "Mēnesis", "monthly_title_text_date_format": "MMMM g", @@ -896,9 +1068,12 @@ "my_albums": "Mani albumi", "name": "Vārds", "name_or_nickname": "Vārds vai iesauka", + "navigate_to_time": "Pāriet uz laiku", "network_requirement_photos_upload": "Izmantot mobilo datu pārraidi, lai dublētu fotoattēlus", "network_requirement_videos_upload": "Izmantot mobilo datu pārraidi, lai dublētu video", "network_requirements": "Tīkla prasības", + "network_requirements_updated": "Tīkla prasības ir mainījušās, atiestata dublēšanas rindu", + "networking_settings": "Tīkla iestatījumi", "networking_subtitle": "Pārvaldīt servera galapunktu iestatījumus", "never": "nekad", "new_album": "Jauns albums", @@ -906,6 +1081,7 @@ "new_password": "Jaunā parole", "new_person": "Jauna persona", "new_pin_code": "Jaunais PIN kods", + "new_timeline": "Jaunā laikjosla", "new_user_created": "Izveidots jauns lietotājs", "new_version_available": "PIEEJAMA JAUNA VERSIJA", "next": "Nākamais", @@ -917,25 +1093,34 @@ "no_archived_assets_message": "Arhivē fotoattēlus un videoklipus, lai paslēptu tos no Fotoattēli skata", "no_assets_message": "NOKLIKŠĶINIET, LAI AUGŠUPIELĀDĒTU SAVU PIRMO FOTOATTĒLU", "no_assets_to_show": "Nav uzrādāmo aktīvu", + "no_cast_devices_found": "Nav atrasta neviena pārraides ierīce", + "no_checksum_local": "Nav pieejama kontrolsumma - nevar iegūt lokālos failus", + "no_checksum_remote": "Nav pieejama kontrolsumma - nevar iegūt attālo failu", "no_duplicates_found": "Dublikāti netika atrasti.", "no_exif_info_available": "Nav pieejama exif informācija", + "no_explore_results_message": "Augšupielādē vairāk fotogrāfiju, lai iepazītu savu kolekciju.", + "no_local_assets_found": "Ar šo kontrolsummu nav atrasts neviens lokālais fails", "no_name": "Nav nosaukuma", "no_notifications": "Nav paziņojumu", "no_places": "Nav atrašanās vietu", "no_results": "Nav rezultātu", "no_results_description": "Izmēģiniet sinonīmu vai vispārīgāku atslēgvārdu", + "not_available": "Nav pieejams", "not_in_any_album": "Nav nevienā albumā", "not_selected": "Nav izvēlēts", + "note_apply_storage_label_to_previously_uploaded assets": "Piezīme: Lai piemērotu glabātuves nosaukumu iepriekš augšupielādētiem failiem, izpildiet", "notes": "Piezīmes", "nothing_here_yet": "Šeit vēl nekā nav", "notification_permission_dialog_content": "Lai iespējotu paziņojumus, atveriet Iestatījumi un atlasiet Atļaut.", "notification_permission_list_tile_content": "Piešķirt atļauju, lai iespējotu paziņojumus.", - "notification_permission_list_tile_enable_button": "Iespējot Paziņojumus", - "notification_permission_list_tile_title": "Paziņojumu Atļaujas", + "notification_permission_list_tile_enable_button": "Iespējot paziņojumus", + "notification_permission_list_tile_title": "Paziņojumu atļaujas", "notification_toggle_setting_description": "Ieslēgt e-pasta paziņojumus", "notifications": "Paziņojumi", "notifications_setting_description": "Paziņojumu pārvaldība", "oauth": "OAuth", + "obtainium_configurator": "Obtainium konfigurētājs", + "obtainium_configurator_instructions": "Lūdzu, izveido API atslēgu un izvēlies variantu, lai izveidotu savu Obtainium konfigurācijas saiti.", "official_immich_resources": "Oficiālie Immich resursi", "offline": "Bezsaistē", "offset": "Nobīde", @@ -972,10 +1157,12 @@ "partner_page_select_partner": "Izvēlēties partneri", "partner_page_shared_to_title": "Kopīgots uz", "partner_page_stop_sharing_content": "{partner} vairs nevarēs piekļūt jūsu fotoattēliem.", + "partner_sharing": "Koplietošana ar partneriem", "partners": "Partneri", "password": "Parole", "password_does_not_match": "Parole nesakrīt", "path": "Ceļš", + "pattern": "Šablons", "pause": "Pauzēt", "pause_memories": "Pauzēt atmiņas", "paused": "Nopauzēts", @@ -1004,6 +1191,7 @@ "please_auth_to_access": "Lai piekļūtu, lūdzu, autentificējieties", "port": "Ports", "preferences_settings_title": "Iestatījumi", + "preparing": "Sagatavo", "preview": "Priekšskatījums", "previous": "Iepriekšējais", "previous_memory": "Iepriekšējā atmiņa", @@ -1013,12 +1201,8 @@ "privacy": "Privātums", "profile": "Profils", "profile_drawer_app_logs": "Žurnāli", - "profile_drawer_client_out_of_date_major": "Mobilā lietotne ir novecojusi. Lūdzu, atjaunini to uz jaunāko pamatversiju.", - "profile_drawer_client_out_of_date_minor": "Mobilā lietotne ir novecojusi. Lūdzu, atjaunini to uz jaunāko papildversiju.", "profile_drawer_client_server_up_to_date": "Klients un serveris ir atjaunināti", "profile_drawer_github": "GitHub", - "profile_drawer_server_out_of_date_major": "Serveris ir novecojis. Lūdzu, atjaunini to uz jaunāko pamatversiju.", - "profile_drawer_server_out_of_date_minor": "Serveris ir novecojis. Lūdzu, atjaunini to uz jaunāko papildversiju.", "profile_image_of_user": "{user} profila attēls", "profile_picture_set": "Profila attēls iestatīts.", "public_album": "Publisks albums", @@ -1059,6 +1243,7 @@ "rating_description": "Rādīt EXIF vērtējumu informācijas panelī", "reaction_options": "Reakcijas iespējas", "read_changelog": "Lasīt izmaiņu sarakstu", + "ready_for_upload": "Gatavs augšupielādei", "recently_added_page_title": "Nesen Pievienotais", "refresh": "Atsvaidzināt", "refresh_faces": "Atsvaidzināt sejas", @@ -1068,8 +1253,10 @@ "refreshes_every_file": "Vēlreiz nolasa esošos un jaunos failus", "refreshing_faces": "Atsvaidzina sejas", "refreshing_metadata": "Atsvaidzina metadatus", + "remote": "Attāli", "remove": "Noņemt", "remove_assets_title": "Izņemt failus?", + "remove_custom_date_range": "Novākt pielāgoto datuma intervālu", "remove_deleted_assets": "Izņemt dzēstos failus", "remove_from_album": "Noņemt no albuma", "remove_from_album_action_prompt": "No albuma izņemti {count} faili", @@ -1084,6 +1271,8 @@ "removed_from_archive": "Noņēma no arhīva", "removed_from_favorites": "Noņēma no izlases", "removed_from_favorites_count": "{count, plural, other {Izņēma #}} no izlases", + "removed_memory": "Noņēma atmiņu", + "removed_photo_from_memory": "Noņēma fotogrāfiju no atmiņas", "rename": "Pārsaukt", "repair": "Remonts", "replace_with_upload": "Aizstāt ar augšupielādi", @@ -1094,6 +1283,7 @@ "reset_password": "Atiestatīt paroli", "reset_people_visibility": "Atiestatīt personu redzamību", "reset_pin_code": "Atiestatīt PIN kodu", + "reset_sqlite": "Atiestatīt SQLite datubāzi", "reset_to_default": "Atiestatīt noklusējuma iestatījumus", "resolve_duplicates": "Atrisināt dublēšanās gadījumus", "resolved_all_duplicates": "Visi dublikāti ir atrisināti", @@ -1142,6 +1332,7 @@ "search_for_existing_person": "Meklēt esošu personu", "search_no_people": "Nav personu", "search_no_people_named": "Nav personas ar vārdu \"{name}\"", + "search_options": "Meklēšanas iespējas", "search_page_categories": "Kategorijas", "search_page_motion_photos": "Kustību Fotoattēli", "search_page_no_objects": "Informācija par Objektiem nav pieejama", @@ -1153,11 +1344,15 @@ "search_page_your_activity": "Jūsu aktivitāte", "search_page_your_map": "Jūsu Karte", "search_people": "Meklēt personas", + "search_rating": "Meklēt pēc vērtējuma...", "search_result_page_new_search_hint": "Jauns Meklējums", + "search_settings": "Meklēt iestatījumos", + "search_state": "Meklēt pēc štata...", "search_suggestion_list_smart_search_hint_1": "Viedā meklēšana pēc noklusējuma ir iespējota, lai meklētu metadatos, izmanto sintaksi ", "search_suggestion_list_smart_search_hint_2": "m:jūsu-meklēšanas-frāze", "search_type": "Meklēšanas veids", - "search_your_photos": "Meklēt Jūsu fotoattēlus", + "search_your_photos": "Meklēt fotoattēlos", + "searching_locales": "Meklē lokalizācijas...", "second": "Sekunde", "see_all_people": "Skatīt visas personas", "select_album_cover": "Izvēlieties albuma vāciņu", @@ -1178,7 +1373,10 @@ "server_online": "Serveris tiešsaistē", "server_privacy": "Servera privātums", "server_stats": "Servera statistika", + "server_update_available": "Pieejams servera atjauninājums", "server_version": "Servera versija", + "set_as_album_cover": "Iestatīt kā albuma vāciņu", + "set_as_profile_picture": "Iestatīt kā profila attēlu", "set_date_of_birth": "Iestatīt dzimšanas datumu", "setting_image_viewer_help": "Detaļu skatītājs vispirms ielādē mazo sīktēlu, pēc tam ielādē vidēja lieluma priekšskatījumu (ja iespējots), visbeidzot ielādē oriģinālu (ja iespējots).", "setting_image_viewer_original_subtitle": "Iespējot sākotnējā pilnas izšķirtspējas attēla (liels!) ielādi. Atspējot, lai samazinātu datu lietojumu (gan tīklā, gan ierīces kešatmiņā).", @@ -1193,19 +1391,22 @@ "setting_notifications_notify_minutes": "{count} minūtes", "setting_notifications_notify_never": "nekad", "setting_notifications_notify_seconds": "{count} sekundes", - "setting_notifications_single_progress_subtitle": "Detalizēta augšupielādes progresa informācija par katru aktīvu", + "setting_notifications_single_progress_subtitle": "Detalizēta augšupielādes progresa informācija par katru failu", "setting_notifications_single_progress_title": "Rādīt fona dublējuma detalizēto progresu", "setting_notifications_subtitle": "Paziņojumu preferenču pielāgošana", "setting_notifications_total_progress_subtitle": "Kopējais augšupielādes progress (pabeigti/kopējie faili)", "setting_notifications_total_progress_title": "Rādīt fona dublējuma kopējo progresu", + "setting_video_viewer_auto_play_subtitle": "Automātiski sākt videoklipu atskaņošanu, kad tie tiek atvērti", + "setting_video_viewer_auto_play_title": "Automātiska video atskaņošana", "setting_video_viewer_looping_title": "Cikliski", "setting_video_viewer_original_video_subtitle": "Straumējot video no servera, izmantot oriģinālu, pat ja ir pieejama pārkodēšana. Tas var izraisīt buferēšanu. Lokāli pieejamie video tiek atskaņoti oriģinālajā kvalitātē, neatkarīgi no šīs iestatījuma.", + "setting_video_viewer_original_video_title": "Vienmēr izmantot oriģinālo video", "settings": "Iestatījumi", "settings_require_restart": "Lūdzu, restartējiet Immich, lai lietotu šo iestatījumu", "setup_pin_code": "Uzstādīt PIN kodu", "share": "Kopīgot", "share_add_photos": "Pievienot fotoattēlus", - "share_assets_selected": "{count} izvēlēti", + "share_assets_selected": "{count} atlasīti", "share_dialog_preparing": "Notiek sagatavošana...", "shared": "Kopīgots", "shared_album_activities_input_disable": "Komentāri atslēgti", @@ -1286,7 +1487,7 @@ "slideshow_settings": "Slīdrādes iestatījumi", "sort_albums_by": "Kārtot albumus pēc...", "sort_created": "Izveides datums", - "sort_items": "Vienību skaits", + "sort_items": "Vienumu skaits", "sort_modified": "Izmaiņu datums", "sort_newest": "Jaunākā fotogrāfija", "sort_oldest": "Vecākā fotogrāfija", @@ -1295,24 +1496,29 @@ "sort_title": "Nosaukums", "source": "Pirmkods", "stack": "Apvienot kaudzē", + "start": "Sākt", "start_date": "Sākuma datums", + "start_date_before_end_date": "Sākuma datumam jābūt pirms beigu datuma", "state": "Štats", "status": "Statuss", "stop_photo_sharing": "Beigt kopīgot jūsu fotogrāfijas?", "stop_photo_sharing_description": "{partner} vairs nevarēs piekļūt tavām fotogrāfijām.", "stop_sharing_photos_with_user": "Pārtraukt dalīties ar fotogrāfijām ar šo lietotāju", "storage": "Vieta krātuvē", + "storage_label": "Glabātuves nosaukums", "storage_usage": "{used} no {available} izmantoti", "submit": "Iesniegt", "suggestions": "Ieteikumi", "sunrise_on_the_beach": "Saullēkts pludmalē", "support": "Atbalsts", "support_and_feedback": "Atbalsts un atsauksmes", + "support_third_party_description": "Tavu Immich instalāciju ir sagatavojusi trešā puse. Problēmas, ar kurām sastopies, var būt saistītas ar šo pakotni, tāpēc lūdzu vispirms ziņo par tām, izmantojot zemāk norādītās saites.", "sync": "Sinhronizēt", + "sync_local": "Sinhronizēt lokāli", "sync_status": "Sinhronizācijas statuss", "sync_status_subtitle": "Skatīt un pārvaldīt sinhronizācijas sistēmu", "theme": "Dizains", - "theme_setting_asset_list_storage_indicator_title": "Rādīt krātuves indikatoru uz aktīvu elementiem", + "theme_setting_asset_list_storage_indicator_title": "Rādīt krātuves indikatoru uz attēliem režga skatā", "theme_setting_asset_list_tiles_per_row_title": "Failu skaits rindā ({count})", "theme_setting_colorful_interface_subtitle": "Piemērot pamatkrāsu fona virsmām.", "theme_setting_colorful_interface_title": "Krāsaina saskarne", @@ -1344,7 +1550,7 @@ "trash_emptied": "Atkritne iztukšota", "trash_no_results_message": "Šeit parādīsies uz atkritni pārvietotās fotogrāfijas un video.", "trash_page_delete_all": "Dzēst Visu", - "trash_page_empty_trash_dialog_content": "Vai vēlaties iztukšot savus izmestos aktīvus? Tie tiks neatgriezeniski izņemti no Immich", + "trash_page_empty_trash_dialog_content": "Vai vēlaties iztukšot savus izmestos failus? Tie tiks neatgriezeniski izņemti no Immich", "trash_page_info": "Atkritnes vienumi tiks neatgriezeniski dzēsti pēc {days} dienām", "trash_page_no_assets": "Atkritnē nav aktīvu", "trash_page_restore_all": "Atjaunot Visu", @@ -1364,7 +1570,9 @@ "unlimited": "Neierobežots", "unnamed_album": "Albums bez nosaukuma", "unsaved_change": "Nesaglabāta izmaiņa", + "unselect_all": "Atcelt visu atlasi", "unstack": "At-Stekot", + "update_location_action_prompt": "Norādīt {count} izvēlēto failu atrašanās vietu kā:", "updated_at": "Atjaunināts", "updated_password": "Parole ir atjaunināta", "upload": "Augšupielādēt", @@ -1404,6 +1612,7 @@ "version_history": "Versiju vēsture", "version_history_item": "{version} uzstādīta {date}", "video": "Videoklips", + "video_hover_setting_description": "Atskaņot video sīktēlu, kad peles kursors atrodas virs objekta. Pat ja funkcija ir atspējota, atskaņošanu var sākt, uzvirzot kursoru uz atskaņošanas ikonas.", "videos": "Videoklipi", "view": "Apskatīt", "view_album": "Skatīt Albumu", diff --git a/i18n/mk.json b/i18n/mk.json index 86694ce346..e81694e63e 100644 --- a/i18n/mk.json +++ b/i18n/mk.json @@ -2,7 +2,7 @@ "about": "За Immich", "account": "Профил", "account_settings": "Поставки за профилот", - "acknowledge": "Прочитано", + "acknowledge": "Маркирај прочитано", "action": "Акција", "action_common_update": "Ажурирај", "actions": "Акции", @@ -15,6 +15,7 @@ "add_a_name": "Додади име", "add_a_title": "Додади наслов", "add_birthday": "Додади роденден", + "add_endpoint": "Додади крајна точка", "add_exclusion_pattern": "Додади шаблон за исклучување", "add_import_path": "Додади патека за импортирање", "add_location": "Додади локација", @@ -22,12 +23,17 @@ "add_partner": "Додади партнер", "add_path": "Додади патека", "add_photos": "Додади слики", + "add_tag": "Додади ознака", "add_to": "Додади во…", "add_to_album": "Додади во албум", "add_to_album_bottom_sheet_added": "Додадено во {album}", + "add_to_album_bottom_sheet_already_exists": "Веќе во {album}", + "add_to_album_bottom_sheet_some_local_assets": "Некои локални ресурси не можеа да се додадат во албумот", + "add_to_album_toggle": "Промени ја селекцијата за {album}", "add_to_albums": "Додади во албуми", "add_to_albums_count": "Додади во албуми ({count})", "add_to_shared_album": "Додади во споделен албум", + "add_upload_to_stack": "Додај прикаченото во куп", "add_url": "Додади URL", "added_to_archive": "Додадено во архива", "added_to_favorites": "Додадено во омилени", @@ -44,8 +50,15 @@ "backup_database": "Креирај резервна копија од базата на податоци", "backup_database_enable_description": "Овозможи резервни копии од базата на податоци", "backup_keep_last_amount": "Количина на претходни резервни копии за чување", - "backup_settings": "Поставки за резервни копии", - "backup_settings_description": "Управувај со поставки за резервни копии на базата на податоци", + "backup_onboarding_1_description": "надворешна копија во облакот или на друга физичка локација.", + "backup_onboarding_2_description": "локални копии на различни уреди. Ова ги вклучува и основните фјалови и резервна копија од истите фајлови локално.", + "backup_onboarding_3_description": "сите копии од твоите податоци, вклучувајќи и оргиналните фајлови. Ова вклучува и 1 надворешна копија и 2 локални копии.", + "backup_onboarding_description": "3-2-1 стратегија за резервна копија е препорачано за да ги заштити твоите податоци. Потребно е да чуваш резервни копии од твоите прикачени фотографии/видеа како и базата за податоци на Immich за целосно решение за зачувување на резервна копија", + "backup_onboarding_footer": "Повеќе информации околу правење резервни копии за Immich, ве молам да се референцирате на документацијата", + "backup_onboarding_parts_title": "3-2-1 резервна копија вклучува:", + "backup_onboarding_title": "Резервни копии", + "backup_settings": "Поставки извезување база на податоци", + "backup_settings_description": "Управувај со поставки за извезување на базата на податоци", "cleared_jobs": "Исчистени задачи за: {job}", "config_set_by_file": "Конгигурацијата е моментално поставена од конфигурациска датотека", "confirm_delete_library": "Дали сте сигурни дека сакате да ја избришете библиотеката {library}?", @@ -53,19 +66,40 @@ "confirm_email_below": "За да потврдите, внесете \"{email}\" доле", "confirm_reprocess_all_faces": "Дали сте сигурни дека сакате да се обработат одново сите лица? Ова ќе ги избрише и сите именувани луѓе.", "confirm_user_password_reset": "Дали сте сигурни дека сакате да се поништи лозинката на {user}?", + "confirm_user_pin_code_reset": "Дали сигурно сакаш да го смените ПИН кодот за {user}", "create_job": "Создади задача", "cron_expression": "Cron израз", "cron_expression_description": "Подеси го интервалот на скенирање користејќи го cron форматот. За повеќе информации погледнете на пр. Crontab Guru", "cron_expression_presets": "Предефинирани Cron изрази", "disable_login": "Оневозможи најава", "duplicate_detection_job_description": "Пушти машинско учење на средствата за да се откријат слични слики. Се потпира на Smart Search", + "external_library_management": "Менаџмент на Надворешна Библиотека", + "face_detection": "Детекција на лице", "force_delete_user_warning": "ПРЕДУПРЕДУВАЊЕ: Ова веднаш ќе го отстрани корисникот и сите средства. Оваа акција не може да се поништи и датотеките нема да може да се вратат назад.", "image_format": "Формат", + "image_format_description": "WebP создава помали фајлви отколку JPEG, но е по спор при енкодирање.", + "image_fullsize_enabled": "Овозможи целосна-големина на генерирање на слика", + "image_fullsize_quality_description": "Целосна-големина на слика со квалитет од 1-100. Повисокто е подобро, но создава поголеми фајлови.", + "image_fullsize_title": "Поставки за Целосна-големина на Слика", + "image_prefer_embedded_preview": "Претпочитан вграден преглед", + "image_preview_title": "Поставки за Преглед", "image_quality": "Квалитет", "image_resolution": "Резолуција", "image_settings": "Поставки за слики", + "job_concurrency": "{job} конкурентност", + "job_created": "Креирана задача", + "job_not_concurrency_safe": "Оваа задача не е конкуретно-безбедна.", + "job_settings": "Поставки за задача", + "job_settings_description": "Управувај со конкурентност на задачи", + "job_status": "Статус на задачи", + "library_created": "Креирана библиотека: {library}", + "library_deleted": "Библиотеката е избришана", + "library_import_path_description": "Предложи папка за внес. Оваа папка, вклучува и под папки, ќе биде скенирана за слики и видеа.", "library_scanning": "Периодично скенирање", + "library_scanning_description": "Подеси периодично скениранје на библиотеката", + "library_scanning_enable_description": "Овозможи периодично скениранје на библиотеката", "library_settings": "Екстерна библиотека", + "library_settings_description": "Управувај со подесувањата за надворешната библиотека", "logging_enable_description": "Вклучи евидентирање", "logging_settings": "Евидентирање", "map_dark_style": "Темен стил", @@ -164,7 +198,6 @@ "edit_location": "Уреди локација", "edit_people": "Уреди луѓе", "edit_user": "Уреди корисник", - "edited": "Уредено", "editor": "Уредувач", "editor_crop_tool_h2_rotation": "Ротација", "email": "Е-пошта", diff --git a/i18n/ml.json b/i18n/ml.json index 1ef0b46e66..ec9eba2794 100644 --- a/i18n/ml.json +++ b/i18n/ml.json @@ -1,87 +1,2128 @@ { - "about": "വിഷയത്തെക്കുറിച്ച്", + "about": "കുറിച്ച്", "account": "അക്കൗണ്ട്", - "account_settings": "അക്കൗണ്ട് സെറ്റിംഗ്സ്", + "account_settings": "അക്കൗണ്ട് ക്രമീകരണങ്ങൾ", "acknowledge": "അംഗീകരിക്കുക", - "action": "ആക്ഷന്‍", - "action_common_update": "പുതുക്കുക", + "action": "പ്രവർത്തനം", + "action_common_update": "അപ്ഡേറ്റ് ചെയ്യുക", "actions": "പ്രവർത്തികൾ", - "active": "സജീവമായവ", - "activity": "പ്രവർത്തനങ്ങൾ", + "active": "സജീവം", + "activity": "പ്രവർത്തനം", + "activity_changed": "പ്രവർത്തനം {enabled, select, true {പ്രവർത്തനക്ഷമമാക്കി} other {നിർജ്ജീവമാക്കി}}", "add": "ചേർക്കുക", - "add_a_description": "ഒരു വിവരണം ചേർക്കുക", - "add_a_location": "ഒരു സ്ഥലം ചേർക്കുക", - "add_a_name": "ഒരു പേര് ചേർക്കുക", - "add_a_title": "ഒരു ശീർഷകം ചേർക്കുക", + "add_a_description": "വിവരണം ചേർക്കുക", + "add_a_location": "സ്ഥാനം ചേർക്കുക", + "add_a_name": "പേര് ചേർക്കുക", + "add_a_title": "ശീർഷകം ചേർക്കുക", "add_birthday": "ജന്മദിനം ചേർക്കുക", - "add_endpoint": "എൻഡ്പോയിന്റ് ചേർക്കുക", - "add_exclusion_pattern": "ഒഴിവാക്കാനുള്ള മാതൃക ചേർക്കുക", - "add_import_path": "ഇറക്കുമതി ചെയ്യുക", - "add_location": "സ്ഥലനാമം ചേര്‍ക്കുക", - "add_more_users": "കൂടുതല്‍ ഉപയോക്താക്കളെ ചേര്‍ക്കുക", - "add_partner": "പങ്കാളിയെ ചേര്‍ക്കുക", - "add_path": "പാത ചേര്‍ക്കുക", - "add_photos": "ചിത്രങ്ങള്‍ ചേര്‍ക്കുക", - "add_tag": "ടാഗ് ചേര്‍ക്കുക", - "add_to": "ചേര്‍ക്കുക…", - "add_to_album": "ആല്‍ബത്തിലേക്ക് ചേര്‍ക്കുക", - "add_to_album_bottom_sheet_added": "{album} - ലേക്ക് ചേര്‍ത്തു", - "add_to_album_bottom_sheet_already_exists": "{album} ആൽബത്തിൽ ഇപ്പോള്‍ തന്നെ ഉണ്ട്", - "add_to_albums": "ആൽബങ്ങളിൽ ചേർക്കുക", - "add_to_albums_count": "ആൽബങ്ങളിൽ ചേർക്കുക ({count})", + "add_endpoint": "എൻഡ്‌പോയിന്റ് ചേർക്കുക", + "add_exclusion_pattern": "ഒഴിവാക്കൽ പാറ്റേൺ ചേർക്കുക", + "add_import_path": "ഇമ്പോർട്ട് പാത്ത് ചേർക്കുക", + "add_location": "സ്ഥാനം ചേർക്കുക", + "add_more_users": "കൂടുതൽ ഉപയോക്താക്കളെ ചേർക്കുക", + "add_partner": "പങ്കാളിയെ ചേർക്കുക", + "add_path": "പാത്ത് ചേർക്കുക", + "add_photos": "ഫോട്ടോകൾ ചേർക്കുക", + "add_tag": "ടാഗ് ചേർക്കുക", + "add_to": "...ലേക്ക് ചേർക്കുക", + "add_to_album": "ആൽബത്തിലേക്ക് ചേർക്കുക", + "add_to_album_bottom_sheet_added": "{album} എന്നതിലേക്ക് ചേർത്തു", + "add_to_album_bottom_sheet_already_exists": "{album}-ൽ ഇതിനകം തന്നെയുണ്ട്", + "add_to_album_bottom_sheet_some_local_assets": "ചില പ്രാദേശിക അസറ്റുകൾ ആൽബത്തിലേക്ക് ചേർക്കാൻ കഴിഞ്ഞില്ല", + "add_to_album_toggle": "{album}-നുള്ള തിരഞ്ഞെടുപ്പ് ടോഗിൾ ചെയ്യുക", + "add_to_albums": "ആൽബങ്ങളിലേക്ക് ചേർക്കുക", + "add_to_albums_count": "ആൽബങ്ങളിലേക്ക് ചേർക്കുക ({count})", "add_to_shared_album": "പങ്കിട്ട ആൽബത്തിലേക്ക് ചേർക്കുക", - "add_url": "URL ചേര്‍ക്കുക", - "added_to_archive": "ചരിത്രരേഖയായി (ആര്‍ക്കൈവ്) ചേര്‍ത്തിരിക്കുന്നു", - "added_to_favorites": "ഇഷ്ടപ്പെട്ടവയിലേക്ക് ചേര്‍ത്തു", - "added_to_favorites_count": "{count, number} ഇഷ്ടപ്പെട്ടവയിലേക്ക് ചേര്‍ത്തു", + "add_url": "URL ചേർക്കുക", + "added_to_archive": "ആർക്കൈവിലേക്ക് ചേർത്തു", + "added_to_favorites": "പ്രിയപ്പെട്ടവയിലേക്ക് ചേർത്തു", + "added_to_favorites_count": "{count, number} എണ്ണം പ്രിയപ്പെട്ടവയിലേക്ക് ചേർത്തു", "admin": { - "add_exclusion_pattern_description": "ഒഴിവാക്കൽ ചിഹ്നങ്ങള്‍ ചേർക്കുക. *, **, ? എന്നിവ ഉപയോഗിച്ചുള്ള ഗ്ലോബിംഗ് പിന്തുണയ്ക്കുന്നു. \"Raw\" എന്ന് പേരുള്ള ഏതെങ്കിലും ഡയറക്ടറിയിലെ എല്ലാ ഫയലുകളും അവഗണിക്കാൻ, \"**/Raw/**\" ഉപയോഗിക്കുക. \".tif\" ൽ അവസാനിക്കുന്ന എല്ലാ ഫയലുകളും അവഗണിക്കാൻ, \"**/*.tif\" ഉപയോഗിക്കുക. ഒരു പരിപൂർണ്ണമായ പാത അവഗണിക്കാൻ, \"/path/to/ignore/**\" ഉപയോഗിക്കുക.", - "admin_user": "ഭരണാധികാരി", - "asset_offline_description": "ഈ പുറത്തുള്ള ശേഖരത്തിലെ വസ്തുക്കള്‍ ഇനി ഡിസ്കിൽ കാണുന്നില്ല, അവയെ ട്രാഷിലേക്ക് നീക്കിയിരിക്കുന്നു. ഫയൽ ലൈബ്രറിക്കുള്ളിൽ നിന്ന് നീക്കിയിട്ടുണ്ടെങ്കിൽ, പുതിയ അനുബന്ധ വസ്തുവിനായി നിങ്ങളുടെ സമയക്രമം (ടൈംലൈന്‍) പരിശോധിക്കുക. ഈ വസ്തു പുനഃസ്ഥാപിക്കാൻ, താഴെയുള്ള ഫയൽ പാത്ത് ഇമ്മിച്ചിന് എത്തിപ്പെടാന്‍ കഴിയുമെന്ന് ഉറപ്പാക്കുകയും ശേഖരം പുനഃപരിശോധിക്കുകയും (സ്കാൻ) ചെയ്യുക.", - "authentication_settings": "ആധികാരികതാ സജ്ജീകരണങ്ങൾ", - "authentication_settings_description": "പാസ്സ്‌വേര്‍ഡ്‌, OAuth തുടങ്ങിയ സജ്ജീകരണങ്ങള്‍", - "authentication_settings_disable_all": "എല്ലാ പ്രവേശന (ലോഗിൻ) രീതികളും പ്രവർത്തനരഹിതമാക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ? പ്രവേശനങ്ങള്‍ പൂർണ്ണമായും പ്രവർത്തനരഹിതമാക്കപ്പെടും.", - "background_task_job": "പശ്ചാത്തല പ്രവര്‍ത്തികള്‍", - "backup_database": "ഡാറ്റാബേസ് ഡംമ്പ് ഉണ്ടാക്കുക", - "backup_database_enable_description": "ഡാറ്റാബേസ് ഡമ്പുകൾ പ്രാപ്തമാക്കുക", - "backup_keep_last_amount": "പഴയ ഡാറ്റാബേസ് ഡമ്പുകൾ എത്രയെണ്ണം സൂക്ഷിക്കണം", - "backup_settings": "ഡാറ്റാബേസ് ഡമ്പ് ക്രമീകരണങ്ങള്‍", - "backup_settings_description": "ഡാറ്റാബേസ് ഡമ്പ് ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക.", - "cleared_jobs": "{job} - ന്‍റെ ജോലികള്‍ മായ്ച്ചിരിക്കുന്നു", - "config_set_by_file": "ക്രമീകരണങ്ങള്‍ ഇപ്പോള്‍ ഒരു ക്രമീകരണ ഫയല്‍ വഴിയാണ് നിശ്ചയിക്കുന്നത്", - "confirm_delete_library": "{library} മായ്ച്ചു കളയണം എന്നുറപ്പാണോ?", - "confirm_delete_library_assets": "ഈ ശേഖരം ഇല്ലാതാക്കണം എന്ന് ഉറപ്പാണോ? ഇത് ഇമ്മിച്ചിൽ നിന്ന് {count, plural, one {# contained asset} other {all # contained assets}} ഇല്ലാതാക്കും, ഇത് പഴയപടിയാക്കാൻ കഴിയില്ല. ഫയലുകൾ ഡിസ്കിൽ തന്നെ തുടരും.", - "confirm_email_below": "തീര്‍ച്ചപ്പെടുത്താന്‍ {email} താഴെ കൊടുക്കുക", - "confirm_reprocess_all_faces": "എല്ലാ മുഖങ്ങളും വീണ്ടും കണ്ടെത്തണം എന്ന് ഉറപ്പാണോ? ഇത് ഇതിനകം പേരു ചേര്‍ത്ത മുഖങ്ങളെയും ആളുകളെയും മായ്ക്കും.", - "confirm_user_password_reset": "{user} എന്ന ഉപയോക്താവിന്‍റെ പാസ്സ്‌വേര്‍ഡ്‌ പുനഃക്രമീകരിക്കണം എന്നുറപ്പാണോ?", - "confirm_user_pin_code_reset": "{user} എന്ന ഉപയോക്താവിന്‍റെ PIN പുനഃക്രമീകരിക്കണം എന്നുറപ്പാണോ?", - "create_job": "ജോലി സൃഷ്ടിക്കുക", - "cron_expression": "ക്രോണ്‍ (cron) പ്രയോഗശൈലി", - "cron_expression_description": "ക്രോൺ ഫോർമാറ്റ് ഉപയോഗിച്ച് സ്കാനിംഗ് ഇടവേള സജ്ജമാക്കുക. കൂടുതൽ വിവരങ്ങൾക്ക് Crontab Guru സന്ദര്‍ശിക്കുക", - "cron_expression_presets": "മുന്‍കൂട്ടി തയ്യാര്‍ ചെയ്ത ക്രോണ്‍ പ്രവര്‍ത്തനശൈലികള്‍", - "disable_login": "ലോഗിന്‍ തടയുക", - "duplicate_detection_job_description": "സമാനമായ ചിത്രങ്ങൾ കണ്ടെത്താൻ വസ്തുവഹകളില്‍ യന്ത്രപഠനം പ്രവർത്തിപ്പിക്കുക. ഇത് സ്മാർട്ട് സര്‍ച്ചിനെ ആശ്രയിക്കുന്നു", - "exclusion_pattern_description": "നിങ്ങളുടെ ലൈബ്രറി സ്കാൻ ചെയ്യുമ്പോൾ ഫയലുകളും ഫോൾഡറുകളും അവഗണിക്കാൻ ഒഴിവാക്കല്‍ മാതൃകകള്‍ നിങ്ങളെ അനുവദിക്കുന്നു. RAW ഫയലുകൾ പോലുള്ള നിങ്ങൾക്ക് ഇറക്കുമതി ചെയ്യാൻ താൽപ്പര്യമില്ലാത്ത ഫയലുകൾ അടങ്ങിയ ഫോൾഡറുകൾ ഉണ്ടെങ്കിൽ ഇത് ഉപയോഗപ്രദമാണ്.", - "external_library_management": "ബാഹ്യമായശേഖരങ്ങളുടെ നിയന്ത്രണം", - "face_detection": "മുഖങ്ങള്‍ കണ്ടെത്തുക", - "face_detection_description": "യന്ത്രപഠനം ഉപയോഗിച്ച് വസ്തുക്കളിലെ മുഖങ്ങൾ കണ്ടെത്തുക. വീഡിയോകൾക്ക്, തംബ്‌നെയിൽ മാത്രമേ പരിഗണിക്കൂ. \"Refresh\" എല്ലാ വസ്തുക്കളും (വീണ്ടും) പ്രോസസ്സ് ചെയ്യുന്നു. കൂടാതെ \"Reset\" നിലവിലുള്ള എല്ലാ മുഖളും വിവരങ്ങളും മായ്‌ക്കുന്നു. \"Missing\" ഇതുവരെ ക്രമീകരിക്കാത്ത വസ്തുക്കളെ വരിയിലേക്ക് നിർത്തുന്നു. മുഖം തിരിച്ചറിയൽ പൂർത്തിയായ ശേഷം കണ്ടെത്തിയ മുഖങ്ങൾ മുഖം തിരിച്ചറിയലിനായി ക്യൂവിൽ നിർത്തും, അവയെ നിലവിലുള്ളതോ പുതിയതോ ആയ ആളുകളിലേക്ക് ഗ്രൂപ്പുചെയ്യും.", - "facial_recognition_job_description": "കണ്ടെത്തിയ മുഖങ്ങളെ ആളുകളുടെ കൂട്ടം ആക്കുക. മുഖം കണ്ടെത്തല്‍ ഘട്ടത്തിനു ശേഷമേ ഇത് ഉണ്ടാകൂ. \"Reset\" വീണ്ടും കൂട്ടങ്ങളെ ഉണ്ടാക്കും. \"Missing\" ആളെ നിയോഗിക്കാത്ത മുഖങ്ങളെ വരിയിലേക്ക് ചേര്‍ക്കുന്നു.", - "failed_job_command": "{job} എന്ന ജോലിക്ക് വേണ്ടിയുള്ള ആജ്ഞ {command} പരാജയപ്പെട്ടിരിക്കുന്നു", - "force_delete_user_warning": "മുന്നറിയിപ്പ്: ഇത് ഉപയോക്താവിനെയും എല്ലാ വസ്തുക്കളേയും ഉടനടി നീക്കം ചെയ്യും. ഇത് പഴയപടിയാക്കാനോ ഫയലുകൾ വീണ്ടെടുക്കാനോ കഴിയില്ല.", - "image_format": "ഘടന", - "image_format_description": "WebP ഉണ്ടാക്കാന്‍ സമയം എടുക്കും എങ്കിലും JPEG ഫയലുകളെക്കാള്‍ ചെറുതായിരിക്കും.", - "image_fullsize_description": "അധികവിവരങ്ങള്‍ ഒഴിവാക്കിയ ചിത്രം, വലുതാക്കി കാണിക്കുമ്പോള്‍ ഉപയോഗിക്കപ്പെടുന്നു", - "image_fullsize_enabled": "പൂര്‍ണ വലുപ്പത്തില്‍ ഉള്ള ചിത്രങ്ങള്‍ ഉണ്ടാക്കാന്‍പ്രാപ്തമാക്കുക", - "image_fullsize_quality_description": "1 മുതൽ 100 വരെയുള്ള പൂർണ്ണ വലുപ്പത്തിലുള്ള ഇമേജ് നിലവാരം. ഉയർന്നതാണ് നല്ലത്, പക്ഷേ വലിയ ഫയലുകൾ ഉത്പാദിപ്പിക്കുന്നു.", - "image_fullsize_title": "പൂർണ്ണ വലുപ്പത്തിലുള്ള ഇമേജ് ക്രമീകരണങ്ങൾ", - "image_quality": "ഗുണനിലവാരം", + "add_exclusion_pattern_description": "ഒഴിവാക്കൽ പാറ്റേണുകൾ ചേർക്കുക. *, **, ? എന്നിവ ഉപയോഗിച്ചുള്ള ഗ്ലോബിംഗ് പിന്തുണയ്ക്കുന്നു. \"Raw\" എന്ന് പേരുള്ള ഏതെങ്കിലും ഡയറക്ടറിയിലെ എല്ലാ ഫയലുകളും ഒഴിവാക്കാൻ, \"**/Raw/**\" ഉപയോഗിക്കുക. \".tif\"-ൽ അവസാനിക്കുന്ന എല്ലാ ഫയലുകളും ഒഴിവാക്കാൻ, \"**/*.tif\" ഉപയോഗിക്കുക. ഒരു കേവല പാത്ത് ഒഴിവാക്കാൻ, \"/path/to/ignore/**\" ഉപയോഗിക്കുക.", + "admin_user": "അഡ്മിൻ ഉപയോക്താവ്", + "asset_offline_description": "ഈ എക്സ്റ്റേണൽ ലൈബ്രറി അസറ്റ് ഇപ്പോൾ ഡിസ്കിൽ ലഭ്യമല്ല, അത് ട്രാഷിലേക്ക് മാറ്റിയിരിക്കുന്നു. ഫയൽ ലൈബ്രറിക്കുള്ളിൽ നീക്കിയിട്ടുണ്ടെങ്കിൽ, പുതിയ അനുബന്ധ അസറ്റിനായി നിങ്ങളുടെ ടൈംലൈൻ പരിശോധിക്കുക. ഈ അസറ്റ് പുനഃസ്ഥാപിക്കാൻ, താഴെയുള്ള ഫയൽ പാത Immich-ന് ആക്സസ് ചെയ്യാൻ കഴിയുമെന്ന് ഉറപ്പാക്കുകയും ലൈബ്രറി സ്കാൻ ചെയ്യുകയും ചെയ്യുക.", + "authentication_settings": "പ്രാമാണീകരണ ക്രമീകരണങ്ങൾ", + "authentication_settings_description": "പാസ്‌വേഡ്, OAuth, മറ്റ് പ്രാമാണീകരണ ക്രമീകരണങ്ങൾ എന്നിവ കൈകാര്യം ചെയ്യുക", + "authentication_settings_disable_all": "എല്ലാ ലോഗിൻ രീതികളും പ്രവർത്തനരഹിതമാക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ? ലോഗിൻ പൂർണ്ണമായും പ്രവർത്തനരഹിതമാകും.", + "authentication_settings_reenable": "വീണ്ടും പ്രവർത്തനക്ഷമമാക്കാൻ, ഒരു സെർവർ കമാൻഡ് ഉപയോഗിക്കുക.", + "background_task_job": "പശ്ചാത്തല ജോലികൾ", + "backup_database": "ഡാറ്റാബേസ് ഡംപ് ഉണ്ടാക്കുക", + "backup_database_enable_description": "ഡാറ്റാബേസ് ഡംപുകൾ പ്രവർത്തനക്ഷമമാക്കുക", + "backup_keep_last_amount": "സൂക്ഷിക്കേണ്ട പഴയ ഡംപുകളുടെ എണ്ണം", + "backup_onboarding_1_description": "ക്ലൗഡിലോ മറ്റൊരു ഭൗതിക സ്ഥാനത്തോ ഉള്ള ഓഫ്‌സൈറ്റ് പകർപ്പ്.", + "backup_onboarding_2_description": "വിവിധ ഉപകരണങ്ങളിലെ പ്രാദേശിക പകർപ്പുകൾ. ഇതിൽ പ്രധാന ഫയലുകളും ആ ഫയലുകളുടെ പ്രാദേശിക ബാക്കപ്പും ഉൾപ്പെടുന്നു.", + "backup_onboarding_3_description": "യഥാർത്ഥ ഫയലുകൾ ഉൾപ്പെടെ നിങ്ങളുടെ ഡാറ്റയുടെ ആകെ പകർപ്പുകൾ. ഇതിൽ 1 ഓഫ്‌സൈറ്റ് പകർപ്പും 2 പ്രാദേശിക പകർപ്പുകളും ഉൾപ്പെടുന്നു.", + "backup_onboarding_description": "നിങ്ങളുടെ ഡാറ്റ പരിരക്ഷിക്കാൻ 3-2-1 ബാക്കപ്പ് തന്ത്രം ശുപാർശ ചെയ്യുന്നു. സമഗ്രമായ ഒരു ബാക്കപ്പ് പരിഹാരത്തിനായി, നിങ്ങൾ അപ്‌ലോഡ് ചെയ്ത ഫോട്ടോകളുടെ/വീഡിയോകളുടെ പകർപ്പുകളും Immich ഡാറ്റാബേസും സൂക്ഷിക്കേണ്ടതാണ്.", + "backup_onboarding_footer": "Immich ബാക്കപ്പ് ചെയ്യുന്നതിനെക്കുറിച്ചുള്ള കൂടുതൽ വിവരങ്ങൾക്കായി, ദയവായി ഡോക്യുമെന്റേഷൻ പരിശോധിക്കുക.", + "backup_onboarding_parts_title": "ഒരു 3-2-1 ബാക്കപ്പിൽ ഉൾപ്പെടുന്നവ:", + "backup_onboarding_title": "ബാക്കപ്പുകൾ", + "backup_settings": "ഡാറ്റാബേസ് ഡംപ് ക്രമീകരണങ്ങൾ", + "backup_settings_description": "ഡാറ്റാബേസ് ഡംപ് ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക.", + "cleared_jobs": "{job}-നുള്ള ജോലികൾ ക്ലിയർ ചെയ്തു", + "config_set_by_file": "കോൺഫിഗറേഷൻ നിലവിൽ ഒരു കോൺഫിഗറേഷൻ ഫയൽ വഴിയാണ് സജ്ജീകരിച്ചിരിക്കുന്നത്", + "confirm_delete_library": "{library} ലൈബ്രറി ഇല്ലാതാക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", + "confirm_delete_library_assets": "ഈ ലൈബ്രറി ഇല്ലാതാക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ? ഇത് Immich-ൽ നിന്ന് {count, plural, one {അതിലുള്ള ഒരു അസറ്റ്} other {അതിലുള്ള എല്ലാ # അസറ്റുകളും}} ഇല്ലാതാക്കും, ഇത് പഴയപടിയാക്കാൻ കഴിയില്ല. ഫയലുകൾ ഡിസ്കിൽ തന്നെ തുടരും.", + "confirm_email_below": "സ്ഥിരീകരിക്കുന്നതിന്, താഴെ \"{email}\" എന്ന് ടൈപ്പ് ചെയ്യുക", + "confirm_reprocess_all_faces": "എല്ലാ മുഖങ്ങളും വീണ്ടും പ്രോസസ്സ് ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ? ഇത് പേരുള്ള ആളുകളെയും നീക്കം ചെയ്യും.", + "confirm_user_password_reset": "{user}-ന്റെ പാസ്‌വേഡ് റീസെറ്റ് ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", + "confirm_user_pin_code_reset": "{user}-ന്റെ പിൻ കോഡ് റീസെറ്റ് ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", + "create_job": "ജോലി ഉണ്ടാക്കുക", + "cron_expression": "ക്രോൺ എക്സ്പ്രഷൻ", + "cron_expression_description": "ക്രോൺ ഫോർമാറ്റ് ഉപയോഗിച്ച് സ്കാനിംഗ് ഇടവേള സജ്ജീകരിക്കുക. കൂടുതൽ വിവരങ്ങൾക്കായി ദയവായി ക്രോൺടാബ് ഗുരു പോലുള്ളവ പരിശോധിക്കുക", + "cron_expression_presets": "ക്രോൺ എക്സ്പ്രഷൻ പ്രീസെറ്റുകൾ", + "disable_login": "ലോഗിൻ പ്രവർത്തനരഹിതമാക്കുക", + "duplicate_detection_job_description": "സമാന ചിത്രങ്ങൾ കണ്ടെത്താൻ അസറ്റുകളിൽ മെഷീൻ ലേണിംഗ് പ്രവർത്തിപ്പിക്കുക. ഇത് സ്മാർട്ട് സെർച്ചിനെ ആശ്രയിച്ചിരിക്കുന്നു", + "exclusion_pattern_description": "നിങ്ങളുടെ ലൈബ്രറി സ്കാൻ ചെയ്യുമ്പോൾ ഫയലുകളും ഫോൾഡറുകളും ഒഴിവാക്കാൻ എക്സ്ക്ലൂഷൻ പാറ്റേണുകൾ നിങ്ങളെ സഹായിക്കുന്നു. നിങ്ങൾ ഇമ്പോർട്ട് ചെയ്യാൻ ആഗ്രഹിക്കാത്ത ഫയലുകൾ അടങ്ങിയ ഫോൾഡറുകൾ (ഉദാഹരണത്തിന് RAW ഫയലുകൾ) ഉണ്ടെങ്കിൽ ഇത് ഉപയോഗപ്രദമാണ്.", + "external_library_management": "എക്സ്റ്റേണൽ ലൈബ്രറി മാനേജ്മെന്റ്", + "face_detection": "മുഖം തിരിച്ചറിയൽ", + "face_detection_description": "മെഷീൻ ലേണിംഗ് ഉപയോഗിച്ച് അസറ്റുകളിലെ മുഖങ്ങൾ കണ്ടെത്തുക. വീഡിയോകൾക്കായി, തംബ്നെയിൽ മാത്രമേ പരിഗണിക്കൂ. \"റിഫ്രഷ്\" എല്ലാ അസറ്റുകളും വീണ്ടും പ്രോസസ്സ് ചെയ്യുന്നു. \"റീസെറ്റ്\" നിലവിലുള്ള എല്ലാ മുഖ ഡാറ്റയും നീക്കംചെയ്യുന്നു. \"മിസ്സിംഗ്\" ഇതുവരെ പ്രോസസ്സ് ചെയ്യാത്ത അസറ്റുകളെ ക്യൂവിലാക്കുന്നു. മുഖം തിരിച്ചറിയൽ പൂർത്തിയായ ശേഷം, കണ്ടെത്തിയ മുഖങ്ങൾ ഫേഷ്യൽ റെക്കഗ്നിഷനായി ക്യൂ ചെയ്യപ്പെടും, അവയെ നിലവിലുള്ളതോ പുതിയതോ ആയ ആളുകളായി തരംതിരിക്കും.", + "facial_recognition_job_description": "കണ്ടെത്തിയ മുഖങ്ങളെ ആളുകളായി ഗ്രൂപ്പ് ചെയ്യുക. മുഖം കണ്ടെത്തൽ പൂർത്തിയായതിന് ശേഷമാണ് ഈ ഘട്ടം പ്രവർത്തിക്കുന്നത്. \"റീസെറ്റ്\" എല്ലാ മുഖങ്ങളെയും വീണ്ടും ക്ലസ്റ്റർ ചെയ്യുന്നു. \"മിസ്സിംഗ്\" ഒരു വ്യക്തിയെയും അസൈൻ ചെയ്യാത്ത മുഖങ്ങളെ ക്യൂവിലാക്കുന്നു.", + "failed_job_command": "{job} എന്ന ജോലിക്കുള്ള കമാൻഡ് {command} പരാജയപ്പെട്ടു", + "force_delete_user_warning": "മുന്നറിയിപ്പ്: ഇത് ഉപയോക്താവിനെയും എല്ലാ അസറ്റുകളെയും ഉടനടി നീക്കം ചെയ്യും. ഇത് പഴയപടിയാക്കാൻ കഴിയില്ല, ഫയലുകൾ വീണ്ടെടുക്കാനും സാധിക്കില്ല.", + "image_format": "ഫോർമാറ്റ്", + "image_format_description": "WebP, JPEG-നെക്കാൾ ചെറിയ ഫയലുകൾ നിർമ്മിക്കുന്നു, പക്ഷേ എൻകോഡ് ചെയ്യാൻ വേഗത കുറവാണ്.", + "image_fullsize_description": "മെറ്റാഡാറ്റ നീക്കംചെയ്ത പൂർണ്ണ വലുപ്പത്തിലുള്ള ചിത്രം, സൂം ചെയ്യുമ്പോൾ ഉപയോഗിക്കുന്നു", + "image_fullsize_enabled": "പൂർണ്ണ വലുപ്പത്തിലുള്ള ചിത്ര നിർമ്മാണം പ്രവർത്തനക്ഷമമാക്കുക", + "image_fullsize_enabled_description": "വെബ്-ഫ്രണ്ട്ലി അല്ലാത്ത ഫോർമാറ്റുകൾക്കായി പൂർണ്ണ വലുപ്പത്തിലുള്ള ചിത്രം നിർമ്മിക്കുക. \"എംബഡഡ് പ്രിവ്യൂവിന് മുൻഗണന നൽകുക\" പ്രവർത്തനക്ഷമമാക്കുമ്പോൾ, എംബഡഡ് പ്രിവ്യൂകൾ പരിവർത്തനം കൂടാതെ നേരിട്ട് ഉപയോഗിക്കുന്നു. JPEG പോലുള്ള വെബ്-ഫ്രണ്ട്ലി ഫോർമാറ്റുകളെ ഇത് ബാധിക്കില്ല.", + "image_fullsize_quality_description": "പൂർണ്ണ വലുപ്പത്തിലുള്ള ചിത്രത്തിന്റെ ഗുണമേന്മ 1-100 വരെ. ഉയർന്ന മൂല്യം മികച്ചതാണ്, പക്ഷേ വലിയ ഫയലുകൾ ഉണ്ടാക്കുന്നു.", + "image_fullsize_title": "പൂർണ്ണ വലുപ്പ ചിത്ര ക്രമീകരണങ്ങൾ", + "image_prefer_embedded_preview": "എംബഡഡ് പ്രിവ്യൂവിന് മുൻഗണന നൽകുക", + "image_prefer_embedded_preview_setting_description": "ലഭ്യമാകുമ്പോൾ, റോ (RAW) ഫോട്ടോകളിലെ എംബഡഡ് പ്രിവ്യൂകൾ ഇമേജ് പ്രോസസ്സിംഗിനുള്ള ഇൻപുട്ടായി ഉപയോഗിക്കുക. ഇത് ചില ചിത്രങ്ങൾക്ക് കൂടുതൽ കൃത്യമായ നിറങ്ങൾ നൽകിയേക്കാം, പക്ഷേ പ്രിവ്യൂവിൻ്റെ ഗുണനിലവാരം ക്യാമറയെ ആശ്രയിച്ചിരിക്കും, കൂടാതെ ചിത്രത്തിൽ കൂടുതൽ കംപ്രഷൻ ആർട്ടിഫാക്റ്റുകൾ ഉണ്ടാകാം.", + "image_prefer_wide_gamut": "വൈഡ് ഗാമറ്റിന് മുൻഗണന നൽകുക", + "image_prefer_wide_gamut_setting_description": "തംബ്നെയിലുകൾക്കായി ഡിസ്പ്ലേ P3 ഉപയോഗിക്കുക. ഇത് വൈഡ് കളർസ്പേസുകളുള്ള ചിത്രങ്ങളുടെ മിഴിവ് നന്നായി സംരക്ഷിക്കുന്നു, പക്ഷേ പഴയ ബ്രൗസർ പതിപ്പുള്ള പഴയ ഉപകരണങ്ങളിൽ ചിത്രങ്ങൾ വ്യത്യസ്തമായി കാണപ്പെട്ടേക്കാം. കളർ ഷിഫ്റ്റുകൾ ഒഴിവാക്കാൻ sRGB ചിത്രങ്ങൾ sRGB ആയി തന്നെ നിലനിർത്തുന്നു.", + "image_preview_description": "മെറ്റാഡാറ്റ നീക്കംചെയ്ത ഇടത്തരം വലുപ്പമുള്ള ചിത്രം, ഒരൊറ്റ അസറ്റ് കാണുമ്പോഴും മെഷീൻ ലേണിംഗിനും ഉപയോഗിക്കുന്നു", + "image_preview_quality_description": "പ്രിവ്യൂ ഗുണമേന്മ 1-100 വരെ. ഉയർന്ന മൂല്യം മികച്ചതാണ്, പക്ഷേ വലിയ ഫയലുകൾ ഉണ്ടാക്കുകയും ആപ്പിന്റെ പ്രതികരണശേഷി കുറയ്ക്കുകയും ചെയ്യും. കുറഞ്ഞ മൂല്യം സജ്ജീകരിക്കുന്നത് മെഷീൻ ലേണിംഗ് ഗുണമേന്മയെ ബാധിച്ചേക്കാം.", + "image_preview_title": "പ്രിവ്യൂ ക്രമീകരണങ്ങൾ", + "image_quality": "ഗുണമേന്മ", + "image_resolution": "റെസല്യൂഷൻ", + "image_resolution_description": "ഉയർന്ന റെസല്യൂഷനുകൾക്ക് കൂടുതൽ വിശദാംശങ്ങൾ സംരക്ഷിക്കാൻ കഴിയും, പക്ഷേ എൻകോഡ് ചെയ്യാൻ കൂടുതൽ സമയമെടുക്കും, വലിയ ഫയൽ വലുപ്പമുണ്ടാകും, കൂടാതെ ആപ്പിന്റെ പ്രതികരണശേഷി കുറയ്ക്കുകയും ചെയ്യും.", + "image_settings": "ചിത്ര ക്രമീകരണങ്ങൾ", + "image_settings_description": "നിർമ്മിച്ച ചിത്രങ്ങളുടെ ഗുണമേന്മയും റെസല്യൂഷനും കൈകാര്യം ചെയ്യുക", + "image_thumbnail_description": "മെറ്റാഡാറ്റ നീക്കംചെയ്ത ചെറിയ തംബ്നെയിൽ, പ്രധാന ടൈംലൈൻ പോലുള്ള ഫോട്ടോകളുടെ ഗ്രൂപ്പുകൾ കാണുമ്പോൾ ഉപയോഗിക്കുന്നു", + "image_thumbnail_quality_description": "തംബ്നെയിലിന്റെ ഗുണമേന്മ 1-100 വരെ. ഉയർന്ന മൂല്യം മികച്ചതാണ്, പക്ഷേ വലിയ ഫയലുകൾ ഉണ്ടാക്കുകയും ആപ്പിന്റെ പ്രതികരണശേഷി കുറയ്ക്കുകയും ചെയ്യും.", + "image_thumbnail_title": "തംബ്നെയിൽ ക്രമീകരണങ്ങൾ", + "job_concurrency": "{job} കോൺകറൻസി", "job_created": "ജോലി സൃഷ്ടിച്ചു", - "job_status": "ജോലി നില" + "job_not_concurrency_safe": "ഈ ജോലി കോൺകറൻസി-സേഫ് അല്ല.", + "job_settings": "ജോലി ക്രമീകരണങ്ങൾ", + "job_settings_description": "ജോലി കോൺകറൻസി കൈകാര്യം ചെയ്യുക", + "job_status": "ജോലിയുടെ നില", + "jobs_delayed": "{jobCount, plural, one {# ജോലി വൈകി} other {# ജോലികൾ വൈകി}}", + "jobs_failed": "{jobCount, plural, one {# ജോലി പരാജയപ്പെട്ടു} other {# ജോലികൾ പരാജയപ്പെട്ടു}}", + "library_created": "{library} എന്ന ലൈബ്രറി സൃഷ്ടിച്ചു", + "library_deleted": "ലൈബ്രറി ഇല്ലാതാക്കി", + "library_import_path_description": "ഇമ്പോർട്ടുചെയ്യാൻ ഒരു ഫോൾഡർ വ്യക്തമാക്കുക. സബ്ഫോൾഡറുകൾ ഉൾപ്പെടെ ഈ ഫോൾഡർ ചിത്രങ്ങൾക്കും വീഡിയോകൾക്കുമായി സ്കാൻ ചെയ്യും.", + "library_scanning": "ആനുകാലിക സ്കാനിംഗ്", + "library_scanning_description": "ആനുകാലിക ലൈബ്രറി സ്കാനിംഗ് കോൺഫിഗർ ചെയ്യുക", + "library_scanning_enable_description": "ആനുകാലിക ലൈബ്രറി സ്കാനിംഗ് പ്രവർത്തനക്ഷമമാക്കുക", + "library_settings": "എക്സ്റ്റേണൽ ലൈബ്രറി", + "library_settings_description": "എക്സ്റ്റേണൽ ലൈബ്രറി ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", + "library_tasks_description": "പുതിയതോ മാറ്റം വരുത്തിയതോ ആയ അസറ്റുകൾക്കായി എക്സ്റ്റേണൽ ലൈബ്രറികൾ സ്കാൻ ചെയ്യുക", + "library_watching_enable_description": "ഫയൽ മാറ്റങ്ങൾക്കായി എക്സ്റ്റേണൽ ലൈബ്രറികൾ നിരീക്ഷിക്കുക", + "library_watching_settings": "ലൈബ്രറി നിരീക്ഷിക്കൽ (പരീക്ഷണാടിസ്ഥാനത്തിൽ)", + "library_watching_settings_description": "മാറ്റം വന്ന ഫയലുകൾക്കായി യാന്ത്രികമായി നിരീക്ഷിക്കുക", + "logging_enable_description": "ലോഗിംഗ് പ്രവർത്തനക്ഷമമാക്കുക", + "logging_level_description": "പ്രവർത്തനക്ഷമമാക്കുമ്പോൾ, ഏത് ലോഗ് ലെവൽ ഉപയോഗിക്കണം.", + "logging_settings": "ലോഗിംഗ്", + "machine_learning_availability_checks": "ലഭ്യത പരിശോധനകൾ", + "machine_learning_availability_checks_description": "ലഭ്യമായ മെഷീൻ ലേണിംഗ് സെർവറുകൾ യാന്ത്രികമായി കണ്ടെത്തുകയും മുൻഗണന നൽകുകയും ചെയ്യുക", + "machine_learning_availability_checks_enabled": "ലഭ്യത പരിശോധനകൾ പ്രവർത്തനക്ഷമമാക്കുക", + "machine_learning_availability_checks_interval": "പരിശോധന ഇടവേള", + "machine_learning_availability_checks_interval_description": "ലഭ്യത പരിശോധനകൾക്കിടയിലുള്ള ഇടവേള (മില്ലിസെക്കൻഡിൽ)", + "machine_learning_availability_checks_timeout": "അഭ്യർത്ഥനയുടെ സമയപരിധി", + "machine_learning_availability_checks_timeout_description": "ലഭ്യത പരിശോധനകൾക്കുള്ള സമയപരിധി (മില്ലിസെക്കൻഡിൽ)", + "machine_learning_clip_model": "CLIP മോഡൽ", + "machine_learning_clip_model_description": "ഇവിടെ ലിസ്റ്റ് ചെയ്തിട്ടുള്ള ഒരു CLIP മോഡലിന്റെ പേര്. ഒരു മോഡൽ മാറ്റുമ്പോൾ എല്ലാ ചിത്രങ്ങൾക്കുമായി 'സ്മാർട്ട് സെർച്ച്' ജോലി വീണ്ടും പ്രവർത്തിപ്പിക്കേണ്ടതുണ്ടെന്ന് ഓർമ്മിക്കുക.", + "machine_learning_duplicate_detection": "ഡ്യൂപ്ലിക്കേറ്റ് കണ്ടെത്തൽ", + "machine_learning_duplicate_detection_enabled": "ഡ്യൂപ്ലിക്കേറ്റ് കണ്ടെത്തൽ പ്രവർത്തനക്ഷമമാക്കുക", + "machine_learning_duplicate_detection_enabled_description": "പ്രവർത്തനരഹിതമാക്കിയാലും, തികച്ചും സമാനമായ അസറ്റുകളിലെ ഡ്യൂപ്ലിക്കേറ്റുകൾ ഒഴിവാക്കപ്പെടും.", + "machine_learning_duplicate_detection_setting_description": "സമാനമായ ഡ്യൂപ്ലിക്കേറ്റുകൾ കണ്ടെത്താൻ CLIP എംബെഡ്ഡിംഗ്‌സ് ഉപയോഗിക്കുക", + "machine_learning_enabled": "മെഷീൻ ലേണിംഗ് പ്രവർത്തനക്ഷമമാക്കുക", + "machine_learning_enabled_description": "പ്രവർത്തനരഹിതമാക്കിയാൽ, താഴെയുള്ള ക്രമീകരണങ്ങൾ പരിഗണിക്കാതെ എല്ലാ ML ഫീച്ചറുകളും പ്രവർത്തനരഹിതമാകും.", + "machine_learning_facial_recognition": "മുഖം തിരിച്ചറിയൽ", + "machine_learning_facial_recognition_description": "ചിത്രങ്ങളിലെ മുഖങ്ങൾ കണ്ടെത്തുക, തിരിച്ചറിയുക, ഗ്രൂപ്പ് ചെയ്യുക", + "machine_learning_facial_recognition_model": "മുഖം തിരിച്ചറിയൽ മോഡൽ", + "machine_learning_facial_recognition_model_description": "മോഡലുകൾ വലുപ്പത്തിന്റെ അവരോഹണ ക്രമത്തിലാണ് ലിസ്റ്റ് ചെയ്തിരിക്കുന്നത്. വലിയ മോഡലുകൾ വേഗത കുറഞ്ഞതും കൂടുതൽ മെമ്മറി ഉപയോഗിക്കുന്നവയുമാണ്, പക്ഷേ മികച്ച ഫലങ്ങൾ നൽകുന്നു. ഒരു മോഡൽ മാറ്റുമ്പോൾ എല്ലാ ചിത്രങ്ങൾക്കുമായി 'മുഖം കണ്ടെത്തൽ' ജോലി വീണ്ടും പ്രവർത്തിപ്പിക്കേണ്ടതുണ്ടെന്ന് ഓർമ്മിക്കുക.", + "machine_learning_facial_recognition_setting": "മുഖം തിരിച്ചറിയൽ പ്രവർത്തനക്ഷമമാക്കുക", + "machine_learning_facial_recognition_setting_description": "പ്രവർത്തനരഹിതമാക്കിയാൽ, മുഖം തിരിച്ചറിയലിനായി ചിത്രങ്ങൾ എൻകോഡ് ചെയ്യപ്പെടില്ല, കൂടാതെ എക്സ്പ്ലോർ പേജിലെ 'ആളുകൾ' വിഭാഗത്തിൽ അവ ദൃശ്യമാകുകയുമില്ല.", + "machine_learning_max_detection_distance": "പരമാവധി കണ്ടെത്തൽ ദൂരം", + "machine_learning_max_detection_distance_description": "രണ്ട് ചിത്രങ്ങളെ ഡ്യൂപ്ലിക്കേറ്റുകളായി കണക്കാക്കുന്നതിനുള്ള പരമാവധി ദൂരം, 0.001-0.1 വരെ. ഉയർന്ന മൂല്യങ്ങൾ കൂടുതൽ ഡ്യൂപ്ലിക്കേറ്റുകളെ കണ്ടെത്തും, പക്ഷേ തെറ്റായ ഫലങ്ങളിലേക്ക് നയിച്ചേക്കാം.", + "machine_learning_max_recognition_distance": "പരമാവധി തിരിച്ചറിയൽ ദൂരം", + "machine_learning_max_recognition_distance_description": "രണ്ട് മുഖങ്ങളെ ഒരേ വ്യക്തിയായി കണക്കാക്കുന്നതിനുള്ള പരമാവധി ദൂരം, 0-2 വരെ. ഇത് കുറയ്ക്കുന്നത് രണ്ട് പേരെ ഒരേ വ്യക്തിയായി ലേബൽ ചെയ്യുന്നത് തടയാൻ സഹായിക്കും, അതേസമയം ഇത് കൂട്ടുന്നത് ഒരേ വ്യക്തിയെ രണ്ട് വ്യത്യസ്ത ആളുകളായി ലേബൽ ചെയ്യുന്നത് തടയാനും സഹായിക്കും. ഒരാളെ രണ്ടായി വിഭജിക്കുന്നതിനേക്കാൾ എളുപ്പമാണ് രണ്ടുപേരെ ഒന്നാക്കുന്നത് എന്നത് ശ്രദ്ധിക്കുക, അതിനാൽ സാധ്യമാകുമ്പോൾ കുറഞ്ഞ പരിധി തിരഞ്ഞെടുക്കുന്നതാണ് ഉചിതം.", + "machine_learning_min_detection_score": "കുറഞ്ഞ കണ്ടെത്തൽ സ്കോർ", + "machine_learning_min_detection_score_description": "ഒരു മുഖം കണ്ടെത്തുന്നതിനുള്ള ഏറ്റവും കുറഞ്ഞ കോൺഫിഡൻസ് സ്കോർ 0-1 വരെ. കുറഞ്ഞ മൂല്യങ്ങൾ കൂടുതൽ മുഖങ്ങളെ കണ്ടെത്തും, പക്ഷേ തെറ്റായ ഫലങ്ങളിലേക്ക് നയിച്ചേക്കാം.", + "machine_learning_min_recognized_faces": "തിരിച്ചറിഞ്ഞ മുഖങ്ങളുടെ കുറഞ്ഞ എണ്ണം", + "machine_learning_min_recognized_faces_description": "ഒരു വ്യക്തിയെ സൃഷ്ടിക്കുന്നതിന് ആവശ്യമായ തിരിച്ചറിഞ്ഞ മുഖങ്ങളുടെ ഏറ്റവും കുറഞ്ഞ എണ്ണം. ഇത് വർദ്ധിപ്പിക്കുന്നത് മുഖം തിരിച്ചറിയൽ കൂടുതൽ കൃത്യമാക്കുന്നു, എന്നാൽ ഒരു മുഖം ഒരു വ്യക്തിക്ക് നൽകാതിരിക്കാനുള്ള സാധ്യതയും വർദ്ധിപ്പിക്കുന്നു.", + "machine_learning_settings": "മെഷീൻ ലേണിംഗ് ക്രമീകരണങ്ങൾ", + "machine_learning_settings_description": "മെഷീൻ ലേണിംഗ് ഫീച്ചറുകളും ക്രമീകരണങ്ങളും കൈകാര്യം ചെയ്യുക", + "machine_learning_smart_search": "സ്മാർട്ട് സെർച്ച്", + "machine_learning_smart_search_description": "CLIP എംബെഡ്ഡിംഗ്‌സ് ഉപയോഗിച്ച് ചിത്രങ്ങൾക്കായി അർത്ഥപൂർവ്വം തിരയുക", + "machine_learning_smart_search_enabled": "സ്മാർട്ട് സെർച്ച് പ്രവർത്തനക്ഷമമാക്കുക", + "machine_learning_smart_search_enabled_description": "പ്രവർത്തനരഹിതമാക്കിയാൽ, സ്മാർട്ട് സെർച്ചിനായി ചിത്രങ്ങൾ എൻകോഡ് ചെയ്യപ്പെടില്ല.", + "machine_learning_url_description": "മെഷീൻ ലേണിംഗ് സെർവറിന്റെ URL. ഒന്നിൽ കൂടുതൽ URL-കൾ നൽകിയിട്ടുണ്ടെങ്കിൽ, ഓരോ സെർവറും ഒന്നിനു പുറകെ ഒന്നായി വിജയകരമായി പ്രതികരിക്കുന്നതുവരെ ശ്രമിക്കും. പ്രതികരിക്കാത്ത സെർവറുകൾ ഓൺലൈനിൽ തിരികെ വരുന്നതുവരെ താൽക്കാലികമായി അവഗണിക്കപ്പെടും.", + "manage_concurrency": "കോൺകറൻസി കൈകാര്യം ചെയ്യുക", + "manage_log_settings": "ലോഗ് ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", + "map_dark_style": "ഡാർക്ക് സ്റ്റൈൽ", + "map_enable_description": "മാപ്പ് ഫീച്ചറുകൾ പ്രവർത്തനക്ഷമമാക്കുക", + "map_gps_settings": "മാപ്പ് & GPS ക്രമീകരണങ്ങൾ", + "map_gps_settings_description": "മാപ്പ് & GPS (റിവേഴ്സ് ജിയോകോഡിംഗ്) ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", + "map_implications": "മാപ്പ് ഫീച്ചർ ഒരു ബാഹ്യ ടൈൽ സേവനത്തെ (tiles.immich.cloud) ആശ്രയിച്ചിരിക്കുന്നു", + "map_light_style": "ലൈറ്റ് സ്റ്റൈൽ", + "map_manage_reverse_geocoding_settings": "റിവേഴ്സ് ജിയോകോഡിംഗ് ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", + "map_reverse_geocoding": "റിവേഴ്സ് ജിയോകോഡിംഗ്", + "map_reverse_geocoding_enable_description": "റിവേഴ്സ് ജിയോകോഡിംഗ് പ്രവർത്തനക്ഷമമാക്കുക", + "map_reverse_geocoding_settings": "റിവേഴ്സ് ജിയോകോഡിംഗ് ക്രമീകരണങ്ങൾ", + "map_settings": "മാപ്പ്", + "map_settings_description": "മാപ്പ് ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", + "map_style_description": "ഒരു style.json മാപ്പ് തീമിലേക്കുള്ള URL", + "memory_cleanup_job": "മെമ്മറി ക്ലീനപ്പ്", + "memory_generate_job": "മെമ്മറി ജനറേഷൻ", + "metadata_extraction_job": "മെറ്റാഡാറ്റ എക്‌സ്‌ട്രാക്റ്റുചെയ്യുക", + "metadata_extraction_job_description": "ഓരോ അസറ്റിൽ നിന്നും GPS, മുഖങ്ങൾ, റെസല്യൂഷൻ തുടങ്ങിയ മെറ്റാഡാറ്റ വിവരങ്ങൾ എക്‌സ്‌ട്രാക്റ്റുചെയ്യുക", + "metadata_faces_import_setting": "മുഖം ഇമ്പോർട്ട് പ്രവർത്തനക്ഷമമാക്കുക", + "metadata_faces_import_setting_description": "ചിത്രത്തിന്റെ EXIF ഡാറ്റയിൽ നിന്നും സൈഡ്‌കാർ ഫയലുകളിൽ നിന്നും മുഖങ്ങൾ ഇമ്പോർട്ടുചെയ്യുക", + "metadata_settings": "മെറ്റാഡാറ്റ ക്രമീകരണങ്ങൾ", + "metadata_settings_description": "മെറ്റാഡാറ്റ ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", + "migration_job": "മൈഗ്രേഷൻ", + "migration_job_description": "അസറ്റുകളുടെയും മുഖങ്ങളുടെയും തംബ്നെയിലുകൾ ഏറ്റവും പുതിയ ഫോൾഡർ ഘടനയിലേക്ക് മൈഗ്രേറ്റ് ചെയ്യുക", + "nightly_tasks_cluster_faces_setting_description": "പുതുതായി കണ്ടെത്തിയ മുഖങ്ങളിൽ ഫേഷ്യൽ റെക്കഗ്നിഷൻ പ്രവർത്തിപ്പിക്കുക", + "nightly_tasks_cluster_new_faces_setting": "പുതിയ മുഖങ്ങളെ ക്ലസ്റ്റർ ചെയ്യുക", + "nightly_tasks_database_cleanup_setting": "ഡാറ്റാബേസ് ക്ലീനപ്പ് ടാസ്ക്കുകൾ", + "nightly_tasks_database_cleanup_setting_description": "ഡാറ്റാബേസിൽ നിന്ന് പഴയതും കാലഹരണപ്പെട്ടതുമായ ഡാറ്റ വൃത്തിയാക്കുക", + "nightly_tasks_generate_memories_setting": "മെമ്മറികൾ നിർമ്മിക്കുക", + "nightly_tasks_generate_memories_setting_description": "അസറ്റുകളിൽ നിന്ന് പുതിയ മെമ്മറികൾ സൃഷ്ടിക്കുക", + "nightly_tasks_missing_thumbnails_setting": "നഷ്ടപ്പെട്ട തംബ്നെയിലുകൾ നിർമ്മിക്കുക", + "nightly_tasks_missing_thumbnails_setting_description": "തംബ്നെയിലുകൾ ഇല്ലാത്ത അസറ്റുകളെ തംബ്നെയിൽ നിർമ്മാണത്തിനായി ക്യൂ ചെയ്യുക", + "nightly_tasks_settings": "രാത്രിയിലെ ടാസ്ക് ക്രമീകരണങ്ങൾ", + "nightly_tasks_settings_description": "രാത്രിയിലെ ടാസ്ക്കുകൾ കൈകാര്യം ചെയ്യുക", + "nightly_tasks_start_time_setting": "തുടങ്ങുന്ന സമയം", + "nightly_tasks_start_time_setting_description": "സെർവർ രാത്രിയിലെ ടാസ്ക്കുകൾ പ്രവർത്തിപ്പിക്കാൻ തുടങ്ങുന്ന സമയം", + "nightly_tasks_sync_quota_usage_setting": "ക്വാട്ട ഉപയോഗം സിങ്ക് ചെയ്യുക", + "nightly_tasks_sync_quota_usage_setting_description": "നിലവിലെ ഉപയോഗത്തെ അടിസ്ഥാനമാക്കി ഉപയോക്താവിന്റെ സ്റ്റോറേജ് ക്വാട്ട അപ്ഡേറ്റ് ചെയ്യുക", + "no_paths_added": "പാത്തുകൾ ഒന്നും ചേർത്തിട്ടില്ല", + "no_pattern_added": "പാറ്റേൺ ഒന്നും ചേർത്തിട്ടില്ല", + "note_apply_storage_label_previous_assets": "കുറിപ്പ്: മുമ്പ് അപ്‌ലോഡ് ചെയ്ത അസറ്റുകളിൽ സ്റ്റോറേജ് ലേബൽ പ്രയോഗിക്കാൻ, ഇത് പ്രവർത്തിപ്പിക്കുക", + "note_cannot_be_changed_later": "കുറിപ്പ്: ഇത് പിന്നീട് മാറ്റാൻ കഴിയില്ല!", + "notification_email_from_address": "അയയ്ക്കുന്നയാളുടെ വിലാസം", + "notification_email_from_address_description": "അയയ്ക്കുന്നയാളുടെ ഇമെയിൽ വിലാസം, ഉദാഹരണത്തിന്: \"Immich Photo Server \". നിങ്ങൾക്ക് ഇമെയിലുകൾ അയയ്ക്കാൻ അനുവാദമുള്ള ഒരു വിലാസം ഉപയോഗിക്കുന്നുവെന്ന് ഉറപ്പാക്കുക.", + "notification_email_host_description": "ഇമെയിൽ സെർവറിന്റെ ഹോസ്റ്റ് (ഉദാ. smtp.immich.app)", + "notification_email_ignore_certificate_errors": "സർട്ടിഫിക്കറ്റ് പിശകുകൾ അവഗണിക്കുക", + "notification_email_ignore_certificate_errors_description": "TLS സർട്ടിഫിക്കറ്റ് സാധുതാ പിശകുകൾ അവഗണിക്കുക (ശുപാർശ ചെയ്യുന്നില്ല)", + "notification_email_password_description": "ഇമെയിൽ സെർവറുമായി പ്രാമാണീകരിക്കുമ്പോൾ ഉപയോഗിക്കേണ്ട പാസ്‌വേഡ്", + "notification_email_port_description": "ഇമെയിൽ സെർവറിന്റെ പോർട്ട് (ഉദാ. 25, 465, അല്ലെങ്കിൽ 587)", + "notification_email_sent_test_email_button": "പരിശോധനാ ഇമെയിൽ അയച്ച് സേവ് ചെയ്യുക", + "notification_email_setting_description": "ഇമെയിൽ അറിയിപ്പുകൾ അയക്കുന്നതിനുള്ള ക്രമീകരണങ്ങൾ", + "notification_email_test_email": "പരിശോധനാ ഇമെയിൽ അയക്കുക", + "notification_email_test_email_failed": "പരിശോധനാ ഇമെയിൽ അയക്കുന്നതിൽ പരാജയപ്പെട്ടു, നിങ്ങളുടെ മൂല്യങ്ങൾ പരിശോധിക്കുക", + "notification_email_test_email_sent": "{email} എന്ന വിലാസത്തിലേക്ക് ഒരു പരിശോധനാ ഇമെയിൽ അയച്ചിട്ടുണ്ട്. ദയവായി നിങ്ങളുടെ ഇൻബോക്സ് പരിശോധിക്കുക.", + "notification_email_username_description": "ഇമെയിൽ സെർവറുമായി പ്രാമാണീകരിക്കുമ്പോൾ ഉപയോഗിക്കേണ്ട ഉപയോക്തൃനാമം", + "notification_enable_email_notifications": "ഇമെയിൽ അറിയിപ്പുകൾ പ്രവർത്തനക്ഷമമാക്കുക", + "notification_settings": "അറിയിപ്പ് ക്രമീകരണങ്ങൾ", + "notification_settings_description": "ഇമെയിൽ ഉൾപ്പെടെയുള്ള അറിയിപ്പ് ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", + "oauth_auto_launch": "യാന്ത്രികമായി തുടങ്ങുക", + "oauth_auto_launch_description": "ലോഗിൻ പേജിലേക്ക് പോകുമ്പോൾ OAuth ലോഗിൻ ഫ്ലോ യാന്ത്രികമായി ആരംഭിക്കുക", + "oauth_auto_register": "യാന്ത്രികമായി രജിസ്റ്റർ ചെയ്യുക", + "oauth_auto_register_description": "OAuth ഉപയോഗിച്ച് സൈൻ ഇൻ ചെയ്ത ശേഷം പുതിയ ഉപയോക്താക്കളെ യാന്ത്രികമായി രജിസ്റ്റർ ചെയ്യുക", + "oauth_button_text": "ബട്ടണിലെ വാചകം", + "oauth_client_secret_description": "OAuth ദാതാവ് PKCE (പ്രൂഫ് കീ ഫോർ കോഡ് എക്സ്ചേഞ്ച്) പിന്തുണയ്ക്കുന്നില്ലെങ്കിൽ ആവശ്യമാണ്", + "oauth_enable_description": "OAuth ഉപയോഗിച്ച് ലോഗിൻ ചെയ്യുക", + "oauth_mobile_redirect_uri": "മൊബൈൽ റീഡയറക്ട് URI", + "oauth_mobile_redirect_uri_override": "മൊബൈൽ റീഡയറക്ട് URI ഓവർറൈഡ്", + "oauth_mobile_redirect_uri_override_description": "OAuth ദാതാവ് \"{callback}\" പോലുള്ള ഒരു മൊബൈൽ URI അനുവദിക്കാത്തപ്പോൾ പ്രവർത്തനക്ഷമമാക്കുക", + "oauth_role_claim": "റോൾ ക്ലെയിം", + "oauth_role_claim_description": "ഈ ക്ലെയിമിന്റെ സാന്നിധ്യത്തെ അടിസ്ഥാനമാക്കി യാന്ത്രികമായി അഡ്മിൻ ആക്സസ് നൽകുക. ക്ലെയിമിന് 'user' അല്ലെങ്കിൽ 'admin' എന്ന് ഉണ്ടാകാം.", + "oauth_settings": "OAuth", + "oauth_settings_description": "OAuth ലോഗിൻ ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", + "oauth_settings_more_details": "ഈ ഫീച്ചറിനെക്കുറിച്ചുള്ള കൂടുതൽ വിവരങ്ങൾക്ക്, ഡോക്സ് പരിശോധിക്കുക.", + "oauth_storage_label_claim": "സ്റ്റോറേജ് ലേബൽ ക്ലെയിം", + "oauth_storage_label_claim_description": "ഉപയോക്താവിന്റെ സ്റ്റോറേജ് ലേബൽ ഈ ക്ലെയിമിന്റെ മൂല്യത്തിലേക്ക് യാന്ത്രികമായി സജ്ജമാക്കുക.", + "oauth_storage_quota_claim": "സ്റ്റോറേജ് ക്വാട്ട ക്ലെയിം", + "oauth_storage_quota_claim_description": "ഉപയോക്താവിന്റെ സ്റ്റോറേജ് ക്വാട്ട ഈ ക്ലെയിമിന്റെ മൂല്യത്തിലേക്ക് യാന്ത്രികമായി സജ്ജമാക്കുക.", + "oauth_storage_quota_default": "ഡിഫോൾട്ട് സ്റ്റോറേജ് ക്വാട്ട (GiB)", + "oauth_storage_quota_default_description": "ക്ലെയിം ഒന്നും നൽകാത്തപ്പോൾ ഉപയോഗിക്കേണ്ട ക്വാട്ട (GiB-യിൽ).", + "oauth_timeout": "അഭ്യർത്ഥനയുടെ സമയപരിധി", + "oauth_timeout_description": "അഭ്യർത്ഥനകൾക്കുള്ള സമയപരിധി (മില്ലിസെക്കൻഡിൽ)", + "password_enable_description": "ഇമെയിലും പാസ്‌വേഡും ഉപയോഗിച്ച് ലോഗിൻ ചെയ്യുക", + "password_settings": "പാസ്‌വേഡ് ലോഗിൻ", + "password_settings_description": "പാസ്‌വേഡ് ലോഗിൻ ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", + "paths_validated_successfully": "എല്ലാ പാത്തുകളും വിജയകരമായി സാധൂകരിച്ചു", + "person_cleanup_job": "വ്യക്തി ക്ലീനപ്പ്", + "quota_size_gib": "ക്വാട്ട വലുപ്പം (GiB)", + "refreshing_all_libraries": "എല്ലാ ലൈബ്രറികളും പുതുക്കുന്നു", + "registration": "അഡ്മിൻ രജിസ്ട്രേഷൻ", + "registration_description": "നിങ്ങളാണ് സിസ്റ്റത്തിലെ ആദ്യത്തെ ഉപയോക്താവ് എന്നതിനാൽ, നിങ്ങളെ അഡ്മിൻ ആയി നിയമിക്കും. ഭരണപരമായ ജോലികൾക്ക് നിങ്ങൾ ഉത്തരവാദിയായിരിക്കും, കൂടാതെ അധിക ഉപയോക്താക്കളെ സൃഷ്ടിക്കുന്നത് നിങ്ങളായിരിക്കും.", + "require_password_change_on_login": "ആദ്യ ലോഗിൻ ചെയ്യുമ്പോൾ പാസ്‌വേഡ് മാറ്റാൻ ഉപയോക്താവിനോട് ആവശ്യപ്പെടുക", + "reset_settings_to_default": "ക്രമീകരണങ്ങൾ ഡിഫോൾട്ടിലേക്ക് പുനഃസജ്ജമാക്കുക", + "reset_settings_to_recent_saved": "അടുത്തിടെ സേവ് ചെയ്ത ക്രമീകരണങ്ങളിലേക്ക് പുനഃസജ്ജമാക്കുക", + "scanning_library": "ലൈബ്രറി സ്കാൻ ചെയ്യുന്നു", + "search_jobs": "ജോലികൾക്കായി തിരയുക…", + "send_welcome_email": "സ്വാഗത ഇമെയിൽ അയക്കുക", + "server_external_domain_settings": "ബാഹ്യ ഡൊമെയ്ൻ", + "server_external_domain_settings_description": "പൊതുവായി പങ്കിട്ട ലിങ്കുകൾക്കുള്ള ഡൊമെയ്ൻ, http(s):// ഉൾപ്പെടെ", + "server_public_users": "പൊതു ഉപയോക്താക്കൾ", + "server_public_users_description": "പങ്കിട്ട ആൽബങ്ങളിലേക്ക് ഒരു ഉപയോക്താവിനെ ചേർക്കുമ്പോൾ എല്ലാ ഉപയോക്താക്കളെയും (പേരും ഇമെയിലും) ലിസ്റ്റ് ചെയ്യും. പ്രവർത്തനരഹിതമാക്കുമ്പോൾ, ഉപയോക്തൃ ലിസ്റ്റ് അഡ്മിൻ ഉപയോക്താക്കൾക്ക് മാത്രമേ ലഭ്യമാകൂ.", + "server_settings": "സെർവർ ക്രമീകരണങ്ങൾ", + "server_settings_description": "സെർവർ ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", + "server_welcome_message": "സ്വാഗത സന്ദേശം", + "server_welcome_message_description": "ലോഗിൻ പേജിൽ പ്രദർശിപ്പിക്കുന്ന ഒരു സന്ദേശം.", + "sidecar_job": "സൈഡ്‌കാർ മെറ്റാഡാറ്റ", + "sidecar_job_description": "ഫയൽസിസ്റ്റത്തിൽ നിന്ന് സൈഡ്‌കാർ മെറ്റാഡാറ്റ കണ്ടെത്തുകയോ സിൻക്രൊണൈസ് ചെയ്യുകയോ ചെയ്യുക", + "slideshow_duration_description": "ഓരോ ചിത്രവും പ്രദർശിപ്പിക്കാനുള്ള സെക്കൻഡുകളുടെ എണ്ണം", + "smart_search_job_description": "സ്മാർട്ട് സെർച്ചിനെ പിന്തുണയ്ക്കുന്നതിനായി അസറ്റുകളിൽ മെഷീൻ ലേണിംഗ് പ്രവർത്തിപ്പിക്കുക", + "storage_template_date_time_description": "ഡേറ്റ്ടൈം വിവരങ്ങൾക്കായി അസറ്റിന്റെ ക്രിയേഷൻ ടൈംസ്റ്റാമ്പ് ഉപയോഗിക്കുന്നു", + "storage_template_date_time_sample": "സാമ്പിൾ സമയം {date}", + "storage_template_enable_description": "സ്റ്റോറേജ് ടെംപ്ലേറ്റ് എഞ്ചിൻ പ്രവർത്തനക്ഷമമാക്കുക", + "storage_template_hash_verification_enabled": "ഹാഷ് വെരിഫിക്കേഷൻ പ്രവർത്തനക്ഷമമാക്കി", + "storage_template_hash_verification_enabled_description": "ഹാഷ് വെരിഫിക്കേഷൻ പ്രവർത്തനക്ഷമമാക്കുന്നു, പ്രത്യാഘാതങ്ങളെക്കുറിച്ച് ഉറപ്പില്ലെങ്കിൽ ഇത് പ്രവർത്തനരഹിതമാക്കരുത്", + "storage_template_migration": "സ്റ്റോറേജ് ടെംപ്ലേറ്റ് മൈഗ്രേഷൻ", + "storage_template_migration_description": "മുമ്പ് അപ്‌ലോഡ് ചെയ്ത അസറ്റുകളിലേക്ക് നിലവിലെ {template} പ്രയോഗിക്കുക", + "storage_template_migration_info": "സ്റ്റോറേജ് ടെംപ്ലേറ്റ് എല്ലാ എക്സ്റ്റൻഷനുകളെയും ചെറിയക്ഷരത്തിലേക്ക് മാറ്റും. ടെംപ്ലേറ്റിലെ മാറ്റങ്ങൾ പുതിയ അസറ്റുകൾക്ക് മാത്രമേ ബാധകമാകൂ. മുമ്പ് അപ്‌ലോഡ് ചെയ്ത അസറ്റുകളിൽ ടെംപ്ലേറ്റ് മുൻകാല പ്രാബല്യത്തോടെ പ്രയോഗിക്കാൻ, {job} പ്രവർത്തിപ്പിക്കുക.", + "storage_template_migration_job": "സ്റ്റോറേജ് ടെംപ്ലേറ്റ് മൈഗ്രേഷൻ ജോലി", + "storage_template_more_details": "ഈ ഫീച്ചറിനെക്കുറിച്ചുള്ള കൂടുതൽ വിവരങ്ങൾക്ക്, സ്റ്റോറേജ് ടെംപ്ലേറ്റ്-ഉം അതിൻ്റെ പ്രത്യാഘാതങ്ങളും പരിശോധിക്കുക", + "storage_template_onboarding_description_v2": "പ്രവർത്തനക്ഷമമാക്കുമ്പോൾ, ഉപയോക്താവ് നിർവചിച്ച ടെംപ്ലേറ്റ് അടിസ്ഥാനമാക്കി ഈ ഫീച്ചർ ഫയലുകൾ യാന്ത്രികമായി ഓർഗനൈസ് ചെയ്യും. കൂടുതൽ വിവരങ്ങൾക്ക്, ദയവായി ഡോക്യുമെന്റേഷൻ കാണുക.", + "storage_template_path_length": "ഏകദേശ പാത്ത് ദൈർഘ്യ പരിധി: {length, number}/{limit, number}", + "storage_template_settings": "സ്റ്റോറേജ് ടെംപ്ലേറ്റ്", + "storage_template_settings_description": "അപ്‌ലോഡ് ചെയ്യുന്ന അസറ്റിന്റെ ഫോൾഡർ ഘടനയും ഫയൽ നാമവും കൈകാര്യം ചെയ്യുക", + "storage_template_user_label": "{label} എന്നത് ഉപയോക്താവിന്റെ സ്റ്റോറേജ് ലേബലാണ്", + "system_settings": "സിസ്റ്റം ക്രമീകരണങ്ങൾ", + "tag_cleanup_job": "ടാഗ് ക്ലീനപ്പ്", + "template_email_available_tags": "നിങ്ങളുടെ ടെംപ്ലേറ്റിൽ ഇനിപ്പറയുന്ന വേരിയബിളുകൾ ഉപയോഗിക്കാം: {tags}", + "template_email_if_empty": "ടെംപ്ലേറ്റ് ശൂന്യമാണെങ്കിൽ, ഡിഫോൾട്ട് ഇമെയിൽ ഉപയോഗിക്കും.", + "template_email_invite_album": "ആൽബം ക്ഷണിക്കാനുള്ള ടെംപ്ലേറ്റ്", + "template_email_preview": "പ്രിവ്യൂ", + "template_email_settings": "ഇമെയിൽ ടെംപ്ലേറ്റുകൾ", + "template_email_update_album": "ആൽബം അപ്ഡേറ്റ് ടെംപ്ലേറ്റ്", + "template_email_welcome": "സ്വാഗത ഇമെയിൽ ടെംപ്ലേറ്റ്", + "template_settings": "അറിയിപ്പ് ടെംപ്ലേറ്റുകൾ", + "template_settings_description": "അറിയിപ്പുകൾക്കായി ഇഷ്ടാനുസൃത ടെംപ്ലേറ്റുകൾ കൈകാര്യം ചെയ്യുക", + "theme_custom_css_settings": "കസ്റ്റം CSS", + "theme_custom_css_settings_description": "കാസ്കേഡിംഗ് സ്റ്റൈൽ ഷീറ്റുകൾ (CSS) Immich-ന്റെ ഡിസൈൻ ഇഷ്ടാനുസൃതമാക്കാൻ അനുവദിക്കുന്നു.", + "theme_settings": "തീം ക്രമീകരണങ്ങൾ", + "theme_settings_description": "Immich വെബ് ഇന്റർഫേസിന്റെ കസ്റ്റമൈസേഷൻ കൈകാര്യം ചെയ്യുക", + "thumbnail_generation_job": "തംബ്നെയിലുകൾ നിർമ്മിക്കുക", + "thumbnail_generation_job_description": "ഓരോ അസറ്റിനും വലുതും ചെറുതും മങ്ങിയതുമായ തംബ്നെയിലുകളും ഓരോ വ്യക്തിക്കും തംബ്നെയിലുകളും നിർമ്മിക്കുക", + "transcoding_acceleration_api": "ആക്സിലറേഷൻ API", + "transcoding_acceleration_api_description": "ട്രാൻസ്‌കോഡിംഗ് ത്വരിതപ്പെടുത്തുന്നതിന് നിങ്ങളുടെ ഉപകരണവുമായി സംവദിക്കുന്ന API. ഈ ക്രമീകരണം 'ബെസ്റ്റ് എഫേർട്ട്' ആണ്: പരാജയപ്പെട്ടാൽ സോഫ്റ്റ്‌വെയർ ട്രാൻസ്‌കോഡിംഗിലേക്ക് മാറും. നിങ്ങളുടെ ഹാർഡ്‌വെയർ അനുസരിച്ച് VP9 പ്രവർത്തിക്കുകയോ പ്രവർത്തിക്കാതിരിക്കുകയോ ചെയ്യാം.", + "transcoding_acceleration_nvenc": "NVENC (NVIDIA GPU ആവശ്യമാണ്)", + "transcoding_acceleration_qsv": "ക്വിക്ക് സിങ്ക് (7-ാം തലമുറ ഇന്റൽ സിപിയു അല്ലെങ്കിൽ അതിനുശേഷമുള്ളത് ആവശ്യമാണ്)", + "transcoding_acceleration_rkmpp": "RKMPP (Rockchip SOC-കളിൽ മാത്രം)", + "transcoding_acceleration_vaapi": "VAAPI", + "transcoding_accepted_audio_codecs": "അംഗീകൃത ഓഡിയോ കോഡെക്കുകൾ", + "transcoding_accepted_audio_codecs_description": "ട്രാൻസ്‌കോഡ് ചെയ്യേണ്ടാത്ത ഓഡിയോ കോഡെക്കുകൾ തിരഞ്ഞെടുക്കുക. ചില ട്രാൻസ്‌കോഡ് നയങ്ങൾക്ക് വേണ്ടി മാത്രം ഉപയോഗിക്കുന്നു.", + "transcoding_accepted_containers": "അംഗീകൃത കണ്ടെയ്‌നറുകൾ", + "transcoding_accepted_containers_description": "MP4-ലേക്ക് റീമക്സ് ചെയ്യേണ്ടാത്ത കണ്ടെയ്‌നർ ഫോർമാറ്റുകൾ തിരഞ്ഞെടുക്കുക. ചില ട്രാൻസ്‌കോഡ് നയങ്ങൾക്ക് വേണ്ടി മാത്രം ഉപയോഗിക്കുന്നു.", + "transcoding_accepted_video_codecs": "അംഗീകൃത വീഡിയോ കോഡെക്കുകൾ", + "transcoding_accepted_video_codecs_description": "ട്രാൻസ്‌കോഡ് ചെയ്യേണ്ടാത്ത വീഡിയോ കോഡെക്കുകൾ തിരഞ്ഞെടുക്കുക. ചില ട്രാൻസ്‌കോഡ് നയങ്ങൾക്ക് വേണ്ടി മാത്രം ഉപയോഗിക്കുന്നു.", + "transcoding_advanced_options_description": "മിക്ക ഉപയോക്താക്കളും മാറ്റം വരുത്തേണ്ടതില്ലാത്ത ഓപ്ഷനുകൾ", + "transcoding_audio_codec": "ഓഡിയോ കോഡെക്", + "transcoding_audio_codec_description": "Opus ഏറ്റവും ഉയർന്ന നിലവാരമുള്ള ഓപ്ഷനാണ്, പക്ഷേ പഴയ ഉപകരണങ്ങളുമായോ സോഫ്റ്റ്‌വെയറുമായോ ഇതിന് കുറഞ്ഞ അനുയോജ്യതയേ ഉള്ളൂ.", + "transcoding_bitrate_description": "പരമാവധി ബിറ്റ്റേറ്റിനേക്കാൾ ഉയർന്നതോ അംഗീകൃത ഫോർമാറ്റിൽ അല്ലാത്തതോ ആയ വീഡിയോകൾ", + "transcoding_codecs_learn_more": "ഇവിടെ ഉപയോഗിച്ചിരിക്കുന്ന പദങ്ങളെക്കുറിച്ച് കൂടുതലറിയാൻ, H.264 കോഡെക്, HEVC കോഡെക്, VP9 കോഡെക് എന്നിവയുടെ FFmpeg ഡോക്യുമെന്റേഷൻ പരിശോധിക്കുക.", + "transcoding_constant_quality_mode": "സ്ഥിരമായ ഗുണമേന്മ മോഡ്", + "transcoding_constant_quality_mode_description": "CQP-യെക്കാൾ മികച്ചതാണ് ICQ, എന്നാൽ ചില ഹാർഡ്‌വെയർ ആക്സിലറേഷൻ ഉപകരണങ്ങൾ ഈ മോഡിനെ പിന്തുണയ്ക്കുന്നില്ല. ഗുണമേന്മ അടിസ്ഥാനമാക്കിയുള്ള എൻകോഡിംഗ് ഉപയോഗിക്കുമ്പോൾ ഈ ഓപ്ഷൻ സജ്ജീകരിക്കുന്നത് നിർദ്ദിഷ്ട മോഡിന് മുൻഗണന നൽകും. ICQ പിന്തുണയ്ക്കാത്തതിനാൽ NVENC ഇത് അവഗണിക്കുന്നു.", + "transcoding_constant_rate_factor": "കോൺസ്റ്റന്റ് റേറ്റ് ഫാക്ടർ (-crf)", + "transcoding_constant_rate_factor_description": "വീഡിയോയുടെ ഗുണമേന്മ നില. സാധാരണ മൂല്യങ്ങൾ H.264-ന് 23, HEVC-ക്ക് 28, VP9-ന് 31, AV1-ന് 35 എന്നിങ്ങനെയാണ്. കുറഞ്ഞ മൂല്യം മികച്ചതാണ്, പക്ഷേ വലിയ ഫയലുകൾ ഉണ്ടാക്കുന്നു.", + "transcoding_disabled_description": "ഒരു വീഡിയോയും ട്രാൻസ്‌കോഡ് ചെയ്യരുത്, ഇത് ചില ക്ലയിന്റുകളിൽ പ്ലേബാക്ക് തടസ്സപ്പെടുത്തിയേക്കാം", + "transcoding_encoding_options": "എൻകോഡിംഗ് ഓപ്ഷനുകൾ", + "transcoding_encoding_options_description": "എൻകോഡ് ചെയ്ത വീഡിയോകൾക്കായി കോഡെക്കുകൾ, റെസല്യൂഷൻ, ഗുണമേന്മ, മറ്റ് ഓപ്ഷനുകൾ എന്നിവ സജ്ജമാക്കുക", + "transcoding_hardware_acceleration": "ഹാർഡ്‌വെയർ ആക്സിലറേഷൻ", + "transcoding_hardware_acceleration_description": "പരീക്ഷണാടിസ്ഥാനത്തിലുള്ളത്: വേഗതയേറിയ ട്രാൻസ്‌കോഡിംഗ്, എന്നാൽ ഒരേ ബിറ്റ്റേറ്റിൽ ഗുണമേന്മ കുറച്ചേക്കാം", + "transcoding_hardware_decoding": "ഹാർഡ്‌വെയർ ഡീകോഡിംഗ്", + "transcoding_hardware_decoding_setting_description": "എൻകോഡിംഗ് മാത്രം ത്വരിതപ്പെടുത്തുന്നതിന് പകരം എൻഡ്-ടു-എൻഡ് ആക്സിലറേഷൻ പ്രവർത്തനക്ഷമമാക്കുന്നു. എല്ലാ വീഡിയോകളിലും പ്രവർത്തിച്ചേക്കില്ല.", + "transcoding_max_b_frames": "പരമാവധി ബി-ഫ്രെയിമുകൾ", + "transcoding_max_b_frames_description": "ഉയർന്ന മൂല്യങ്ങൾ കംപ്രഷൻ കാര്യക്ഷമത മെച്ചപ്പെടുത്തുന്നു, പക്ഷേ എൻകോഡിംഗ് വേഗത കുറയ്ക്കുന്നു. പഴയ ഉപകരണങ്ങളിലെ ഹാർഡ്‌വെയർ ആക്സിലറേഷനുമായി പൊരുത്തപ്പെടണമെന്നില്ല. 0 ബി-ഫ്രെയിമുകൾ പ്രവർത്തനരഹിതമാക്കുന്നു, അതേസമയം -1 ഈ മൂല്യം യാന്ത്രികമായി സജ്ജീകരിക്കുന്നു.", + "transcoding_max_bitrate": "പരമാവധി ബിറ്റ്റേറ്റ്", + "transcoding_max_bitrate_description": "ഒരു പരമാവധി ബിറ്റ്റേറ്റ് സജ്ജീകരിക്കുന്നത് ഗുണമേന്മയിൽ ചെറിയൊരു വിട്ടുവീഴ്ചയോടെ ഫയൽ വലുപ്പങ്ങൾ കൂടുതൽ പ്രവചനാതീതമാക്കും. 720p-ൽ, സാധാരണ മൂല്യങ്ങൾ VP9 അല്ലെങ്കിൽ HEVC-ക്ക് 2600 kbit/s, അല്ലെങ്കിൽ H.264-ന് 4500 kbit/s ആണ്. 0 ആയി സജ്ജീകരിച്ചാൽ പ്രവർത്തനരഹിതമാകും.", + "transcoding_max_keyframe_interval": "പരമാവധി കീഫ്രെയിം ഇടവേള", + "transcoding_max_keyframe_interval_description": "കീഫ്രെയിമുകൾക്കിടയിലുള്ള പരമാവധി ഫ്രെയിം ദൂരം സജ്ജമാക്കുന്നു. കുറഞ്ഞ മൂല്യങ്ങൾ കംപ്രഷൻ കാര്യക്ഷമത കുറയ്ക്കുന്നു, പക്ഷേ സീക്ക് സമയം മെച്ചപ്പെടുത്തുകയും വേഗതയേറിയ ചലനമുള്ള രംഗങ്ങളിൽ ഗുണമേന്മ മെച്ചപ്പെടുത്തുകയും ചെയ്തേക്കാം. 0 ഈ മൂല്യം യാന്ത്രികമായി സജ്ജീകരിക്കുന്നു.", + "transcoding_optimal_description": "ലക്ഷ്യമിട്ട റെസല്യൂഷനേക്കാൾ ഉയർന്നതോ അംഗീകൃത ഫോർമാറ്റിൽ അല്ലാത്തതോ ആയ വീഡിയോകൾ", + "transcoding_policy": "ട്രാൻസ്‌കോഡ് നയം", + "transcoding_policy_description": "ഒരു വീഡിയോ എപ്പോൾ ട്രാൻസ്‌കോഡ് ചെയ്യണമെന്ന് സജ്ജമാക്കുക", + "transcoding_preferred_hardware_device": "തിരഞ്ഞെടുക്കുന്ന ഹാർഡ്‌വെയർ ഉപകരണം", + "transcoding_preferred_hardware_device_description": "VAAPI, QSV എന്നിവയ്ക്ക് മാത്രം ബാധകം. ഹാർഡ്‌വെയർ ട്രാൻസ്‌കോഡിംഗിനായി ഉപയോഗിക്കുന്ന dri നോഡ് സജ്ജമാക്കുന്നു.", + "transcoding_preset_preset": "പ്രീസെറ്റ് (-preset)", + "transcoding_preset_preset_description": "കംപ്രഷൻ വേഗത. വേഗത കുറഞ്ഞ പ്രീസെറ്റുകൾ ചെറിയ ഫയലുകൾ നിർമ്മിക്കുന്നു, ഒരു നിശ്ചിത ബിറ്റ്റേറ്റ് ലക്ഷ്യമിടുമ്പോൾ ഗുണമേന്മ വർദ്ധിപ്പിക്കുന്നു. 'faster'-നേക്കാൾ ഉയർന്ന വേഗത VP9 അവഗണിക്കുന്നു.", + "transcoding_reference_frames": "റഫറൻസ് ഫ്രെയിമുകൾ", + "transcoding_reference_frames_description": "നൽകിയിരിക്കുന്ന ഒരു ഫ്രെയിം കംപ്രസ് ചെയ്യുമ്പോൾ റഫറൻസ് ചെയ്യേണ്ട ഫ്രെയിമുകളുടെ എണ്ണം. ഉയർന്ന മൂല്യങ്ങൾ കംപ്രഷൻ കാര്യക്ഷമത മെച്ചപ്പെടുത്തുന്നു, പക്ഷേ എൻകോഡിംഗ് വേഗത കുറയ്ക്കുന്നു. 0 ഈ മൂല്യം യാന്ത്രികമായി സജ്ജീകരിക്കുന്നു.", + "transcoding_required_description": "അംഗീകൃത ഫോർമാറ്റിൽ അല്ലാത്ത വീഡിയോകൾ മാത്രം", + "transcoding_settings": "വീഡിയോ ട്രാൻസ്കോഡിംഗ് ക്രമീകരണങ്ങൾ", + "transcoding_settings_description": "ഏതൊക്കെ വീഡിയോകളാണ് ട്രാൻസ്‌കോഡ് ചെയ്യേണ്ടതെന്നും അവ എങ്ങനെ പ്രോസസ്സ് ചെയ്യണമെന്നും കൈകാര്യം ചെയ്യുക", + "transcoding_target_resolution": "ലക്ഷ്യമിടുന്ന റെസല്യൂഷൻ", + "transcoding_target_resolution_description": "ഉയർന്ന റെസല്യൂഷനുകൾക്ക് കൂടുതൽ വിശദാംശങ്ങൾ സംരക്ഷിക്കാൻ കഴിയും, പക്ഷേ എൻകോഡ് ചെയ്യാൻ കൂടുതൽ സമയമെടുക്കും, വലിയ ഫയൽ വലുപ്പമുണ്ടാകും, കൂടാതെ ആപ്പിന്റെ പ്രതികരണശേഷി കുറയ്ക്കുകയും ചെയ്യും.", + "transcoding_temporal_aq": "ടെമ്പറൽ AQ", + "transcoding_temporal_aq_description": "NVENC-ക്ക് മാത്രം ബാധകം. ഉയർന്ന വിശദാംശങ്ങളും കുറഞ്ഞ ചലനവുമുള്ള രംഗങ്ങളുടെ ഗുണമേന്മ വർദ്ധിപ്പിക്കുന്നു. പഴയ ഉപകരണങ്ങളുമായി പൊരുത്തപ്പെടണമെന്നില്ല.", + "transcoding_threads": "ത്രെഡുകൾ", + "transcoding_threads_description": "ഉയർന്ന മൂല്യങ്ങൾ വേഗതയേറിയ എൻകോഡിംഗിലേക്ക് നയിക്കുന്നു, പക്ഷേ സെർവറിന് മറ്റ് ജോലികൾ പ്രോസസ്സ് ചെയ്യാൻ കുറച്ച് ഇടം നൽകുന്നു. ഈ മൂല്യം സിപിയു കോറുകളുടെ എണ്ണത്തേക്കാൾ കൂടുതലാകരുത്. 0 ആയി സജ്ജീകരിച്ചാൽ ഉപയോഗം പരമാവധിയാക്കുന്നു.", + "transcoding_tone_mapping": "ടോൺ-മാപ്പിംഗ്", + "transcoding_tone_mapping_description": "HDR വീഡിയോകൾ SDR-ലേക്ക് പരിവർത്തനം ചെയ്യുമ്പോൾ അവയുടെ രൂപം നിലനിർത്താൻ ശ്രമിക്കുന്നു. ഓരോ അൽഗോരിതവും നിറം, വിശദാംശങ്ങൾ, തെളിച്ചം എന്നിവയ്ക്കായി വ്യത്യസ്ത വിട്ടുവീഴ്ചകൾ ചെയ്യുന്നു. Hable വിശദാംശങ്ങൾ സംരക്ഷിക്കുന്നു, Mobius നിറം സംരക്ഷിക്കുന്നു, Reinhard തെളിച്ചം സംരക്ഷിക്കുന്നു.", + "transcoding_transcode_policy": "ട്രാൻസ്‌കോഡ് നയം", + "transcoding_transcode_policy_description": "ഒരു വീഡിയോ എപ്പോൾ ട്രാൻസ്‌കോഡ് ചെയ്യണം എന്നതിനെക്കുറിച്ചുള്ള നയം. HDR വീഡിയോകൾ എല്ലായ്പ്പോഴും ട്രാൻസ്‌കോഡ് ചെയ്യപ്പെടും (ട്രാൻസ്‌കോഡിംഗ് പ്രവർത്തനരഹിതമാക്കിയിട്ടില്ലെങ്കിൽ).", + "transcoding_two_pass_encoding": "ടു-പാസ് എൻകോഡിംഗ്", + "transcoding_two_pass_encoding_setting_description": "മെച്ചപ്പെട്ട എൻകോഡ് ചെയ്ത വീഡിയോകൾ നിർമ്മിക്കുന്നതിന് രണ്ട് പാസുകളിലായി ട്രാൻസ്‌കോഡ് ചെയ്യുക. പരമാവധി ബിറ്റ്റേറ്റ് പ്രവർത്തനക്ഷമമാക്കുമ്പോൾ (H.264, HEVC എന്നിവയ്‌ക്കൊപ്പം പ്രവർത്തിക്കാൻ ഇത് ആവശ്യമാണ്), ഈ മോഡ് പരമാവധി ബിറ്റ്റേറ്റിനെ അടിസ്ഥാനമാക്കി ഒരു ബിറ്റ്റേറ്റ് ശ്രേണി ഉപയോഗിക്കുകയും CRF അവഗണിക്കുകയും ചെയ്യുന്നു. VP9-നായി, പരമാവധി ബിറ്റ്റേറ്റ് പ്രവർത്തനരഹിതമാക്കിയിട്ടുണ്ടെങ്കിൽ CRF ഉപയോഗിക്കാം.", + "transcoding_video_codec": "വീഡിയോ കോഡെക്", + "transcoding_video_codec_description": "VP9-ന് ഉയർന്ന കാര്യക്ഷമതയും വെബ് അനുയോജ്യതയുമുണ്ട്, പക്ഷേ ട്രാൻസ്‌കോഡ് ചെയ്യാൻ കൂടുതൽ സമയമെടുക്കും. HEVC സമാനമായി പ്രവർത്തിക്കുന്നു, പക്ഷേ വെബ് അനുയോജ്യത കുറവാണ്. H.264 വ്യാപകമായി അനുയോജ്യവും വേഗത്തിൽ ട്രാൻസ്‌കോഡ് ചെയ്യാൻ കഴിയുന്നതുമാണ്, പക്ഷേ വളരെ വലിയ ഫയലുകൾ നിർമ്മിക്കുന്നു. AV1 ഏറ്റവും കാര്യക്ഷമമായ കോഡെക്കാണ്, പക്ഷേ പഴയ ഉപകരണങ്ങളിൽ ഇതിന് പിന്തുണയില്ല.", + "trash_enabled_description": "ട്രാഷ് ഫീച്ചറുകൾ പ്രവർത്തനക്ഷമമാക്കുക", + "trash_number_of_days": "ദിവസങ്ങളുടെ എണ്ണം", + "trash_number_of_days_description": "അസറ്റുകൾ ശാശ്വതമായി നീക്കം ചെയ്യുന്നതിനുമുമ്പ് ട്രാഷിൽ സൂക്ഷിക്കേണ്ട ദിവസങ്ങളുടെ എണ്ണം", + "trash_settings": "ട്രാഷ് ക്രമീകരണങ്ങൾ", + "trash_settings_description": "ട്രാഷ് ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", + "unlink_all_oauth_accounts": "എല്ലാ OAuth അക്കൗണ്ടുകളും അൺലിങ്ക് ചെയ്യുക", + "unlink_all_oauth_accounts_description": "ഒരു പുതിയ ദാതാവിലേക്ക് മാറുന്നതിന് മുമ്പ് എല്ലാ OAuth അക്കൗണ്ടുകളും അൺലിങ്ക് ചെയ്യാൻ ഓർമ്മിക്കുക.", + "unlink_all_oauth_accounts_prompt": "എല്ലാ OAuth അക്കൗണ്ടുകളും അൺലിങ്ക് ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ? ഇത് ഓരോ ഉപയോക്താവിന്റെയും OAuth ഐഡി റീസെറ്റ് ചെയ്യും, ഇത് പഴയപടിയാക്കാൻ കഴിയില്ല.", + "user_cleanup_job": "ഉപയോക്തൃ ക്ലീനപ്പ്", + "user_delete_delay": "{user}-ന്റെ അക്കൗണ്ടും അസറ്റുകളും {delay, plural, one {# ദിവസത്തിനുള്ളിൽ} other {# ദിവസങ്ങൾക്കുള്ളിൽ}} ശാശ്വതമായി ഇല്ലാതാക്കാൻ ഷെഡ്യൂൾ ചെയ്യും.", + "user_delete_delay_settings": "ഇല്ലാതാക്കാനുള്ള കാലതാമസം", + "user_delete_delay_settings_description": "ഒരു ഉപയോക്താവിന്റെ അക്കൗണ്ടും അസറ്റുകളും ശാശ്വതമായി ഇല്ലാതാക്കാൻ നീക്കം ചെയ്തതിന് ശേഷമുള്ള ദിവസങ്ങളുടെ എണ്ണം. ഇല്ലാതാക്കാൻ തയ്യാറായ ഉപയോക്താക്കളെ പരിശോധിക്കാൻ ഉപയോക്താവിനെ ഇല്ലാതാക്കാനുള്ള ജോലി അർദ്ധരാത്രിയിൽ പ്രവർത്തിക്കുന്നു. ഈ ക്രമീകരണത്തിലെ മാറ്റങ്ങൾ അടുത്ത എക്സിക്യൂഷനിൽ വിലയിരുത്തപ്പെടും.", + "user_delete_immediately": "{user}-ന്റെ അക്കൗണ്ടും അസറ്റുകളും ഉടനടി ശാശ്വതമായി ഇല്ലാതാക്കുന്നതിനായി ക്യൂ ചെയ്യും.", + "user_delete_immediately_checkbox": "ഉപയോക്താവിനെയും അസറ്റുകളെയും ഉടനടി ഇല്ലാതാക്കാൻ ക്യൂ ചെയ്യുക", + "user_details": "ഉപയോക്തൃ വിശദാംശങ്ങൾ", + "user_management": "ഉപയോക്തൃ മാനേജ്മെന്റ്", + "user_password_has_been_reset": "ഉപയോക്താവിന്റെ പാസ്‌വേഡ് റീസെറ്റ് ചെയ്തു:", + "user_password_reset_description": "ദയവായി ഉപയോക്താവിന് താൽക്കാലിക പാസ്‌വേഡ് നൽകുക, അടുത്ത ലോഗിൻ ചെയ്യുമ്പോൾ പാസ്‌വേഡ് മാറ്റേണ്ടിവരുമെന്ന് അവരെ അറിയിക്കുക.", + "user_restore_description": "{user}-ന്റെ അക്കൗണ്ട് പുനഃസ്ഥാപിക്കും.", + "user_restore_scheduled_removal": "ഉപയോക്താവിനെ പുനഃസ്ഥാപിക്കുക - {date, date, long}-ന് നീക്കംചെയ്യാൻ ഷെഡ്യൂൾ ചെയ്‌തിരിക്കുന്നു", + "user_settings": "ഉപയോക്താവിന്റെ ക്രമീകരണങ്ങൾ", + "user_settings_description": "ഉപയോക്തൃ ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", + "user_successfully_removed": "{email} എന്ന ഉപയോക്താവിനെ വിജയകരമായി നീക്കംചെയ്തു.", + "version_check_enabled_description": "പതിപ്പ് പരിശോധന പ്രവർത്തനക്ഷമമാക്കുക", + "version_check_implications": "പതിപ്പ് പരിശോധന ഫീച്ചർ github.com-മായി ആനുകാലിക ആശയവിനിമയത്തെ ആശ്രയിച്ചിരിക്കുന്നു", + "version_check_settings": "പതിപ്പ് പരിശോധന", + "version_check_settings_description": "പുതിയ പതിപ്പിന്റെ അറിയിപ്പ് പ്രവർത്തനക്ഷമമാക്കുക/പ്രവർത്തനരഹിതമാക്കുക", + "video_conversion_job": "വീഡിയോകൾ ട്രാൻസ്‌കോഡ് ചെയ്യുക", + "video_conversion_job_description": "ബ്രൗസറുകളുമായും ഉപകരണങ്ങളുമായും കൂടുതൽ അനുയോജ്യതയ്ക്കായി വീഡിയോകൾ ട്രാൻസ്‌കോഡ് ചെയ്യുക" }, + "admin_email": "അഡ്മിൻ ഇമെയിൽ", + "admin_password": "അഡ്മിൻ പാസ്‌വേഡ്", + "administration": "അഡ്മിനിസ്ട്രേഷൻ", + "advanced": "വിപുലമായത്", + "advanced_settings_enable_alternate_media_filter_subtitle": "ഇതര മാനദണ്ഡങ്ങളെ അടിസ്ഥാനമാക്കി സിങ്ക് സമയത്ത് മീഡിയ ഫിൽട്ടർ ചെയ്യാൻ ഈ ഓപ്ഷൻ ഉപയോഗിക്കുക. എല്ലാ ആൽബങ്ങളും കണ്ടെത്തുന്നതിൽ ആപ്പിന് പ്രശ്നങ്ങളുണ്ടെങ്കിൽ മാത്രം ഇത് പരീക്ഷിക്കുക.", + "advanced_settings_enable_alternate_media_filter_title": "[പരീക്ഷണാടിസ്ഥാനത്തിൽ] ഇതര ഉപകരണ ആൽബം സിങ്ക് ഫിൽട്ടർ ഉപയോഗിക്കുക", + "advanced_settings_log_level_title": "ലോഗ് ലെവൽ: {level}", + "advanced_settings_prefer_remote_subtitle": "ചില ഉപകരണങ്ങളിൽ പ്രാദേശിക അസറ്റുകളിൽ നിന്ന് തംബ്നെയിലുകൾ ലോഡുചെയ്യാൻ വളരെ വേഗത കുറവാണ്. പകരം റിമോട്ട് ചിത്രങ്ങൾ ലോഡുചെയ്യാൻ ഈ ക്രമീകരണം സജീവമാക്കുക.", + "advanced_settings_prefer_remote_title": "റിമോട്ട് ചിത്രങ്ങൾക്ക് മുൻഗണന നൽകുക", + "advanced_settings_proxy_headers_subtitle": "ഓരോ നെറ്റ്‌വർക്ക് അഭ്യർത്ഥനയ്‌ക്കൊപ്പവും Immich അയയ്‌ക്കേണ്ട പ്രോക്സി ഹെഡറുകൾ നിർവചിക്കുക", + "advanced_settings_proxy_headers_title": "പ്രോക്സി ഹെഡറുകൾ", + "advanced_settings_readonly_mode_subtitle": "ഫോട്ടോകൾ മാത്രം കാണാൻ കഴിയുന്ന റീഡ്-ഓൺലി മോഡ് പ്രവർത്തനക്ഷമമാക്കുന്നു. ഒന്നിലധികം ചിത്രങ്ങൾ തിരഞ്ഞെടുക്കൽ, പങ്കിടൽ, കാസ്റ്റിംഗ്, ഇല്ലാതാക്കൽ എന്നിവയെല്ലാം പ്രവർത്തനരഹിതമാകും. പ്രധാന സ്ക്രീനിലെ ഉപയോക്തൃ അവതാർ വഴി റീഡ്-ഓൺലി പ്രവർത്തനക്ഷമമാക്കുക/പ്രവർത്തനരഹിതമാക്കുക.", + "advanced_settings_readonly_mode_title": "റീഡ്-ഓൺലി മോഡ്", + "advanced_settings_self_signed_ssl_subtitle": "സെർവർ എൻഡ്‌പോയിന്റിനായുള്ള SSL സർട്ടിഫിക്കറ്റ് പരിശോധന ഒഴിവാക്കുന്നു. സ്വയം ഒപ്പിട്ട സർട്ടിഫിക്കറ്റുകൾക്ക് ആവശ്യമാണ്.", + "advanced_settings_self_signed_ssl_title": "സ്വയം ഒപ്പിട്ട SSL സർട്ടിഫിക്കറ്റുകൾ അനുവദിക്കുക", + "advanced_settings_sync_remote_deletions_subtitle": "വെബിൽ ആ പ്രവർത്തനം നടത്തുമ്പോൾ ഈ ഉപകരണത്തിലെ ഒരു അസറ്റ് യാന്ത്രികമായി ഇല്ലാതാക്കുകയോ പുനഃസ്ഥാപിക്കുകയോ ചെയ്യുക", + "advanced_settings_sync_remote_deletions_title": "റിമോട്ട് ഇല്ലാതാക്കലുകൾ സിങ്ക് ചെയ്യുക [പരീക്ഷണാടിസ്ഥാനത്തിൽ]", + "advanced_settings_tile_subtitle": "വിപുലമായ ഉപയോക്തൃ ക്രമീകരണങ്ങൾ", + "advanced_settings_troubleshooting_subtitle": "ട്രബിൾഷൂട്ടിംഗിനായി അധിക ഫീച്ചറുകൾ പ്രവർത്തനക്ഷമമാക്കുക", + "advanced_settings_troubleshooting_title": "ട്രബിൾഷൂട്ടിംഗ്", + "age_months": "പ്രായം {months, plural, one {# മാസം} other {# മാസം}}", + "age_year_months": "പ്രായം 1 വർഷം, {months, plural, one {# മാസം} other {# മാസം}}", + "age_years": "{years, plural, other {പ്രായം #}}", + "album_added": "ആൽബം ചേർത്തു", + "album_added_notification_setting_description": "നിങ്ങളെ ഒരു പങ്കിട്ട ആൽബത്തിലേക്ക് ചേർക്കുമ്പോൾ ഒരു ഇമെയിൽ അറിയിപ്പ് സ്വീകരിക്കുക", + "album_cover_updated": "ആൽബം കവർ അപ്ഡേറ്റ് ചെയ്തു", + "album_delete_confirmation": "നിങ്ങൾക്ക് {album} ആൽബം ഇല്ലാതാക്കണമെന്ന് ഉറപ്പാണോ?", + "album_delete_confirmation_description": "ഈ ആൽബം പങ്കിട്ടതാണെങ്കിൽ, മറ്റ് ഉപയോക്താക്കൾക്ക് ഇനി ഇത് ആക്‌സസ് ചെയ്യാൻ കഴിയില്ല.", + "album_deleted": "ആൽബം ഇല്ലാതാക്കി", + "album_info_card_backup_album_excluded": "ഒഴിവാക്കി", + "album_info_card_backup_album_included": "ഉൾപ്പെടുത്തി", + "album_info_updated": "ആൽബം വിവരങ്ങൾ അപ്ഡേറ്റ് ചെയ്തു", + "album_leave": "ആൽബം വിടുകയാണോ?", + "album_leave_confirmation": "{album} വിട്ടുപോകണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", + "album_name": "ആൽബത്തിന്റെ പേര്", + "album_options": "ആൽബം ഓപ്ഷനുകൾ", + "album_remove_user": "ഉപയോക്താവിനെ നീക്കം ചെയ്യണോ?", + "album_remove_user_confirmation": "{user}-നെ നീക്കം ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", + "album_search_not_found": "നിങ്ങളുടെ തിരയലുമായി പൊരുത്തപ്പെടുന്ന ആൽബങ്ങളൊന്നും കണ്ടെത്തിയില്ല", + "album_share_no_users": "നിങ്ങൾ ഈ ആൽബം എല്ലാ ഉപയോക്താക്കളുമായും പങ്കിട്ടു, അല്ലെങ്കിൽ നിങ്ങൾക്ക് പങ്കിടാൻ ഉപയോക്താക്കളാരും ഇല്ലെന്നു തോന്നുന്നു.", + "album_summary": "ആൽബത്തിന്റെ സംഗ്രഹം", + "album_updated": "ആൽബം അപ്ഡേറ്റ് ചെയ്തു", + "album_updated_setting_description": "ഒരു പങ്കിട്ട ആൽബത്തിൽ പുതിയ അസറ്റുകൾ ഉണ്ടാകുമ്പോൾ ഒരു ഇമെയിൽ അറിയിപ്പ് സ്വീകരിക്കുക", + "album_user_left": "{album} വിട്ടുപോയി", + "album_user_removed": "{user}-നെ നീക്കം ചെയ്തു", + "album_viewer_appbar_delete_confirm": "നിങ്ങളുടെ അക്കൗണ്ടിൽ നിന്ന് ഈ ആൽബം ഇല്ലാതാക്കണമെന്ന് ഉറപ്പാണോ?", + "album_viewer_appbar_share_err_delete": "ആൽബം ഇല്ലാതാക്കുന്നതിൽ പരാജയപ്പെട്ടു", + "album_viewer_appbar_share_err_leave": "ആൽബം വിടുന്നതിൽ പരാജയപ്പെട്ടു", + "album_viewer_appbar_share_err_remove": "ആൽബത്തിൽ നിന്ന് അസറ്റുകൾ നീക്കം ചെയ്യുന്നതിൽ പ്രശ്നങ്ങളുണ്ട്", + "album_viewer_appbar_share_err_title": "ആൽബത്തിന്റെ ശീർഷകം മാറ്റുന്നതിൽ പരാജയപ്പെട്ടു", + "album_viewer_appbar_share_leave": "ആൽബം വിടുക", + "album_viewer_appbar_share_to": "ഇതിലേക്ക് പങ്കിടുക", + "album_viewer_page_share_add_users": "ഉപയോക്താക്കളെ ചേർക്കുക", + "album_with_link_access": "ലിങ്കുള്ള ആർക്കും ഈ ആൽബത്തിലെ ഫോട്ടോകളും ആളുകളെയും കാണാൻ അനുവദിക്കുക.", + "albums": "ആൽബങ്ങൾ", + "albums_count": "{count, plural, one {{count, number} ആൽബം} other {{count, number} ആൽബങ്ങൾ}}", + "albums_default_sort_order": "ഡിഫോൾട്ട് ആൽബം സോർട്ട് ഓർഡർ", + "albums_default_sort_order_description": "പുതിയ ആൽബങ്ങൾ സൃഷ്ടിക്കുമ്പോൾ പ്രാരംഭ അസറ്റ് സോർട്ട് ഓർഡർ.", + "albums_feature_description": "മറ്റ് ഉപയോക്താക്കളുമായി പങ്കിടാൻ കഴിയുന്ന അസറ്റുകളുടെ ശേഖരം.", + "albums_on_device_count": "ഉപകരണത്തിലെ ആൽബങ്ങൾ ({count})", + "all": "എല്ലാം", + "all_albums": "എല്ലാ ആൽബങ്ങളും", + "all_people": "എല്ലാ ആളുകളും", + "all_videos": "എല്ലാ വീഡിയോകളും", + "allow_dark_mode": "ഡാർക്ക് മോഡ് അനുവദിക്കുക", + "allow_edits": "എഡിറ്റുകൾ അനുവദിക്കുക", + "allow_public_user_to_download": "പൊതു ഉപയോക്താവിനെ ഡൗൺലോഡ് ചെയ്യാൻ അനുവദിക്കുക", + "allow_public_user_to_upload": "പൊതു ഉപയോക്താവിനെ അപ്‌ലോഡ് ചെയ്യാൻ അനുവദിക്കുക", + "alt_text_qr_code": "QR കോഡ് ചിത്രം", + "anti_clockwise": "അപ്രദക്ഷിണമായി", + "api_key": "API കീ", + "api_key_description": "ഈ മൂല്യം ഒരു തവണ മാത്രമേ കാണിക്കൂ. വിൻഡോ അടയ്ക്കുന്നതിന് മുമ്പ് ഇത് പകർത്തുന്നത് ഉറപ്പാക്കുക.", + "api_key_empty": "നിങ്ങളുടെ API കീയുടെ പേര് ശൂന്യമാകരുത്", + "api_keys": "API കീകൾ", + "app_bar_signout_dialog_content": "സൈൻ ഔട്ട് ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", + "app_bar_signout_dialog_ok": "അതെ", + "app_bar_signout_dialog_title": "സൈൻ ഔട്ട്", + "app_settings": "ആപ്പ് ക്രമീകരണങ്ങൾ", + "appears_in": "ഇതിൽ കാണപ്പെടുന്നു", + "apply_count": "പ്രയോഗിക്കുക ({count, number})", + "archive": "ആർക്കൈവ്", + "archive_action_prompt": "{count} എണ്ണം ആർക്കൈവിലേക്ക് ചേർത്തു", + "archive_or_unarchive_photo": "ഫോട്ടോ ആർക്കൈവ് ചെയ്യുക അല്ലെങ്കിൽ അൺആർക്കൈവ് ചെയ്യുക", + "archive_page_no_archived_assets": "ആർക്കൈവുചെയ്‌ത അസറ്റുകളൊന്നും കണ്ടെത്തിയില്ല", + "archive_page_title": "ആർക്കൈവ് ({count})", + "archive_size": "ആർക്കൈവ് വലുപ്പം", + "archive_size_description": "ഡൗൺലോഡുകൾക്കായി ആർക്കൈവ് വലുപ്പം കോൺഫിഗർ ചെയ്യുക (GiB-യിൽ)", + "archived": "ആർക്കൈവുചെയ്‌തു", + "archived_count": "{count, plural, other {ആർക്കൈവുചെയ്‌തവ #}}", + "are_these_the_same_person": "ഇവർ ഒരേ വ്യക്തിയാണോ?", + "are_you_sure_to_do_this": "ഇത് ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", + "asset_action_delete_err_read_only": "വായിക്കാൻ മാത്രമുള്ള അസറ്റ്(കൾ) ഇല്ലാതാക്കാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", + "asset_action_share_err_offline": "ഓഫ്‌ലൈൻ അസറ്റ്(കൾ) ലഭ്യമാക്കാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", + "asset_added_to_album": "ആൽബത്തിലേക്ക് ചേർത്തു", + "asset_adding_to_album": "ആൽബത്തിലേക്ക് ചേർത്തുകൊണ്ടിരിക്കുന്നു…", + "asset_description_updated": "അസറ്റിന്റെ വിവരണം അപ്ഡേറ്റ് ചെയ്തു", + "asset_filename_is_offline": "{filename} എന്ന അസറ്റ് ഓഫ്‌ലൈനാണ്", + "asset_has_unassigned_faces": "അസറ്റിൽ അസൈൻ ചെയ്യാത്ത മുഖങ്ങളുണ്ട്", + "asset_hashing": "ഹാഷിംഗ്…", + "asset_list_group_by_sub_title": "ഇതനുസരിച്ച് ഗ്രൂപ്പ് ചെയ്യുക", + "asset_list_layout_settings_dynamic_layout_title": "ഡൈനാമിക് ലേഔട്ട്", + "asset_list_layout_settings_group_automatically": "യാന്ത്രികം", + "asset_list_layout_settings_group_by": "അസറ്റുകളെ ഇതനുസരിച്ച് ഗ്രൂപ്പ് ചെയ്യുക", + "asset_list_layout_settings_group_by_month_day": "മാസം + ദിവസം", + "asset_list_layout_sub_title": "ലേഔട്ട്", + "asset_list_settings_subtitle": "ഫോട്ടോ ഗ്രിഡ് ലേഔട്ട് ക്രമീകരണങ്ങൾ", + "asset_list_settings_title": "ഫോട്ടോ ഗ്രിഡ്", + "asset_offline": "അസറ്റ് ഓഫ്‌ലൈൻ", + "asset_offline_description": "ഈ എക്സ്റ്റേണൽ അസറ്റ് ഇപ്പോൾ ഡിസ്കിൽ ലഭ്യമല്ല. സഹായത്തിനായി നിങ്ങളുടെ Immich അഡ്മിനിസ്ട്രേറ്ററുമായി ബന്ധപ്പെടുക.", + "asset_restored_successfully": "അസറ്റ് വിജയകരമായി പുനഃസ്ഥാപിച്ചു", + "asset_skipped": "ഒഴിവാക്കി", + "asset_skipped_in_trash": "ട്രാഷിൽ", + "asset_trashed": "അസറ്റ് ട്രാഷ് ചെയ്തു", + "asset_troubleshoot": "അസറ്റ് ട്രബിൾഷൂട്ട്", + "asset_uploaded": "അപ്‌ലോഡ് ചെയ്തു", + "asset_uploading": "അപ്‌ലോഡ് ചെയ്യുന്നു…", + "asset_viewer_settings_subtitle": "നിങ്ങളുടെ ഗാലറി വ്യൂവർ ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", + "asset_viewer_settings_title": "അസറ്റ് വ്യൂവർ", + "assets": "അസറ്റുകൾ", + "assets_added_count": "{count, plural, one {# അസറ്റ് ചേർത്തു} other {# അസറ്റുകൾ ചേർത്തു}}", + "assets_added_to_album_count": "ആൽബത്തിലേക്ക് {count, plural, one {# അസറ്റ് ചേർത്തു} other {# അസറ്റുകൾ ചേർത്തു}}", + "assets_added_to_albums_count": "{albumTotal, plural, one {# ആൽബത്തിലേക്ക്} other {# ആൽബങ്ങളിലേക്ക്}} {assetTotal, plural, one {# അസറ്റ് ചേർത്തു} other {# അസറ്റുകൾ ചേർത്തു}}", + "assets_cannot_be_added_to_album_count": "{count, plural, one {അസറ്റ്} other {അസറ്റുകൾ}} ആൽബത്തിലേക്ക് ചേർക്കാൻ കഴിയില്ല", + "assets_cannot_be_added_to_albums": "{count, plural, one {അസറ്റ്} other {അസറ്റുകൾ}} ഒരു ആൽബത്തിലേക്കും ചേർക്കാൻ കഴിയില്ല", + "assets_count": "{count, plural, one {# അസറ്റ്} other {# അസറ്റുകൾ}}", + "assets_deleted_permanently": "{count} അസറ്റ്(കൾ) ശാശ്വതമായി ഇല്ലാതാക്കി", + "assets_deleted_permanently_from_server": "{count} അസറ്റ്(കൾ) Immich സെർവറിൽ നിന്ന് ശാശ്വതമായി ഇല്ലാതാക്കി", + "assets_downloaded_failed": "{count, plural, one {# ഫയൽ ഡൗൺലോഡ് ചെയ്തു - {error} ഫയൽ പരാജയപ്പെട്ടു} other {# ഫയലുകൾ ഡൗൺലോഡ് ചെയ്തു - {error} ഫയലുകൾ പരാജയപ്പെട്ടു}}", + "assets_downloaded_successfully": "{count, plural, one {# ഫയൽ വിജയകരമായി ഡൗൺലോഡ് ചെയ്തു} other {# ഫയലുകൾ വിജയകരമായി ഡൗൺലോഡ് ചെയ്തു}}", + "assets_moved_to_trash_count": "{count, plural, one {# അസറ്റ് ട്രാഷിലേക്ക് മാറ്റി} other {# അസറ്റുകൾ ട്രാഷിലേക്ക് മാറ്റി}}", + "assets_permanently_deleted_count": "{count, plural, one {# അസറ്റ് ശാശ്വതമായി ഇല്ലാതാക്കി} other {# അസറ്റുകൾ ശാശ്വതമായി ഇല്ലാതാക്കി}}", + "assets_removed_count": "{count, plural, one {# അസറ്റ് നീക്കം ചെയ്തു} other {# അസറ്റുകൾ നീക്കം ചെയ്തു}}", + "assets_removed_permanently_from_device": "{count} അസറ്റ്(കൾ) നിങ്ങളുടെ ഉപകരണത്തിൽ നിന്ന് ശാശ്വതമായി നീക്കം ചെയ്തു", + "assets_restore_confirmation": "ട്രാഷ് ചെയ്ത എല്ലാ അസറ്റുകളും പുനഃസ്ഥാപിക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ? ഈ പ്രവർത്തനം പഴയപടിയാക്കാൻ കഴിയില്ല! ഓഫ്‌ലൈൻ അസറ്റുകളൊന്നും ഈ രീതിയിൽ പുനഃസ്ഥാപിക്കാൻ കഴിയില്ലെന്ന കാര്യം ശ്രദ്ധിക്കുക.", + "assets_restored_count": "{count, plural, one {# അസറ്റ് പുനഃസ്ഥാപിച്ചു} other {# അസറ്റുകൾ പുനഃസ്ഥാപിച്ചു}}", + "assets_restored_successfully": "{count} അസറ്റ്(കൾ) വിജയകരമായി പുനഃസ്ഥാപിച്ചു", + "assets_trashed": "{count} അസറ്റ്(കൾ) ട്രാഷ് ചെയ്തു", + "assets_trashed_count": "{count, plural, one {# അസറ്റ് ട്രാഷ് ചെയ്തു} other {# അസറ്റുകൾ ട്രാഷ് ചെയ്തു}}", + "assets_trashed_from_server": "{count} അസറ്റ്(കൾ) Immich സെർവറിൽ നിന്ന് ട്രാഷ് ചെയ്തു", + "assets_were_part_of_album_count": "{count, plural, one {അസറ്റ് ഇതിനകം} other {അസറ്റുകൾ ഇതിനകം}} ആൽബത്തിന്റെ ഭാഗമായിരുന്നു", + "assets_were_part_of_albums_count": "{count, plural, one {അസറ്റ് ഇതിനകം} other {അസറ്റുകൾ ഇതിനകം}} ആൽബങ്ങളുടെ ഭാഗമായിരുന്നു", + "authorized_devices": "അംഗീകൃത ഉപകരണങ്ങൾ", + "automatic_endpoint_switching_subtitle": "ലഭ്യമാകുമ്പോൾ നിശ്ചിത വൈ-ഫൈ വഴി പ്രാദേശികമായി കണക്റ്റുചെയ്യുക, മറ്റ് സ്ഥലങ്ങളിൽ ഇതര കണക്ഷനുകൾ ഉപയോഗിക്കുക", + "automatic_endpoint_switching_title": "യാന്ത്രിക URL സ്വിച്ചിംഗ്", + "autoplay_slideshow": "സ്ലൈഡ്‌ഷോ യാന്ത്രികമായി പ്ലേ ചെയ്യുക", + "back": "തിരികെ", + "back_close_deselect": "പുറകോട്ട്, അടയ്ക്കുക, അല്ലെങ്കിൽ തിരഞ്ഞെടുത്തത് മാറ്റുക", + "background_backup_running_error": "പശ്ചാത്തല ബാക്കപ്പ് ഇപ്പോൾ പ്രവർത്തിക്കുന്നു, മാനുവൽ ബാക്കപ്പ് ആരംഭിക്കാൻ കഴിയില്ല", + "background_location_permission": "പശ്ചാത്തല ലൊക്കേഷൻ അനുമതി", + "background_location_permission_content": "പശ്ചാത്തലത്തിൽ പ്രവർത്തിക്കുമ്പോൾ നെറ്റ്‌വർക്കുകൾ മാറുന്നതിന്, Immich-ന് എപ്പോഴും കൃത്യമായ ലൊക്കേഷൻ ആക്‌സസ് ഉണ്ടായിരിക്കണം, അതുവഴി ആപ്പിന് വൈ-ഫൈ നെറ്റ്‌വർക്കിന്റെ പേര് വായിക്കാൻ കഴിയും", + "background_options": "പശ്ചാത്തല ഓപ്ഷനുകൾ", + "backup": "ബാക്കപ്പ്", + "backup_album_selection_page_albums_device": "ഉപകരണത്തിലെ ആൽബങ്ങൾ ({count})", + "backup_album_selection_page_albums_tap": "ഉൾപ്പെടുത്താൻ ടാപ്പുചെയ്യുക, ഒഴിവാക്കാൻ ഡബിൾ ടാപ്പുചെയ്യുക", + "backup_album_selection_page_assets_scatter": "അസറ്റുകൾ ഒന്നിലധികം ആൽബങ്ങളിലായി വ്യാപിച്ചുകിടക്കാം. അതിനാൽ, ബാക്കപ്പ് പ്രക്രിയയിൽ ആൽബങ്ങൾ ഉൾപ്പെടുത്തുകയോ ഒഴിവാക്കുകയോ ചെയ്യാം.", + "backup_album_selection_page_select_albums": "ആൽബങ്ങൾ തിരഞ്ഞെടുക്കുക", + "backup_album_selection_page_selection_info": "തിരഞ്ഞെടുക്കൽ വിവരം", + "backup_album_selection_page_total_assets": "ആകെ സവിശേഷമായ അസറ്റുകൾ", + "backup_albums_sync": "ബാക്കപ്പ് ആൽബം സിൻക്രൊണൈസേഷൻ", + "backup_all": "എല്ലാം ബാക്കപ്പ് ചെയ്യുക", + "backup_background_service_backup_failed_message": "അസറ്റുകൾ ബാക്കപ്പ് ചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു. വീണ്ടും ശ്രമിക്കുന്നു…", + "backup_background_service_connection_failed_message": "സെർവറിലേക്ക് കണക്റ്റുചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു. വീണ്ടും ശ്രമിക്കുന്നു…", + "backup_background_service_current_upload_notification": "{filename} അപ്‌ലോഡ് ചെയ്യുന്നു", + "backup_background_service_default_notification": "പുതിയ അസറ്റുകൾക്കായി പരിശോധിക്കുന്നു…", + "backup_background_service_error_title": "ബാക്കപ്പ് പിശക്", + "backup_background_service_in_progress_notification": "നിങ്ങളുടെ അസറ്റുകൾ ബാക്കപ്പ് ചെയ്യുന്നു…", + "backup_background_service_upload_failure_notification": "{filename} അപ്‌ലോഡ് ചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", + "backup_controller_page_albums": "ബാക്കപ്പ് ആൽബങ്ങൾ", + "backup_controller_page_background_app_refresh_disabled_content": "പശ്ചാത്തല ബാക്കപ്പ് ഉപയോഗിക്കുന്നതിന്, ക്രമീകരണങ്ങൾ > പൊതുവായത് > പശ്ചാത്തല ആപ്പ് റിഫ്രഷ് എന്നതിൽ പശ്ചാത്തല ആപ്പ് റിഫ്രഷ് പ്രവർത്തനക്ഷമമാക്കുക.", + "backup_controller_page_background_app_refresh_disabled_title": "പശ്ചാത്തല ആപ്പ് റിഫ്രഷ് പ്രവർത്തനരഹിതമാക്കി", + "backup_controller_page_background_app_refresh_enable_button_text": "ക്രമീകരണങ്ങളിലേക്ക് പോകുക", + "backup_controller_page_background_battery_info_link": "എങ്ങനെയെന്ന് കാണിക്കുക", + "backup_controller_page_background_battery_info_message": "മികച്ച പശ്ചാത്തല ബാക്കപ്പ് അനുഭവത്തിനായി, Immich-ന്റെ പശ്ചാത്തല പ്രവർത്തനം നിയന്ത്രിക്കുന്ന ഏതെങ്കിലും ബാറ്ററി ഒപ്റ്റിമൈസേഷനുകൾ പ്രവർത്തനരഹിതമാക്കുക.\n\nഇത് ഉപകരണത്തെ ആശ്രയിച്ചിരിക്കുന്നതിനാൽ, നിങ്ങളുടെ ഉപകരണ നിർമ്മാതാവിനുള്ള ആവശ്യമായ വിവരങ്ങൾ ദയവായി കണ്ടെത്തുക.", + "backup_controller_page_background_battery_info_ok": "ശരി", + "backup_controller_page_background_battery_info_title": "ബാറ്ററി ഒപ്റ്റിമൈസേഷനുകൾ", + "backup_controller_page_background_charging": "ചാർജ് ചെയ്യുമ്പോൾ മാത്രം", + "backup_controller_page_background_configure_error": "പശ്ചാത്തല സേവനം കോൺഫിഗർ ചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", + "backup_controller_page_background_delay": "പുതിയ അസറ്റുകളുടെ ബാക്കപ്പ് വൈകിപ്പിക്കുക: {duration}", + "backup_controller_page_background_description": "ആപ്പ് തുറക്കാതെ തന്നെ പുതിയ അസറ്റുകൾ യാന്ത്രികമായി ബാക്കപ്പ് ചെയ്യാൻ പശ്ചാത്തല സേവനം ഓണാക്കുക", + "backup_controller_page_background_is_off": "യാന്ത്രിക പശ്ചാത്തല ബാക്കപ്പ് ഓഫാണ്", + "backup_controller_page_background_is_on": "യാന്ത്രിക പശ്ചാത്തല ബാക്കപ്പ് ഓണാണ്", + "backup_controller_page_background_turn_off": "പശ്ചാത്തല സേവനം ഓഫാക്കുക", + "backup_controller_page_background_turn_on": "പശ്ചാത്തല സേവനം ഓണാക്കുക", + "backup_controller_page_background_wifi": "വൈ-ഫൈയിൽ മാത്രം", + "backup_controller_page_backup": "ബാക്കപ്പ്", + "backup_controller_page_backup_selected": "തിരഞ്ഞെടുത്തവ: ", + "backup_controller_page_backup_sub": "ബാക്കപ്പ് ചെയ്ത ഫോട്ടോകളും വീഡിയോകളും", + "backup_controller_page_created": "സൃഷ്ടിച്ചത്: {date}", + "backup_controller_page_desc_backup": "ആപ്പ് തുറക്കുമ്പോൾ പുതിയ അസറ്റുകൾ യാന്ത്രികമായി സെർവറിലേക്ക് അപ്‌ലോഡ് ചെയ്യാൻ ഫോർഗ്രൗണ്ട് ബാക്കപ്പ് ഓണാക്കുക.", + "backup_controller_page_excluded": "ഒഴിവാക്കിയവ: ", + "backup_controller_page_failed": "പരാജയപ്പെട്ടു ({count})", + "backup_controller_page_filename": "ഫയലിന്റെ പേര്: {filename}[{size}]", + "backup_controller_page_id": "ഐഡി: {id}", + "backup_controller_page_info": "ബാക്കപ്പ് വിവരങ്ങൾ", + "backup_controller_page_none_selected": "ഒന്നും തിരഞ്ഞെടുത്തിട്ടില്ല", + "backup_controller_page_remainder": "ശേഷിക്കുന്നത്", + "backup_controller_page_remainder_sub": "തിരഞ്ഞെടുത്തവയിൽ നിന്ന് ബാക്കപ്പ് ചെയ്യാനുള്ള ശേഷിക്കുന്ന ഫോട്ടോകളും വീഡിയോകളും", + "backup_controller_page_server_storage": "സെർവർ സ്റ്റോറേജ്", + "backup_controller_page_start_backup": "ബാക്കപ്പ് ആരംഭിക്കുക", + "backup_controller_page_status_off": "യാന്ത്രിക ഫോർഗ്രൗണ്ട് ബാക്കപ്പ് ഓഫാണ്", + "backup_controller_page_status_on": "യാന്ത്രിക ഫോർഗ്രൗണ്ട് ബാക്കപ്പ് ഓണാണ്", + "backup_controller_page_storage_format": "{total}-ൽ {used} ഉപയോഗിച്ചു", + "backup_controller_page_to_backup": "ബാക്കപ്പ് ചെയ്യേണ്ട ആൽബങ്ങൾ", + "backup_controller_page_total_sub": "തിരഞ്ഞെടുത്ത ആൽബങ്ങളിൽ നിന്നുള്ള എല്ലാ സവിശേഷമായ ഫോട്ടോകളും വീഡിയോകളും", + "backup_controller_page_turn_off": "ഫോർഗ്രൗണ്ട് ബാക്കപ്പ് ഓഫാക്കുക", + "backup_controller_page_turn_on": "ഫോർഗ്രൗണ്ട് ബാക്കപ്പ് ഓണാക്കുക", + "backup_controller_page_uploading_file_info": "ഫയൽ വിവരങ്ങൾ അപ്‌ലോഡ് ചെയ്യുന്നു", + "backup_err_only_album": "ഒരേയൊരു ആൽബം നീക്കം ചെയ്യാൻ കഴിയില്ല", + "backup_error_sync_failed": "സിങ്ക് പരാജയപ്പെട്ടു. ബാക്കപ്പ് പ്രോസസ്സ് ചെയ്യാൻ കഴിയില്ല.", + "backup_info_card_assets": "അസറ്റുകൾ", + "backup_manual_cancelled": "റദ്ദാക്കി", + "backup_manual_in_progress": "അപ്‌ലോഡ് ഇതിനകം പുരോഗമിക്കുന്നു. കുറച്ച് സമയത്തിന് ശേഷം ശ്രമിക്കുക", + "backup_manual_success": "വിജയം", + "backup_manual_title": "അപ്‌ലോഡ് നില", + "backup_options": "ബാക്കപ്പ് ഓപ്ഷനുകൾ", + "backup_options_page_title": "ബാക്കപ്പ് ഓപ്ഷനുകൾ", + "backup_setting_subtitle": "പശ്ചാത്തല, മുൻനിര അപ്‌ലോഡ് ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", + "backup_settings_subtitle": "അപ്‌ലോഡ് ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", + "backward": "പിന്നോട്ട്", + "biometric_auth_enabled": "ബയോമെട്രിക് പ്രാമാണീകരണം പ്രവർത്തനക്ഷമമാക്കി", + "biometric_locked_out": "ബയോമെട്രിക് പ്രാമാണീകരണത്തിൽ നിന്ന് നിങ്ങളെ ലോക്ക് ഔട്ട് ചെയ്തിരിക്കുന്നു", + "biometric_no_options": "ബയോമെട്രിക് ഓപ്ഷനുകളൊന്നും ലഭ്യമല്ല", + "biometric_not_available": "ഈ ഉപകരണത്തിൽ ബയോമെട്രിക് പ്രാമാണീകരണം ലഭ്യമല്ല", + "birthdate_saved": "ജനനത്തീയതി വിജയകരമായി സേവ് ചെയ്തു", + "birthdate_set_description": "ഒരു ഫോട്ടോ എടുക്കുമ്പോഴുള്ള ഈ വ്യക്തിയുടെ പ്രായം കണക്കാക്കാൻ ജനനത്തീയതി ഉപയോഗിക്കുന്നു.", + "blurred_background": "മങ്ങിയ പശ്ചാത്തലം", + "bugs_and_feature_requests": "ബഗുകളും ഫീച്ചർ അഭ്യർത്ഥനകളും", + "build": "ബിൽഡ്", + "build_image": "ഇമേജ് നിർമ്മിക്കുക", + "bulk_delete_duplicates_confirmation": "നിങ്ങൾക്ക് {count, plural, one {# ഡ്യൂപ്ലിക്കേറ്റ് അസറ്റ്} other {# ഡ്യൂപ്ലിക്കേറ്റ് അസറ്റുകൾ}} ബൾക്കായി ഇല്ലാതാക്കണമെന്ന് ഉറപ്പാണോ? ഇത് ഓരോ ഗ്രൂപ്പിലെയും ഏറ്റവും വലിയ അസറ്റ് നിലനിർത്തുകയും മറ്റ് എല്ലാ ഡ്യൂപ്ലിക്കേറ്റുകളും ശാശ്വതമായി ഇല്ലാതാക്കുകയും ചെയ്യും. ഈ പ്രവർത്തനം പഴയപടിയാക്കാൻ കഴിയില്ല!", + "bulk_keep_duplicates_confirmation": "നിങ്ങൾക്ക് {count, plural, one {# ഡ്യൂപ്ലിക്കേറ്റ് അസറ്റ്} other {# ഡ്യൂപ്ലിക്കേറ്റ് അസറ്റുകൾ}} നിലനിർത്തണമെന്ന് ഉറപ്പാണോ? ഇത് ഒന്നും ഇല്ലാതാക്കാതെ എല്ലാ ഡ്യൂപ്ലിക്കേറ്റ് ഗ്രൂപ്പുകളെയും പരിഹരിക്കും.", + "bulk_trash_duplicates_confirmation": "നിങ്ങൾക്ക് {count, plural, one {# ഡ്യൂപ്ലിക്കേറ്റ് അസറ്റ്} other {# ഡ്യൂപ്ലിക്കേറ്റ് അസറ്റുകൾ}} ബൾക്കായി ട്രാഷ് ചെയ്യണമെന്ന് ഉറപ്പാണോ? ഇത് ഓരോ ഗ്രൂപ്പിലെയും ഏറ്റവും വലിയ അസറ്റ് നിലനിർത്തുകയും മറ്റ് എല്ലാ ഡ്യൂപ്ലിക്കേറ്റുകളും ട്രാഷ് ചെയ്യുകയും ചെയ്യും.", + "buy": "Immich വാങ്ങുക", + "cache_settings_clear_cache_button": "കാഷെ ക്ലിയർ ചെയ്യുക", + "cache_settings_clear_cache_button_title": "ആപ്പിന്റെ കാഷെ ക്ലിയർ ചെയ്യുന്നു. കാഷെ പുനർനിർമ്മിക്കുന്നതുവരെ ഇത് ആപ്പിന്റെ പ്രകടനത്തെ സാരമായി ബാധിക്കും.", + "cache_settings_duplicated_assets_clear_button": "ക്ലിയർ ചെയ്യുക", + "cache_settings_duplicated_assets_subtitle": "ആപ്പ് അവഗണിച്ച ഫോട്ടോകളും വീഡിയോകളും", + "cache_settings_duplicated_assets_title": "ഡ്യൂപ്ലിക്കേറ്റ് അസറ്റുകൾ ({count})", + "cache_settings_statistics_album": "ലൈബ്രറി തംബ്നെയിലുകൾ", + "cache_settings_statistics_full": "പൂർണ്ണ ചിത്രങ്ങൾ", + "cache_settings_statistics_shared": "പങ്കിട്ട ആൽബം തംബ്നെയിലുകൾ", + "cache_settings_statistics_thumbnail": "തംബ്നെയിലുകൾ", + "cache_settings_statistics_title": "കാഷെ ഉപയോഗം", + "cache_settings_subtitle": "Immich മൊബൈൽ ആപ്ലിക്കേഷന്റെ കാഷിംഗ് സ്വഭാവം നിയന്ത്രിക്കുക", + "cache_settings_tile_subtitle": "പ്രാദേശിക സ്റ്റോറേജ് സ്വഭാവം നിയന്ത്രിക്കുക", + "cache_settings_tile_title": "പ്രാദേശിക സ്റ്റോറേജ്", + "cache_settings_title": "കാഷിംഗ് ക്രമീകരണങ്ങൾ", + "camera": "ക്യാമറ", + "camera_brand": "ക്യാമറ ബ്രാൻഡ്", + "camera_model": "ക്യാമറ മോഡൽ", + "cancel": "റദ്ദാക്കുക", + "cancel_search": "തിരയൽ റദ്ദാക്കുക", + "canceled": "റദ്ദാക്കി", + "canceling": "റദ്ദാക്കുന്നു", + "cannot_merge_people": "ആളുകളെ ലയിപ്പിക്കാൻ കഴിയില്ല", + "cannot_undo_this_action": "നിങ്ങൾക്ക് ഈ പ്രവർത്തനം പഴയപടിയാക്കാൻ കഴിയില്ല!", + "cannot_update_the_description": "വിവരണം അപ്ഡേറ്റ് ചെയ്യാൻ കഴിയില്ല", + "cast": "കാസ്റ്റ്", + "cast_description": "ലഭ്യമായ കാസ്റ്റ് ഡെസ്റ്റിനേഷനുകൾ കോൺഫിഗർ ചെയ്യുക", + "change_date": "തീയതി മാറ്റുക", + "change_description": "വിവരണം മാറ്റുക", + "change_display_order": "പ്രദർശന ക്രമം മാറ്റുക", + "change_expiration_time": "കാലഹരണപ്പെടുന്ന സമയം മാറ്റുക", + "change_location": "സ്ഥാനം മാറ്റുക", + "change_name": "പേര് മാറ്റുക", + "change_name_successfully": "പേര് വിജയകരമായി മാറ്റി", + "change_password": "പാസ്‌വേഡ് മാറ്റുക", + "change_password_description": "ഇത് നിങ്ങൾ ആദ്യമായി സിസ്റ്റത്തിൽ സൈൻ ഇൻ ചെയ്യുന്നതുകൊണ്ടോ അല്ലെങ്കിൽ നിങ്ങളുടെ പാസ്‌വേഡ് മാറ്റാൻ ഒരു അഭ്യർത്ഥന നടത്തിയതുകൊണ്ടോ ആകാം. ദയവായി താഴെ പുതിയ പാസ്‌വേഡ് നൽകുക.", + "change_password_form_confirm_password": "പാസ്‌വേഡ് സ്ഥിരീകരിക്കുക", + "change_password_form_description": "നമസ്കാരം {name},\n\nഇത് നിങ്ങൾ ആദ്യമായി സിസ്റ്റത്തിൽ സൈൻ ഇൻ ചെയ്യുന്നതുകൊണ്ടോ അല്ലെങ്കിൽ നിങ്ങളുടെ പാസ്‌വേഡ് മാറ്റാൻ ഒരു അഭ്യർത്ഥന നടത്തിയതുകൊണ്ടോ ആകാം. ദയവായി താഴെ പുതിയ പാസ്‌വേഡ് നൽകുക.", + "change_password_form_new_password": "പുതിയ പാസ്‌വേഡ്", + "change_password_form_password_mismatch": "പാസ്‌വേഡുകൾ പൊരുത്തപ്പെടുന്നില്ല", + "change_password_form_reenter_new_password": "പുതിയ പാസ്‌വേഡ് വീണ്ടും നൽകുക", + "change_pin_code": "പിൻ കോഡ് മാറ്റുക", + "change_your_password": "നിങ്ങളുടെ പാസ്‌വേഡ് മാറ്റുക", + "changed_visibility_successfully": "ദൃശ്യത വിജയകരമായി മാറ്റി", + "charging": "ചാർജ്ജ് ചെയ്യുന്നു", + "charging_requirement_mobile_backup": "പശ്ചാത്തല ബാക്കപ്പിന് ഉപകരണം ചാർജ് ചെയ്യേണ്ടതുണ്ട്", + "check_corrupt_asset_backup": "കേടായ അസറ്റ് ബാക്കപ്പുകൾ പരിശോധിക്കുക", + "check_corrupt_asset_backup_button": "പരിശോധന നടത്തുക", + "check_corrupt_asset_backup_description": "എല്ലാ അസറ്റുകളും ബാക്കപ്പ് ചെയ്ത ശേഷം, വൈ-ഫൈയിൽ മാത്രം ഈ പരിശോധന നടത്തുക. നടപടിക്രമത്തിന് കുറച്ച് മിനിറ്റുകൾ എടുത്തേക്കാം.", + "check_logs": "ലോഗുകൾ പരിശോധിക്കുക", + "choose_matching_people_to_merge": "ലയിപ്പിക്കാൻ പൊരുത്തപ്പെടുന്ന ആളുകളെ തിരഞ്ഞെടുക്കുക", + "city": "നഗരം", + "clear": "ക്ലിയർ ചെയ്യുക", + "clear_all": "എല്ലാം ക്ലിയർ ചെയ്യുക", + "clear_all_recent_searches": "അടുത്തിടെ നടത്തിയ എല്ലാ തിരയലുകളും ക്ലിയർ ചെയ്യുക", + "clear_file_cache": "ഫയൽ കാഷെ ക്ലിയർ ചെയ്യുക", + "clear_message": "സന്ദേശം ക്ലിയർ ചെയ്യുക", + "clear_value": "മൂല്യം ക്ലിയർ ചെയ്യുക", + "client_cert_dialog_msg_confirm": "ശരി", + "client_cert_enter_password": "പാസ്‌വേഡ് നൽകുക", + "client_cert_import": "ഇമ്പോർട്ട് ചെയ്യുക", + "client_cert_import_success_msg": "ക്ലയിന്റ് സർട്ടിഫിക്കറ്റ് ഇമ്പോർട്ടുചെയ്‌തു", + "client_cert_invalid_msg": "അസാധുവായ സർട്ടിഫിക്കറ്റ് ഫയൽ അല്ലെങ്കിൽ തെറ്റായ പാസ്‌വേഡ്", + "client_cert_remove_msg": "ക്ലയിന്റ് സർട്ടിഫിക്കറ്റ് നീക്കംചെയ്‌തു", + "client_cert_subtitle": "PKCS12 (.p12, .pfx) ഫോർമാറ്റ് മാത്രം പിന്തുണയ്ക്കുന്നു. ലോഗിൻ ചെയ്യുന്നതിന് മുമ്പ് മാത്രമേ സർട്ടിഫിക്കറ്റ് ഇമ്പോർട്ട്/നീക്കംചെയ്യൽ ലഭ്യമാകൂ", + "client_cert_title": "SSL ക്ലയിന്റ് സർട്ടിഫിക്കറ്റ്", + "clockwise": "പ്രദക്ഷിണമായി", + "close": "അടയ്ക്കുക", + "collapse": "ചുരുക്കുക", + "collapse_all": "എല്ലാം ചുരുക്കുക", + "color": "നിറം", + "color_theme": "കളർ തീം", + "comment_deleted": "അഭിപ്രായം ഇല്ലാതാക്കി", + "comment_options": "അഭിപ്രായത്തിനുള്ള ഓപ്ഷനുകൾ", + "comments_and_likes": "അഭിപ്രായങ്ങളും ലൈക്കുകളും", + "comments_are_disabled": "അഭിപ്രായങ്ങൾ പ്രവർത്തനരഹിതമാക്കി", + "common_create_new_album": "പുതിയ ആൽബം ഉണ്ടാക്കുക", + "completed": "പൂർത്തിയായി", + "confirm": "സ്ഥിരീകരിക്കുക", + "confirm_admin_password": "അഡ്മിൻ പാസ്‌വേഡ് സ്ഥിരീകരിക്കുക", + "confirm_delete_face": "{name} എന്ന മുഖം അസറ്റിൽ നിന്ന് ഇല്ലാതാക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", + "confirm_delete_shared_link": "ഈ പങ്കിട്ട ലിങ്ക് ഇല്ലാതാക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", + "confirm_keep_this_delete_others": "ഈ അസറ്റ് ഒഴികെ സ്റ്റാക്കിലെ മറ്റെല്ലാ അസറ്റുകളും ഇല്ലാതാക്കപ്പെടും. തുടരണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", + "confirm_new_pin_code": "പുതിയ പിൻ കോഡ് സ്ഥിരീകരിക്കുക", + "confirm_password": "പാസ്‌വേഡ് സ്ഥിരീകരിക്കുക", + "confirm_tag_face": "ഈ മുഖം {name} എന്ന് ടാഗ് ചെയ്യണോ?", + "confirm_tag_face_unnamed": "ഈ മുഖം ടാഗ് ചെയ്യണോ?", + "connected_device": "ബന്ധിപ്പിച്ച ഉപകരണം", + "connected_to": "ഇതുമായി ബന്ധിപ്പിച്ചു", + "contain": "ഉൾക്കൊള്ളുക", + "context": "സന്ദർഭം", + "continue": "തുടരുക", + "control_bottom_app_bar_create_new_album": "പുതിയ ആൽബം ഉണ്ടാക്കുക", + "control_bottom_app_bar_delete_from_immich": "Immich-ൽ നിന്ന് ഇല്ലാതാക്കുക", + "control_bottom_app_bar_delete_from_local": "ഉപകരണത്തിൽ നിന്ന് ഇല്ലാതാക്കുക", + "control_bottom_app_bar_edit_location": "സ്ഥാനം എഡിറ്റുചെയ്യുക", + "control_bottom_app_bar_edit_time": "തീയതിയും സമയവും എഡിറ്റുചെയ്യുക", + "control_bottom_app_bar_share_link": "ലിങ്ക് പങ്കിടുക", + "control_bottom_app_bar_share_to": "ഇതിലേക്ക് പങ്കിടുക", + "control_bottom_app_bar_trash_from_immich": "ട്രാഷിലേക്ക് മാറ്റുക", + "copied_image_to_clipboard": "ചിത്രം ക്ലിപ്പ്ബോർഡിലേക്ക് പകർത്തി.", + "copied_to_clipboard": "ക്ലിപ്പ്ബോർഡിലേക്ക് പകർത്തി!", + "copy_error": "പകർത്തുന്നതിൽ പിശക്", + "copy_file_path": "ഫയൽ പാത്ത് പകർത്തുക", + "copy_image": "ചിത്രം പകർത്തുക", + "copy_link": "ലിങ്ക് പകർത്തുക", + "copy_link_to_clipboard": "ലിങ്ക് ക്ലിപ്പ്ബോർഡിലേക്ക് പകർത്തുക", + "copy_password": "പാസ്‌വേഡ് പകർത്തുക", + "copy_to_clipboard": "ക്ലിപ്പ്ബോർഡിലേക്ക് പകർത്തുക", + "country": "രാജ്യം", + "cover": "കവർ", + "covers": "കവറുകൾ", + "create": "സൃഷ്ടിക്കുക", + "create_album": "ആൽബം ഉണ്ടാക്കുക", + "create_album_page_untitled": "പേരില്ലാത്തത്", + "create_library": "ലൈബ്രറി ഉണ്ടാക്കുക", + "create_link": "ലിങ്ക് ഉണ്ടാക്കുക", + "create_link_to_share": "പങ്കിടാൻ ലിങ്ക് ഉണ്ടാക്കുക", + "create_link_to_share_description": "തിരഞ്ഞെടുത്ത ഫോട്ടോ(കൾ) കാണാൻ ലിങ്കുള്ള ആർക്കും അനുവാദം നൽകുക", + "create_new": "പുതിയത് ഉണ്ടാക്കുക", + "create_new_person": "പുതിയ വ്യക്തിയെ ഉണ്ടാക്കുക", + "create_new_person_hint": "തിരഞ്ഞെടുത്ത അസറ്റുകൾ ഒരു പുതിയ വ്യക്തിക്ക് നൽകുക", + "create_new_user": "പുതിയ ഉപയോക്താവിനെ ഉണ്ടാക്കുക", + "create_shared_album_page_share_add_assets": "അസറ്റുകൾ ചേർക്കുക", + "create_shared_album_page_share_select_photos": "ഫോട്ടോകൾ തിരഞ്ഞെടുക്കുക", + "create_shared_link": "പങ്കിട്ട ലിങ്ക് ഉണ്ടാക്കുക", + "create_tag": "ടാഗ് ഉണ്ടാക്കുക", + "create_tag_description": "ഒരു പുതിയ ടാഗ് ഉണ്ടാക്കുക. നെസ്റ്റഡ് ടാഗുകൾക്കായി, ഫോർവേഡ് സ്ലാഷുകൾ ഉൾപ്പെടെ ടാഗിന്റെ പൂർണ്ണ പാത നൽകുക.", + "create_user": "ഉപയോക്താവിനെ ഉണ്ടാക്കുക", + "created": "സൃഷ്ടിച്ചത്", + "created_at": "സൃഷ്ടിച്ചത്", + "creating_linked_albums": "ബന്ധിപ്പിച്ച ആൽബങ്ങൾ ഉണ്ടാക്കുന്നു...", + "crop": "ക്രോപ്പ് ചെയ്യുക", + "curated_object_page_title": "വസ്തുക്കൾ", + "current_device": "നിലവിലെ ഉപകരണം", + "current_pin_code": "നിലവിലെ പിൻ കോഡ്", + "current_server_address": "നിലവിലെ സെർവർ വിലാസം", + "custom_locale": "കസ്റ്റം ലൊക്കേൽ", + "custom_locale_description": "ഭാഷയെയും പ്രദേശത്തെയും അടിസ്ഥാനമാക്കി തീയതികളും അക്കങ്ങളും ഫോർമാറ്റ് ചെയ്യുക", + "custom_url": "കസ്റ്റം URL", + "daily_title_text_date": "E, MMM dd", + "daily_title_text_date_year": "E, MMM dd, yyyy", + "dark": "ഇരുണ്ടത്", + "dark_theme": "ഡാർക്ക് തീം ടോഗിൾ ചെയ്യുക", + "date_after": "ഇതിനു ശേഷമുള്ള തീയതി", + "date_and_time": "തീയതിയും സമയവും", + "date_before": "ഇതിനു മുമ്പുള്ള തീയതി", + "date_format": "E, LLL d, y • h:mm a", + "date_of_birth_saved": "ജനനത്തീയതി വിജയകരമായി സേവ് ചെയ്തു", + "date_range": "തീയതി പരിധി", + "day": "ദിവസം", + "days": "ദിവസങ്ങൾ", + "deduplicate_all": "എല്ലാ ഡ്യൂപ്ലിക്കേറ്റുകളും ഒഴിവാക്കുക", + "deduplication_criteria_1": "ചിത്രത്തിന്റെ വലുപ്പം (ബൈറ്റുകളിൽ)", + "deduplication_criteria_2": "EXIF ഡാറ്റയുടെ എണ്ണം", + "deduplication_info": "ഡ്യൂപ്ലിക്കേഷൻ ഒഴിവാക്കൽ വിവരം", + "deduplication_info_description": "അസറ്റുകൾ യാന്ത്രികമായി മുൻകൂട്ടി തിരഞ്ഞെടുക്കുന്നതിനും ഡ്യൂപ്ലിക്കേറ്റുകൾ ബൾക്കായി നീക്കം ചെയ്യുന്നതിനും, ഞങ്ങൾ ഇവ പരിഗണിക്കുന്നു:", + "default_locale": "ഡിഫോൾട്ട് ലൊക്കേൽ", + "default_locale_description": "നിങ്ങളുടെ ബ്രൗസർ ലൊക്കേലിനെ അടിസ്ഥാനമാക്കി തീയതികളും അക്കങ്ങളും ഫോർമാറ്റ് ചെയ്യുക", + "delete": "ഇല്ലാതാക്കുക", + "delete_action_confirmation_message": "ഈ അസറ്റ് ഇല്ലാതാക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ? ഈ പ്രവർത്തനം അസറ്റിനെ സെർവറിന്റെ ട്രാഷിലേക്ക് മാറ്റും, കൂടാതെ ഇത് പ്രാദേശികമായി ഇല്ലാതാക്കണോ എന്ന് ചോദിക്കുകയും ചെയ്യും", + "delete_action_prompt": "{count} എണ്ണം ഇല്ലാതാക്കി", + "delete_album": "ആൽബം ഇല്ലാതാക്കുക", + "delete_api_key_prompt": "ഈ API കീ ഇല്ലാതാക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", + "delete_dialog_alert": "ഈ ഇനങ്ങൾ Immich-ൽ നിന്നും നിങ്ങളുടെ ഉപകരണത്തിൽ നിന്നും ശാശ്വതമായി ഇല്ലാതാക്കപ്പെടും", + "delete_dialog_alert_local": "ഈ ഇനങ്ങൾ നിങ്ങളുടെ ഉപകരണത്തിൽ നിന്ന് ശാശ്വതമായി നീക്കം ചെയ്യപ്പെടും, പക്ഷേ Immich സെർവറിൽ തുടർന്നും ലഭ്യമാകും", + "delete_dialog_alert_local_non_backed_up": "ചില ഇനങ്ങൾ Immich-ലേക്ക് ബാക്കപ്പ് ചെയ്തിട്ടില്ല, അവ നിങ്ങളുടെ ഉപകരണത്തിൽ നിന്ന് ശാശ്വതമായി നീക്കം ചെയ്യപ്പെടും", + "delete_dialog_alert_remote": "ഈ ഇനങ്ങൾ Immich സെർവറിൽ നിന്ന് ശാശ്വതമായി ഇല്ലാതാക്കപ്പെടും", + "delete_dialog_ok_force": "എന്തായാലും ഇല്ലാതാക്കുക", + "delete_dialog_title": "ശാശ്വതമായി ഇല്ലാതാക്കുക", + "delete_duplicates_confirmation": "ഈ ഡ്യൂപ്ലിക്കേറ്റുകൾ ശാശ്വതമായി ഇല്ലാതാക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", + "delete_face": "മുഖം ഇല്ലാതാക്കുക", + "delete_key": "കീ ഇല്ലാതാക്കുക", + "delete_library": "ലൈബ്രറി ഇല്ലാതാക്കുക", + "delete_link": "ലിങ്ക് ഇല്ലാതാക്കുക", + "delete_local_action_prompt": "{count} എണ്ണം പ്രാദേശികമായി ഇല്ലാതാക്കി", + "delete_local_dialog_ok_backed_up_only": "ബാക്കപ്പ് ചെയ്തവ മാത്രം ഇല്ലാതാക്കുക", + "delete_local_dialog_ok_force": "എന്തായാലും ഇല്ലാതാക്കുക", + "delete_others": "മറ്റുള്ളവ ഇല്ലാതാക്കുക", + "delete_permanently": "ശാശ്വതമായി ഇല്ലാതാക്കുക", + "delete_permanently_action_prompt": "{count} എണ്ണം ശാശ്വതമായി ഇല്ലാതാക്കി", + "delete_shared_link": "പങ്കിട്ട ലിങ്ക് ഇല്ലാതാക്കുക", + "delete_shared_link_dialog_title": "പങ്കിട്ട ലിങ്ക് ഇല്ലാതാക്കുക", + "delete_tag": "ടാഗ് ഇല്ലാതാക്കുക", + "delete_tag_confirmation_prompt": "{tagName} എന്ന ടാഗ് ഇല്ലാതാക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", + "delete_user": "ഉപയോക്താവിനെ ഇല്ലാതാക്കുക", + "deleted_shared_link": "പങ്കിട്ട ലിങ്ക് ഇല്ലാതാക്കി", + "deletes_missing_assets": "ഡിസ്കിൽ നിന്ന് കാണാതായ അസറ്റുകൾ ഇല്ലാതാക്കുന്നു", + "description": "വിവരണം", + "description_input_hint_text": "വിവരണം ചേർക്കുക...", + "description_input_submit_error": "വിവരണം അപ്ഡേറ്റ് ചെയ്യുന്നതിൽ പിശക്, കൂടുതൽ വിവരങ്ങൾക്ക് ലോഗ് പരിശോധിക്കുക", + "deselect_all": "എല്ലാം തിരഞ്ഞെടുത്തത് മാറ്റുക", + "details": "വിശദാംശങ്ങൾ", + "direction": "ദിശ", + "disabled": "പ്രവർത്തനരഹിതം", + "disallow_edits": "എഡിറ്റുകൾ അനുവദിക്കരുത്", + "discord": "ഡിസ്കോർഡ്", + "discover": "കണ്ടെത്തുക", + "discovered_devices": "കണ്ടെത്തിയ ഉപകരണങ്ങൾ", + "dismiss_all_errors": "എല്ലാ പിശകുകളും തള്ളിക്കളയുക", + "dismiss_error": "പിശക് തള്ളിക്കളയുക", + "display_options": "പ്രദർശന ഓപ്ഷനുകൾ", + "display_order": "പ്രദർശന ക്രമം", + "display_original_photos": "യഥാർത്ഥ ഫോട്ടോകൾ പ്രദർശിപ്പിക്കുക", + "display_original_photos_setting_description": "യഥാർത്ഥ അസറ്റ് വെബ്-അനുയോജ്യമാകുമ്പോൾ, തംബ്നെയിലുകൾക്ക് പകരം യഥാർത്ഥ ഫോട്ടോ പ്രദർശിപ്പിക്കാൻ മുൻഗണന നൽകുക. ഇത് ഫോട്ടോ പ്രദർശന വേഗത കുറയ്ക്കാൻ ഇടയാക്കും.", + "do_not_show_again": "ഈ സന്ദേശം വീണ്ടും കാണിക്കരുത്", + "documentation": "ഡോക്യുമെന്റേഷൻ", + "done": "പൂർത്തിയായി", + "download": "ഡൗൺലോഡ്", + "download_action_prompt": "{count} അസറ്റുകൾ ഡൗൺലോഡ് ചെയ്യുന്നു", + "download_canceled": "ഡൗൺലോഡ് റദ്ദാക്കി", + "download_complete": "ഡൗൺലോഡ് പൂർത്തിയായി", + "download_enqueue": "ഡൗൺലോഡ് ക്യൂവിൽ ചേർത്തു", + "download_error": "ഡൗൺലോഡ് പിശക്", + "download_failed": "ഡൗൺലോഡ് പരാജയപ്പെട്ടു", + "download_finished": "ഡൗൺലോഡ് കഴിഞ്ഞു", + "download_include_embedded_motion_videos": "ഉൾച്ചേർത്ത വീഡിയോകൾ", + "download_include_embedded_motion_videos_description": "ചലിക്കുന്ന ഫോട്ടോകളിൽ ഉൾച്ചേർത്ത വീഡിയോകൾ ഒരു പ്രത്യേക ഫയലായി ഉൾപ്പെടുത്തുക", + "download_notfound": "ഡൗൺലോഡ് കണ്ടെത്തിയില്ല", + "download_paused": "ഡൗൺലോഡ് താൽക്കാലികമായി നിർത്തി", + "download_settings": "ഡൗൺലോഡ് ക്രമീകരണങ്ങൾ", + "download_settings_description": "അസറ്റ് ഡൗൺലോഡുമായി ബന്ധപ്പെട്ട ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", + "download_started": "ഡൗൺലോഡ് ആരംഭിച്ചു", + "download_sucess": "ഡൗൺലോഡ് വിജയിച്ചു", + "download_sucess_android": "മീഡിയ DCIM/Immich എന്നതിലേക്ക് ഡൗൺലോഡ് ചെയ്തിരിക്കുന്നു", + "download_waiting_to_retry": "വീണ്ടും ശ്രമിക്കാൻ കാത്തിരിക്കുന്നു", + "downloading": "ഡൗൺലോഡ് ചെയ്യുന്നു", + "downloading_asset_filename": "{filename} എന്ന അസറ്റ് ഡൗൺലോഡ് ചെയ്യുന്നു", + "downloading_media": "മീഡിയ ഡൗൺലോഡ് ചെയ്യുന്നു", + "drop_files_to_upload": "അപ്‌ലോഡ് ചെയ്യാൻ ഫയലുകൾ എവിടെയും ഡ്രോപ്പ് ചെയ്യുക", + "duplicates": "ഡ്യൂപ്ലിക്കേറ്റുകൾ", + "duplicates_description": "ഏതാണ് ഡ്യൂപ്ലിക്കേറ്റ് എന്ന് സൂചിപ്പിച്ച് ഓരോ ഗ്രൂപ്പും പരിഹരിക്കുക", + "duration": "ദൈർഘ്യം", + "edit": "തിരുത്തുക", + "edit_album": "ആൽബം എഡിറ്റുചെയ്യുക", + "edit_avatar": "അവതാർ എഡിറ്റുചെയ്യുക", + "edit_birthday": "ജന്മദിനം എഡിറ്റുചെയ്യുക", + "edit_date": "തീയതി എഡിറ്റുചെയ്യുക", + "edit_date_and_time": "തീയതിയും സമയവും എഡിറ്റുചെയ്യുക", + "edit_date_and_time_action_prompt": "{count} എണ്ണത്തിന്റെ തീയതിയും സമയവും എഡിറ്റുചെയ്തു", + "edit_date_and_time_by_offset": "ഓഫ്സെറ്റ് ഉപയോഗിച്ച് തീയതി മാറ്റുക", + "edit_date_and_time_by_offset_interval": "പുതിയ തീയതി പരിധി: {from}-{to}", + "edit_description": "വിവരണം എഡിറ്റുചെയ്യുക", + "edit_description_prompt": "ദയവായി ഒരു പുതിയ വിവരണം തിരഞ്ഞെടുക്കുക:", + "edit_exclusion_pattern": "ഒഴിവാക്കൽ പാറ്റേൺ എഡിറ്റുചെയ്യുക", + "edit_faces": "മുഖങ്ങൾ എഡിറ്റുചെയ്യുക", + "edit_import_path": "ഇമ്പോർട്ട് പാത്ത് എഡിറ്റുചെയ്യുക", + "edit_import_paths": "ഇമ്പോർട്ട് പാത്തുകൾ എഡിറ്റുചെയ്യുക", + "edit_key": "കീ എഡിറ്റുചെയ്യുക", + "edit_link": "ലിങ്ക് എഡിറ്റുചെയ്യുക", + "edit_location": "സ്ഥാനം എഡിറ്റുചെയ്യുക", + "edit_location_action_prompt": "{count} എണ്ണത്തിന്റെ സ്ഥാനം എഡിറ്റുചെയ്തു", + "edit_location_dialog_title": "സ്ഥാനം", + "edit_name": "പേര് എഡിറ്റുചെയ്യുക", + "edit_people": "ആളുകളെ എഡിറ്റുചെയ്യുക", + "edit_tag": "ടാഗ് എഡിറ്റുചെയ്യുക", + "edit_title": "ശീർഷകം എഡിറ്റുചെയ്യുക", + "edit_user": "ഉപയോക്താവിനെ എഡിറ്റുചെയ്യുക", + "editor": "എഡിറ്റർ", + "editor_close_without_save_prompt": "മാറ്റങ്ങൾ സേവ് ചെയ്യില്ല", + "editor_close_without_save_title": "എഡിറ്റർ അടയ്ക്കണോ?", + "editor_crop_tool_h2_aspect_ratios": "വീക്ഷണാനുപാതം", + "editor_crop_tool_h2_rotation": "റൊട്ടേഷൻ", + "email": "ഇമെയിൽ", + "email_notifications": "ഇമെയിൽ അറിയിപ്പുകൾ", + "empty_folder": "ഈ ഫോൾഡർ ശൂന്യമാണ്", + "empty_trash": "ട്രാഷ് ശൂന്യമാക്കുക", + "empty_trash_confirmation": "ട്രാഷ് ശൂന്യമാക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ? ഇത് ട്രാഷിലെ എല്ലാ അസറ്റുകളും Immich-ൽ നിന്ന് ശാശ്വതമായി നീക്കംചെയ്യും.\nനിങ്ങൾക്ക് ഈ പ്രവർത്തനം പഴയപടിയാക്കാൻ കഴിയില്ല!", + "enable": "പ്രവർത്തനക്ഷമമാക്കുക", + "enable_backup": "ബാക്കപ്പ് പ്രവർത്തനക്ഷമമാക്കുക", + "enable_biometric_auth_description": "ബയോമെട്രിക് പ്രാമാണീകരണം പ്രവർത്തനക്ഷമമാക്കാൻ നിങ്ങളുടെ പിൻ കോഡ് നൽകുക", + "enabled": "പ്രവർത്തനക്ഷമമാക്കി", + "end_date": "അവസാന തീയതി", + "enqueued": "ക്യൂവിൽ ചേർത്തു", + "enter_wifi_name": "വൈ-ഫൈയുടെ പേര് നൽകുക", + "enter_your_pin_code": "നിങ്ങളുടെ പിൻ കോഡ് നൽകുക", + "enter_your_pin_code_subtitle": "ലോക്ക് ചെയ്ത ഫോൾഡർ ആക്‌സസ് ചെയ്യാൻ നിങ്ങളുടെ പിൻ കോഡ് നൽകുക", + "error": "പിശക്", + "error_change_sort_album": "ആൽബത്തിന്റെ സോർട്ട് ഓർഡർ മാറ്റുന്നതിൽ പരാജയപ്പെട്ടു", + "error_delete_face": "അസറ്റിൽ നിന്ന് മുഖം ഇല്ലാതാക്കുന്നതിൽ പിശക്", + "error_getting_places": "സ്ഥലങ്ങൾ ലഭിക്കുന്നതിൽ പിശക്", + "error_loading_image": "ചിത്രം ലോഡുചെയ്യുന്നതിൽ പിശക്", + "error_loading_partners": "പങ്കാളികളെ ലോഡുചെയ്യുന്നതിൽ പിശക്: {error}", + "error_saving_image": "പിശക്: {error}", + "error_tag_face_bounding_box": "മുഖം ടാഗ് ചെയ്യുന്നതിൽ പിശക് - ബൗണ്ടിംഗ് ബോക്സ് കോർഡിനേറ്റുകൾ ലഭിക്കുന്നില്ല", + "error_title": "പിശക് - എന്തോ കുഴപ്പം സംഭവിച്ചു", + "errors": { + "cannot_navigate_next_asset": "അടുത്ത അസറ്റിലേക്ക് പോകാൻ കഴിയില്ല", + "cannot_navigate_previous_asset": "മുമ്പത്തെ അസറ്റിലേക്ക് പോകാൻ കഴിയില്ല", + "cant_apply_changes": "മാറ്റങ്ങൾ പ്രയോഗിക്കാൻ കഴിയില്ല", + "cant_change_activity": "പ്രവർത്തനം {enabled, select, true {പ്രവർത്തനരഹിതമാക്കാൻ} other {പ്രവർത്തനക്ഷമമാക്കാൻ}} കഴിയില്ല", + "cant_change_asset_favorite": "അസറ്റിന്റെ പ്രിയപ്പെട്ടവ മാറ്റാൻ കഴിയില്ല", + "cant_change_metadata_assets_count": "{count, plural, one {# അസറ്റിന്റെ} other {# അസറ്റുകളുടെ}} മെറ്റാഡാറ്റ മാറ്റാൻ കഴിയില്ല", + "cant_get_faces": "മുഖങ്ങൾ ലഭിക്കുന്നില്ല", + "cant_get_number_of_comments": "അഭിപ്രായങ്ങളുടെ എണ്ണം ലഭിക്കുന്നില്ല", + "cant_search_people": "ആളുകളെ തിരയാൻ കഴിയില്ല", + "cant_search_places": "സ്ഥലങ്ങൾ തിരയാൻ കഴിയില്ല", + "error_adding_assets_to_album": "ആൽബത്തിലേക്ക് അസറ്റുകൾ ചേർക്കുന്നതിൽ പിശക്", + "error_adding_users_to_album": "ആൽബത്തിലേക്ക് ഉപയോക്താക്കളെ ചേർക്കുന്നതിൽ പിശക്", + "error_deleting_shared_user": "പങ്കിട്ട ഉപയോക്താവിനെ ഇല്ലാതാക്കുന്നതിൽ പിശക്", + "error_downloading": "{filename} ഡൗൺലോഡ് ചെയ്യുന്നതിൽ പിശക്", + "error_hiding_buy_button": "വാങ്ങാനുള്ള ബട്ടൺ മറയ്ക്കുന്നതിൽ പിശക്", + "error_removing_assets_from_album": "ആൽബത്തിൽ നിന്ന് അസറ്റുകൾ നീക്കം ചെയ്യുന്നതിൽ പിശക്, കൂടുതൽ വിവരങ്ങൾക്ക് കൺസോൾ പരിശോധിക്കുക", + "error_selecting_all_assets": "എല്ലാ അസറ്റുകളും തിരഞ്ഞെടുക്കുന്നതിൽ പിശക്", + "exclusion_pattern_already_exists": "ഈ ഒഴിവാക്കൽ പാറ്റേൺ ഇതിനകം നിലവിലുണ്ട്.", + "failed_to_create_album": "ആൽബം ഉണ്ടാക്കുന്നതിൽ പരാജയപ്പെട്ടു", + "failed_to_create_shared_link": "പങ്കിട്ട ലിങ്ക് ഉണ്ടാക്കുന്നതിൽ പരാജയപ്പെട്ടു", + "failed_to_edit_shared_link": "പങ്കിട്ട ലിങ്ക് എഡിറ്റുചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", + "failed_to_get_people": "ആളുകളെ ലഭിക്കുന്നതിൽ പരാജയപ്പെട്ടു", + "failed_to_keep_this_delete_others": "ഈ അസറ്റ് നിലനിർത്തി മറ്റ് അസറ്റുകൾ ഇല്ലാതാക്കുന്നതിൽ പരാജയപ്പെട്ടു", + "failed_to_load_asset": "അസറ്റ് ലോഡുചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", + "failed_to_load_assets": "അസറ്റുകൾ ലോഡുചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", + "failed_to_load_notifications": "അറിയിപ്പുകൾ ലോഡുചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", + "failed_to_load_people": "ആളുകളെ ലോഡുചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", + "failed_to_remove_product_key": "പ്രൊഡക്റ്റ് കീ നീക്കം ചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", + "failed_to_reset_pin_code": "പിൻ കോഡ് റീസെറ്റ് ചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", + "failed_to_stack_assets": "അസറ്റുകൾ സ്റ്റാക്ക് ചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", + "failed_to_unstack_assets": "അസറ്റുകൾ അൺ-സ്റ്റാക്ക് ചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", + "failed_to_update_notification_status": "അറിയിപ്പിന്റെ നില അപ്ഡേറ്റ് ചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", + "import_path_already_exists": "ഈ ഇമ്പോർട്ട് പാത്ത് ഇതിനകം നിലവിലുണ്ട്.", + "incorrect_email_or_password": "തെറ്റായ ഇമെയിൽ അല്ലെങ്കിൽ പാസ്‌വേഡ്", + "paths_validation_failed": "{paths, plural, one {# പാത്ത്} other {# പാത്തുകൾ}} സാധൂകരണത്തിൽ പരാജയപ്പെട്ടു", + "profile_picture_transparent_pixels": "പ്രൊഫൈൽ ചിത്രങ്ങൾക്ക് സുതാര്യമായ പിക്സലുകൾ ഉണ്ടാകരുത്. ദയവായി സൂം ഇൻ ചെയ്യുക കൂടാതെ/അല്ലെങ്കിൽ ചിത്രം നീക്കുക.", + "quota_higher_than_disk_size": "ഡിസ്ക് വലുപ്പത്തേക്കാൾ ഉയർന്ന ക്വാട്ട നിങ്ങൾ സജ്ജമാക്കിയിരിക്കുന്നു", + "something_went_wrong": "എന്തോ കുഴപ്പം സംഭവിച്ചു", + "unable_to_add_album_users": "ആൽബത്തിലേക്ക് ഉപയോക്താക്കളെ ചേർക്കാൻ കഴിയില്ല", + "unable_to_add_assets_to_shared_link": "പങ്കിട്ട ലിങ്കിലേക്ക് അസറ്റുകൾ ചേർക്കാൻ കഴിയില്ല", + "unable_to_add_comment": "അഭിപ്രായം ചേർക്കാൻ കഴിയില്ല", + "unable_to_add_exclusion_pattern": "ഒഴിവാക്കൽ പാറ്റേൺ ചേർക്കാൻ കഴിയില്ല", + "unable_to_add_import_path": "ഇമ്പോർട്ട് പാത്ത് ചേർക്കാൻ കഴിയില്ല", + "unable_to_add_partners": "പങ്കാളികളെ ചേർക്കാൻ കഴിയില്ല", + "unable_to_add_remove_archive": "ആർക്കൈവിൽ {archived, select, true {നിന്ന് അസറ്റ് നീക്കംചെയ്യാൻ} other {ലേക്ക് അസറ്റ് ചേർക്കാൻ}} കഴിയില്ല", + "unable_to_add_remove_favorites": "പ്രിയപ്പെട്ടവയിലേക്ക് {favorite, select, true {അസറ്റ് ചേർക്കാൻ} other {നിന്ന് അസറ്റ് നീക്കംചെയ്യാൻ}} കഴിയില്ല", + "unable_to_archive_unarchive": "{archived, select, true {ആർക്കൈവ് ചെയ്യാൻ} other {അൺആർക്കൈവ് ചെയ്യാൻ}} കഴിയില്ല", + "unable_to_change_album_user_role": "ആൽബം ഉപയോക്താവിന്റെ റോൾ മാറ്റാൻ കഴിയില്ല", + "unable_to_change_date": "തീയതി മാറ്റാൻ കഴിയില്ല", + "unable_to_change_description": "വിവരണം മാറ്റാൻ കഴിയില്ല", + "unable_to_change_favorite": "അസറ്റിന്റെ പ്രിയപ്പെട്ടവ മാറ്റാൻ കഴിയില്ല", + "unable_to_change_location": "സ്ഥാനം മാറ്റാൻ കഴിയില്ല", + "unable_to_change_password": "പാസ്‌വേഡ് മാറ്റാൻ കഴിയില്ല", + "unable_to_change_visibility": "{count, plural, one {# വ്യക്തിയുടെ} other {# ആളുകളുടെ}} ദൃശ്യത മാറ്റാൻ കഴിയില്ല", + "unable_to_complete_oauth_login": "OAuth ലോഗിൻ പൂർത്തിയാക്കാൻ കഴിയില്ല", + "unable_to_connect": "ബന്ധിപ്പിക്കാൻ കഴിയില്ല", + "unable_to_copy_to_clipboard": "ക്ലിപ്പ്ബോർഡിലേക്ക് പകർത്താൻ കഴിയില്ല, നിങ്ങൾ https വഴിയാണ് പേജ് ആക്‌സസ് ചെയ്യുന്നതെന്ന് ഉറപ്പാക്കുക", + "unable_to_create_admin_account": "അഡ്മിൻ അക്കൗണ്ട് ഉണ്ടാക്കാൻ കഴിയില്ല", + "unable_to_create_api_key": "പുതിയ API കീ ഉണ്ടാക്കാൻ കഴിയില്ല", + "unable_to_create_library": "ലൈബ്രറി ഉണ്ടാക്കാൻ കഴിയില്ല", + "unable_to_create_user": "ഉപയോക്താവിനെ ഉണ്ടാക്കാൻ കഴിയില്ല", + "unable_to_delete_album": "ആൽബം ഇല്ലാതാക്കാൻ കഴിയില്ല", + "unable_to_delete_asset": "അസറ്റ് ഇല്ലാതാക്കാൻ കഴിയില്ല", + "unable_to_delete_assets": "അസറ്റുകൾ ഇല്ലാതാക്കുന്നതിൽ പിശക്", + "unable_to_delete_exclusion_pattern": "ഒഴിവാക്കൽ പാറ്റേൺ ഇല്ലാതാക്കാൻ കഴിയില്ല", + "unable_to_delete_import_path": "ഇമ്പോർട്ട് പാത്ത് ഇല്ലാതാക്കാൻ കഴിയില്ല", + "unable_to_delete_shared_link": "പങ്കിട്ട ലിങ്ക് ഇല്ലാതാക്കാൻ കഴിയില്ല", + "unable_to_delete_user": "ഉപയോക്താവിനെ ഇല്ലാതാക്കാൻ കഴിയില്ല", + "unable_to_download_files": "ഫയലുകൾ ഡൗൺലോഡ് ചെയ്യാൻ കഴിയില്ല", + "unable_to_edit_exclusion_pattern": "ഒഴിവാക്കൽ പാറ്റേൺ എഡിറ്റുചെയ്യാൻ കഴിയില്ല", + "unable_to_edit_import_path": "ഇമ്പോർട്ട് പാത്ത് എഡിറ്റുചെയ്യാൻ കഴിയില്ല", + "unable_to_empty_trash": "ട്രാഷ് ശൂന്യമാക്കാൻ കഴിയില്ല", + "unable_to_enter_fullscreen": "ഫുൾസ്ക്രീനിൽ പ്രവേശിക്കാൻ കഴിയില്ല", + "unable_to_exit_fullscreen": "ഫുൾസ്ക്രീനിൽ നിന്ന് പുറത്തുകടക്കാൻ കഴിയില്ല", + "unable_to_get_comments_number": "അഭിപ്രായങ്ങളുടെ എണ്ണം ലഭിക്കുന്നില്ല", + "unable_to_get_shared_link": "പങ്കിട്ട ലിങ്ക് ലഭിക്കുന്നതിൽ പരാജയപ്പെട്ടു", + "unable_to_hide_person": "വ്യക്തിയെ മറയ്ക്കാൻ കഴിയില്ല", + "unable_to_link_motion_video": "ചലിക്കുന്ന വീഡിയോ ലിങ്ക് ചെയ്യാൻ കഴിയില്ല", + "unable_to_link_oauth_account": "OAuth അക്കൗണ്ട് ലിങ്ക് ചെയ്യാൻ കഴിയില്ല", + "unable_to_log_out_all_devices": "എല്ലാ ഉപകരണങ്ങളിൽ നിന്നും ലോഗ് ഔട്ട് ചെയ്യാൻ കഴിയില്ല", + "unable_to_log_out_device": "ഉപകരണത്തിൽ നിന്ന് ലോഗ് ഔട്ട് ചെയ്യാൻ കഴിയില്ല", + "unable_to_login_with_oauth": "OAuth ഉപയോഗിച്ച് ലോഗിൻ ചെയ്യാൻ കഴിയില്ല", + "unable_to_play_video": "വീഡിയോ പ്ലേ ചെയ്യാൻ കഴിയില്ല", + "unable_to_reassign_assets_existing_person": "{name, select, null {നിലവിലുള്ള ഒരു വ്യക്തിക്ക്} other {{name}-ന്}} അസറ്റുകൾ വീണ്ടും നൽകാൻ കഴിയില്ല", + "unable_to_reassign_assets_new_person": "ഒരു പുതിയ വ്യക്തിക്ക് അസറ്റുകൾ വീണ്ടും നൽകാൻ കഴിയില്ല", + "unable_to_refresh_user": "ഉപയോക്താവിനെ പുതുക്കാൻ കഴിയില്ല", + "unable_to_remove_album_users": "ആൽബത്തിൽ നിന്ന് ഉപയോക്താക്കളെ നീക്കംചെയ്യാൻ കഴിയില്ല", + "unable_to_remove_api_key": "API കീ നീക്കംചെയ്യാൻ കഴിയില്ല", + "unable_to_remove_assets_from_shared_link": "പങ്കിട്ട ലിങ്കിൽ നിന്ന് അസറ്റുകൾ നീക്കംചെയ്യാൻ കഴിയില്ല", + "unable_to_remove_library": "ലൈബ്രറി നീക്കംചെയ്യാൻ കഴിയില്ല", + "unable_to_remove_partner": "പങ്കാളിയെ നീക്കംചെയ്യാൻ കഴിയില്ല", + "unable_to_remove_reaction": "പ്രതികരണം നീക്കംചെയ്യാൻ കഴിയില്ല", + "unable_to_reset_password": "പാസ്‌വേഡ് റീസെറ്റ് ചെയ്യാൻ കഴിയില്ല", + "unable_to_reset_pin_code": "പിൻ കോഡ് റീസെറ്റ് ചെയ്യാൻ കഴിയില്ല", + "unable_to_resolve_duplicate": "ഡ്യൂപ്ലിക്കേറ്റ് പരിഹരിക്കാൻ കഴിയില്ല", + "unable_to_restore_assets": "അസറ്റുകൾ പുനഃസ്ഥാപിക്കാൻ കഴിയില്ല", + "unable_to_restore_trash": "ട്രാഷ് പുനഃസ്ഥാപിക്കാൻ കഴിയില്ല", + "unable_to_restore_user": "ഉപയോക്താവിനെ പുനഃസ്ഥാപിക്കാൻ കഴിയില്ല", + "unable_to_save_album": "ആൽബം സേവ് ചെയ്യാൻ കഴിയില്ല", + "unable_to_save_api_key": "API കീ സേവ് ചെയ്യാൻ കഴിയില്ല", + "unable_to_save_date_of_birth": "ജനനത്തീയതി സേവ് ചെയ്യാൻ കഴിയില്ല", + "unable_to_save_name": "പേര് സേവ് ചെയ്യാൻ കഴിയില്ല", + "unable_to_save_profile": "പ്രൊഫൈൽ സേവ് ചെയ്യാൻ കഴിയില്ല", + "unable_to_save_settings": "ക്രമീകരണങ്ങൾ സേവ് ചെയ്യാൻ കഴിയില്ല", + "unable_to_scan_libraries": "ലൈബ്രറികൾ സ്കാൻ ചെയ്യാൻ കഴിയില്ല", + "unable_to_scan_library": "ലൈബ്രറി സ്കാൻ ചെയ്യാൻ കഴിയില്ല", + "unable_to_set_feature_photo": "ഫീച്ചർ ഫോട്ടോ സജ്ജമാക്കാൻ കഴിയില്ല", + "unable_to_set_profile_picture": "പ്രൊഫൈൽ ചിത്രം സജ്ജമാക്കാൻ കഴിയില്ല", + "unable_to_submit_job": "ജോലി സമർപ്പിക്കാൻ കഴിയില്ല", + "unable_to_trash_asset": "അസറ്റ് ട്രാഷ് ചെയ്യാൻ കഴിയില്ല", + "unable_to_unlink_account": "അക്കൗണ്ട് അൺലിങ്ക് ചെയ്യാൻ കഴിയില്ല", + "unable_to_unlink_motion_video": "ചലിക്കുന്ന വീഡിയോ അൺലിങ്ക് ചെയ്യാൻ കഴിയില്ല", + "unable_to_update_album_cover": "ആൽബം കവർ അപ്ഡേറ്റ് ചെയ്യാൻ കഴിയില്ല", + "unable_to_update_album_info": "ആൽബം വിവരങ്ങൾ അപ്ഡേറ്റ് ചെയ്യാൻ കഴിയില്ല", + "unable_to_update_library": "ലൈബ്രറി അപ്ഡേറ്റ് ചെയ്യാൻ കഴിയില്ല", + "unable_to_update_location": "സ്ഥാനം അപ്ഡേറ്റ് ചെയ്യാൻ കഴിയില്ല", + "unable_to_update_settings": "ക്രമീകരണങ്ങൾ അപ്ഡേറ്റ് ചെയ്യാൻ കഴിയില്ല", + "unable_to_update_timeline_display_status": "ടൈംലൈൻ പ്രദർശന നില അപ്ഡേറ്റ് ചെയ്യാൻ കഴിയില്ല", + "unable_to_update_user": "ഉപയോക്താവിനെ അപ്ഡേറ്റ് ചെയ്യാൻ കഴിയില്ല", + "unable_to_upload_file": "ഫയൽ അപ്‌ലോഡ് ചെയ്യാൻ കഴിയില്ല" + }, + "exif": "Exif", + "exif_bottom_sheet_description": "വിവരണം ചേർക്കുക...", + "exif_bottom_sheet_description_error": "വിവരണം അപ്ഡേറ്റ് ചെയ്യുന്നതിൽ പിശക്", + "exif_bottom_sheet_details": "വിശദാംശങ്ങൾ", + "exif_bottom_sheet_location": "സ്ഥാനം", + "exif_bottom_sheet_people": "ആളുകൾ", + "exif_bottom_sheet_person_add_person": "പേര് ചേർക്കുക", + "exit_slideshow": "സ്ലൈഡ്‌ഷോയിൽ നിന്ന് പുറത്തുകടക്കുക", + "expand_all": "എല്ലാം വികസിപ്പിക്കുക", + "experimental_settings_new_asset_list_subtitle": "പുരോഗതിയിലാണ്", + "experimental_settings_new_asset_list_title": "പരീക്ഷണാടിസ്ഥാനത്തിലുള്ള ഫോട്ടോ ഗ്രിഡ് പ്രവർത്തനക്ഷമമാക്കുക", + "experimental_settings_subtitle": "നിങ്ങളുടെ സ്വന്തം ഉത്തരവാദിത്വത്തിൽ ഉപയോഗിക്കുക!", + "experimental_settings_title": "പരീക്ഷണാടിസ്ഥാനത്തിലുള്ളത്", + "expire_after": "ഇതിന് ശേഷം കാലഹരണപ്പെടും", + "expired": "കാലഹരണപ്പെട്ടു", + "expires_date": "{date}-ന് കാലഹരണപ്പെടും", + "explore": "പര്യവേക്ഷണം ചെയ്യുക", + "explorer": "എക്സ്പ്ലോറർ", + "export": "കയറ്റുമതി ചെയ്യുക", + "export_as_json": "JSON ആയി കയറ്റുമതി ചെയ്യുക", + "export_database": "ഡാറ്റാബേസ് കയറ്റുമതി ചെയ്യുക", + "export_database_description": "SQLite ഡാറ്റാബേസ് കയറ്റുമതി ചെയ്യുക", + "extension": "എക്സ്റ്റൻഷൻ", + "external": "ബാഹ്യം", + "external_libraries": "ബാഹ്യ ലൈബ്രറികൾ", + "external_network": "ബാഹ്യ നെറ്റ്‌വർക്ക്", + "external_network_sheet_info": "തിരഞ്ഞെടുത്ത വൈ-ഫൈ നെറ്റ്‌വർക്കിൽ അല്ലാത്തപ്പോൾ, താഴെ നൽകിയിട്ടുള്ള URL-കളിൽ ആദ്യം ലഭ്യമാകുന്ന ഒന്നിലൂടെ, മുകളിൽ നിന്ന് താഴേക്കുള്ള ക്രമത്തിൽ, ആപ്പ് സെർവറുമായി ബന്ധിപ്പിക്കും", + "face_unassigned": "അസൈൻ ചെയ്തിട്ടില്ല", + "failed": "പരാജയപ്പെട്ടു", + "failed_to_authenticate": "പ്രാമാണീകരിക്കുന്നതിൽ പരാജയപ്പെട്ടു", + "failed_to_load_assets": "അസറ്റുകൾ ലോഡുചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", + "failed_to_load_folder": "ഫോൾഡർ ലോഡുചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", + "favorite": "പ്രിയം", + "favorite_action_prompt": "{count} എണ്ണം പ്രിയപ്പെട്ടവയിലേക്ക് ചേർത്തു", + "favorite_or_unfavorite_photo": "ഫോട്ടോ പ്രിയപ്പെട്ടതാക്കുക അല്ലെങ്കിൽ മാറ്റുക", + "favorites": "പ്രിയങ്ങൾ", + "favorites_page_no_favorites": "പ്രിയപ്പെട്ട അസറ്റുകളൊന്നും കണ്ടെത്തിയില്ല", + "feature_photo_updated": "ഫീച്ചർ ഫോട്ടോ അപ്ഡേറ്റ് ചെയ്തു", + "features": "ഫീച്ചറുകൾ", + "features_in_development": "വികസിപ്പിച്ചുകൊണ്ടിരിക്കുന്ന ഫീച്ചറുകൾ", + "features_setting_description": "ആപ്പ് ഫീച്ചറുകൾ കൈകാര്യം ചെയ്യുക", + "file_name": "ഫയലിന്റെ പേര്", + "file_name_or_extension": "ഫയലിന്റെ പേര് അല്ലെങ്കിൽ എക്സ്റ്റൻഷൻ", + "filename": "ഫയൽനാമം", + "filetype": "ഫയൽ തരം", + "filter": "ഫിൽട്ടർ ചെയ്യുക", + "filter_people": "ആളുകളെ ഫിൽട്ടർ ചെയ്യുക", + "filter_places": "സ്ഥലങ്ങൾ ഫിൽട്ടർ ചെയ്യുക", + "find_them_fast": "തിരയലിലൂടെ പേര് ഉപയോഗിച്ച് അവരെ വേഗത്തിൽ കണ്ടെത്തുക", + "first": "ആദ്യം", + "fix_incorrect_match": "തെറ്റായ പൊരുത്തം ശരിയാക്കുക", + "folder": "ഫോൾഡർ", + "folder_not_found": "ഫോൾഡർ കണ്ടെത്തിയില്ല", + "folders": "ഫോൾഡറുകൾ", + "folders_feature_description": "ഫയൽ സിസ്റ്റത്തിലെ ഫോട്ടോകൾക്കും വീഡിയോകൾക്കുമായി ഫോൾഡർ കാഴ്‌ച ബ്രൗസുചെയ്യുന്നു", + "forgot_pin_code_question": "നിങ്ങളുടെ പിൻ മറന്നോ?", + "forward": "മുന്നോട്ട്", + "gcast_enabled": "ഗൂഗിൾ കാസ്റ്റ്", + "gcast_enabled_description": "പ്രവർത്തിക്കുന്നതിനായി ഈ ഫീച്ചർ ഗൂഗിളിൽ നിന്ന് ബാഹ്യ ഉറവിടങ്ങൾ ലോഡുചെയ്യുന്നു.", + "general": "പൊതുവായത്", + "geolocation_instruction_location": "ലൊക്കേഷൻ ഉപയോഗിക്കുന്നതിന് GPS കോർഡിനേറ്റുകളുള്ള ഒരു അസറ്റിൽ ക്ലിക്കുചെയ്യുക, അല്ലെങ്കിൽ മാപ്പിൽ നിന്ന് നേരിട്ട് ഒരു സ്ഥലം തിരഞ്ഞെടുക്കുക", + "get_help": "സഹായം നേടുക", + "get_wifiname_error": "വൈ-ഫൈയുടെ പേര് ലഭിച്ചില്ല. നിങ്ങൾ ആവശ്യമായ അനുമതികൾ നൽകിയിട്ടുണ്ടെന്നും ഒരു വൈ-ഫൈ നെറ്റ്‌വർക്കിലേക്ക് കണക്റ്റുചെയ്‌തിട്ടുണ്ടെന്നും ഉറപ്പാക്കുക", + "getting_started": "ആരംഭിക്കുന്നു", + "go_back": "പിന്നോട്ട് പോകുക", + "go_to_folder": "ഫോൾഡറിലേക്ക് പോകുക", + "go_to_search": "തിരയലിലേക്ക് പോകുക", + "gps": "ജിപിഎസ്", + "gps_missing": "GPS ഇല്ല", + "grant_permission": "അനുമതി നൽകുക", + "group_albums_by": "ആൽബങ്ങളെ ഇതനുസരിച്ച് ഗ്രൂപ്പ് ചെയ്യുക...", + "group_country": "രാജ്യം അനുസരിച്ച് ഗ്രൂപ്പ് ചെയ്യുക", + "group_no": "ഗ്രൂപ്പിംഗ് ഇല്ല", + "group_owner": "ഉടമ അനുസരിച്ച് ഗ്രൂപ്പ് ചെയ്യുക", + "group_places_by": "സ്ഥലങ്ങളെ ഇതനുസരിച്ച് ഗ്രൂപ്പ് ചെയ്യുക...", + "group_year": "വർഷം അനുസരിച്ച് ഗ്രൂപ്പ് ചെയ്യുക", + "haptic_feedback_switch": "ഹാപ്റ്റിക് ഫീഡ്‌ബാക്ക് പ്രവർത്തനക്ഷമമാക്കുക", + "haptic_feedback_title": "ഹാപ്റ്റിക് ഫീഡ്‌ബാക്ക്", + "has_quota": "ക്വാട്ടയുണ്ട്", + "hash_asset": "അസറ്റ് ഹാഷ് ചെയ്യുക", + "hashed_assets": "ഹാഷ് ചെയ്ത അസറ്റുകൾ", + "hashing": "ഹാഷിംഗ്", + "header_settings_add_header_tip": "ഹെഡർ ചേർക്കുക", + "header_settings_field_validator_msg": "മൂല്യം ശൂന്യമാകരുത്", + "header_settings_header_name_input": "ഹെഡറിന്റെ പേര്", + "header_settings_header_value_input": "ഹെഡറിന്റെ മൂല്യം", + "headers_settings_tile_title": "കസ്റ്റം പ്രോക്സി ഹെഡറുകൾ", + "hi_user": "നമസ്കാരം {name} ({email})", + "hide_all_people": "എല്ലാ ആളുകളെയും മറയ്ക്കുക", + "hide_gallery": "ഗാലറി മറയ്ക്കുക", + "hide_named_person": "{name} എന്ന വ്യക്തിയെ മറയ്ക്കുക", + "hide_password": "പാസ്‌വേഡ് മറയ്ക്കുക", + "hide_person": "വ്യക്തിയെ മറയ്ക്കുക", + "hide_unnamed_people": "പേരില്ലാത്ത ആളുകളെ മറയ്ക്കുക", + "home_page_add_to_album_conflicts": "{album} എന്ന ആൽബത്തിലേക്ക് {added} അസറ്റുകൾ ചേർത്തു. {failed} അസറ്റുകൾ ഇതിനകം ആൽബത്തിലുണ്ട്.", + "home_page_add_to_album_err_local": "പ്രാദേശിക അസറ്റുകൾ ഇപ്പോൾ ആൽബങ്ങളിലേക്ക് ചേർക്കാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", + "home_page_add_to_album_success": "{album} എന്ന ആൽബത്തിലേക്ക് {added} അസറ്റുകൾ ചേർത്തു.", + "home_page_album_err_partner": "പങ്കാളിയുടെ അസറ്റുകൾ ഇപ്പോൾ ഒരു ആൽബത്തിലേക്ക് ചേർക്കാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", + "home_page_archive_err_local": "പ്രാദേശിക അസറ്റുകൾ ഇപ്പോൾ ആർക്കൈവ് ചെയ്യാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", + "home_page_archive_err_partner": "പങ്കാളിയുടെ അസറ്റുകൾ ആർക്കൈവ് ചെയ്യാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", + "home_page_building_timeline": "ടൈംലൈൻ നിർമ്മിക്കുന്നു", + "home_page_delete_err_partner": "പങ്കാളിയുടെ അസറ്റുകൾ ഇല്ലാതാക്കാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", + "home_page_delete_remote_err_local": "റിമോട്ട് ഇല്ലാതാക്കൽ തിരഞ്ഞെടുപ്പിൽ പ്രാദേശിക അസറ്റുകൾ ഉണ്ട്, ഒഴിവാക്കുന്നു", + "home_page_favorite_err_local": "പ്രാദേശിക അസറ്റുകൾ ഇപ്പോൾ പ്രിയപ്പെട്ടതാക്കാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", + "home_page_favorite_err_partner": "പങ്കാളിയുടെ അസറ്റുകൾ ഇപ്പോൾ പ്രിയപ്പെട്ടതാക്കാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", + "home_page_first_time_notice": "നിങ്ങൾ ആദ്യമായിട്ടാണ് ഈ ആപ്പ് ഉപയോഗിക്കുന്നതെങ്കിൽ, ഒരു ബാക്കപ്പ് ആൽബം തിരഞ്ഞെടുക്കുന്നത് ഉറപ്പാക്കുക, അതുവഴി ടൈംലൈനിൽ ഫോട്ടോകളും വീഡിയോകളും ചേർക്കാൻ കഴിയും", + "home_page_locked_error_local": "പ്രാദേശിക അസറ്റുകൾ ലോക്ക് ചെയ്ത ഫോൾഡറിലേക്ക് മാറ്റാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", + "home_page_locked_error_partner": "പങ്കാളിയുടെ അസറ്റുകൾ ലോക്ക് ചെയ്ത ഫോൾഡറിലേക്ക് മാറ്റാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", + "home_page_share_err_local": "പ്രാദേശിക അസറ്റുകൾ ലിങ്ക് വഴി പങ്കിടാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", + "home_page_upload_err_limit": "ഒരു സമയം പരമാവധി 30 അസറ്റുകൾ മാത്രമേ അപ്‌ലോഡ് ചെയ്യാൻ കഴിയൂ, ഒഴിവാക്കുന്നു", + "host": "ഹോസ്റ്റ്", + "hour": "മണിക്കൂർ", + "hours": "മണിക്കൂറുകൾ", + "id": "ഐഡി", + "idle": "നിഷ്‌ക്രിയം", + "ignore_icloud_photos": "iCloud ഫോട്ടോകൾ അവഗണിക്കുക", + "ignore_icloud_photos_description": "iCloud-ൽ സംഭരിച്ചിരിക്കുന്ന ഫോട്ടോകൾ Immich സെർവറിലേക്ക് അപ്‌ലോഡ് ചെയ്യില്ല", + "image": "ചിത്രം", + "image_alt_text_date": "{date}-ന് എടുത്ത {isVideo, select, true {വീഡിയോ} other {ചിത്രം}}", + "image_alt_text_date_1_person": "{date}-ന് {person1}-നൊപ്പം എടുത്ത {isVideo, select, true {വീഡിയോ} other {ചിത്രം}}", + "image_alt_text_date_2_people": "{date}-ന് {person1}, {person2} എന്നിവർക്കൊപ്പം എടുത്ത {isVideo, select, true {വീഡിയോ} other {ചിത്രം}}", + "image_alt_text_date_3_people": "{date}-ന് {person1}, {person2}, {person3} എന്നിവർക്കൊപ്പം എടുത്ത {isVideo, select, true {വീഡിയോ} other {ചിത്രം}}", + "image_alt_text_date_4_or_more_people": "{date}-ന് {person1}, {person2}, കൂടാതെ മറ്റ് {additionalCount, number} പേർക്കുമൊപ്പം എടുത്ത {isVideo, select, true {വീഡിയോ} other {ചിത്രം}}", + "image_alt_text_date_place": "{date}-ന് {city}, {country} എന്നിവിടങ്ങളിൽ വെച്ച് എടുത്ത {isVideo, select, true {വീഡിയോ} other {ചിത്രം}}", + "image_alt_text_date_place_1_person": "{date}-ന് {city}, {country} എന്നിവിടങ്ങളിൽ വെച്ച് {person1}-നൊപ്പം എടുത്ത {isVideo, select, true {വീഡിയോ} other {ചിത്രം}}", + "image_alt_text_date_place_2_people": "{date}-ന് {city}, {country} എന്നിവിടങ്ങളിൽ വെച്ച് {person1}, {person2} എന്നിവർക്കൊപ്പം എടുത്ത {isVideo, select, true {വീഡിയോ} other {ചിത്രം}}", + "image_alt_text_date_place_3_people": "{date}-ന് {city}, {country} എന്നിവിടങ്ങളിൽ വെച്ച് {person1}, {person2}, {person3} എന്നിവർക്കൊപ്പം എടുത്ത {isVideo, select, true {വീഡിയോ} other {ചിത്രം}}", + "image_alt_text_date_place_4_or_more_people": "{date}-ന് {city}, {country} എന്നിവിടങ്ങളിൽ വെച്ച് {person1}, {person2}, കൂടാതെ മറ്റ് {additionalCount, number} പേർക്കുമൊപ്പം എടുത്ത {isVideo, select, true {വീഡിയോ} other {ചിത്രം}}", + "image_saved_successfully": "ചിത്രം സേവ് ചെയ്തു", + "image_viewer_page_state_provider_download_started": "ഡൗൺലോഡ് ആരംഭിച്ചു", + "image_viewer_page_state_provider_download_success": "ഡൗൺലോഡ് വിജയിച്ചു", + "image_viewer_page_state_provider_share_error": "പങ്കിടുന്നതിൽ പിശക്", + "immich_logo": "Immich ലോഗോ", + "immich_web_interface": "Immich വെബ് ഇന്റർഫേസ്", + "import_from_json": "JSON-ൽ നിന്ന് ഇമ്പോർട്ടുചെയ്യുക", + "import_path": "ഇമ്പോർട്ട് പാത്ത്", + "in_albums": "{count, plural, one {# ആൽബത്തിൽ} other {# ആൽബങ്ങളിൽ}}", + "in_archive": "ആർക്കൈവിൽ", + "include_archived": "ആർക്കൈവുചെയ്‌തവ ഉൾപ്പെടുത്തുക", + "include_shared_albums": "പങ്കിട്ട ആൽബങ്ങൾ ഉൾപ്പെടുത്തുക", + "include_shared_partner_assets": "പങ്കിട്ട പങ്കാളിയുടെ അസറ്റുകൾ ഉൾപ്പെടുത്തുക", + "individual_share": "വ്യക്തിഗത പങ്കിടൽ", + "individual_shares": "വ്യക്തിഗത പങ്കിടലുകൾ", + "info": "വിവരങ്ങൾ", + "interval": { + "day_at_onepm": "എല്ലാ ദിവസവും ഉച്ചയ്ക്ക് 1 മണിക്ക്", + "hours": "{hours, plural, one {ഓരോ മണിക്കൂറിലും} other {ഓരോ {hours, number} മണിക്കൂറിലും}}", + "night_at_midnight": "എല്ലാ രാത്രിയും അർദ്ധരാത്രിക്ക്", + "night_at_twoam": "എല്ലാ രാത്രിയും പുലർച്ചെ 2 മണിക്ക്" + }, + "invalid_date": "അസാധുവായ തീയതി", + "invalid_date_format": "അസാധുവായ തീയതി ഫോർമാറ്റ്", + "invite_people": "ആളുകളെ ക്ഷണിക്കുക", + "invite_to_album": "ആൽബത്തിലേക്ക് ക്ഷണിക്കുക", + "ios_debug_info_fetch_ran_at": "ഫെച്ച് പ്രവർത്തിച്ചത് {dateTime}", + "ios_debug_info_last_sync_at": "അവസാന സിങ്ക് {dateTime}", + "ios_debug_info_no_processes_queued": "പശ്ചാത്തല പ്രോസസ്സുകളൊന്നും ക്യൂവിൽ ഇല്ല", + "ios_debug_info_no_sync_yet": "പശ്ചാത്തല സിങ്ക് ജോലി ഇതുവരെ പ്രവർത്തിച്ചിട്ടില്ല", + "ios_debug_info_processes_queued": "{count, plural, one {{count} പശ്ചാത്തല പ്രോസസ്സ് ക്യൂവിലുണ്ട്} other {{count} പശ്ചാത്തല പ്രോസസ്സുകൾ ക്യൂവിലുണ്ട്}}", + "ios_debug_info_processing_ran_at": "പ്രോസസ്സിംഗ് പ്രവർത്തിച്ചത് {dateTime}", + "items_count": "{count, plural, one {# ഇനം} other {# ഇനങ്ങൾ}}", + "jobs": "ജോലികൾ", + "keep": "സൂക്ഷിക്കുക", + "keep_all": "എല്ലാം സൂക്ഷിക്കുക", + "keep_this_delete_others": "ഇത് സൂക്ഷിച്ച് മറ്റുള്ളവ ഇല്ലാതാക്കുക", + "kept_this_deleted_others": "ഈ അസറ്റ് സൂക്ഷിക്കുകയും {count, plural, one {# അസറ്റ്} other {# അസറ്റുകൾ}} ഇല്ലാതാക്കുകയും ചെയ്തു", + "keyboard_shortcuts": "കീബോർഡ് കുറുക്കുവഴികൾ", + "language": "ഭാഷ", + "language_no_results_subtitle": "നിങ്ങളുടെ തിരയൽ പദം ക്രമീകരിച്ച് ശ്രമിക്കുക", + "language_no_results_title": "ഭാഷകളൊന്നും കണ്ടെത്തിയില്ല", + "language_search_hint": "ഭാഷകൾക്കായി തിരയുക...", + "language_setting_description": "നിങ്ങൾക്കിഷ്ടപ്പെട്ട ഭാഷ തിരഞ്ഞെടുക്കുക", + "large_files": "വലിയ ഫയലുകൾ", + "last": "അവസാനത്തേത്", + "last_seen": "അവസാനം കണ്ടത്", + "latest_version": "ഏറ്റവും പുതിയ പതിപ്പ്", + "latitude": "അക്ഷാംശം", + "leave": "വിടുക", + "leave_album": "ആൽബം വിടുക", + "lens_model": "ലെൻസ് മോഡൽ", + "let_others_respond": "മറ്റുള്ളവരെ പ്രതികരിക്കാൻ അനുവദിക്കുക", + "level": "തലം", + "library": "ലൈബ്രറി", + "library_options": "ലൈബ്രറി ഓപ്ഷനുകൾ", + "library_page_device_albums": "ഉപകരണത്തിലെ ആൽബങ്ങൾ", + "library_page_new_album": "പുതിയ ആൽബം", + "library_page_sort_asset_count": "അസറ്റുകളുടെ എണ്ണം", + "library_page_sort_created": "സൃഷ്ടിച്ച തീയതി", + "library_page_sort_last_modified": "അവസാനം മാറ്റം വരുത്തിയത്", + "library_page_sort_title": "ആൽബത്തിന്റെ ശീർഷകം", + "licenses": "ലൈസൻസുകൾ", + "light": "ലൈറ്റ്", + "like": "ഇഷ്ടപ്പെട്ടു", + "like_deleted": "ഇഷ്ടം നീക്കംചെയ്തു", + "link_motion_video": "ചലിക്കുന്ന വീഡിയോ ലിങ്ക് ചെയ്യുക", + "link_to_oauth": "OAuth-ലേക്ക് ലിങ്ക് ചെയ്യുക", + "linked_oauth_account": "ലിങ്ക് ചെയ്ത OAuth അക്കൗണ്ട്", + "list": "ലിസ്റ്റ്", + "loading": "ലോഡിംഗ്", + "loading_search_results_failed": "തിരയൽ ഫലങ്ങൾ ലോഡുചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", + "local": "പ്രാദേശികം", + "local_asset_cast_failed": "സെർവറിലേക്ക് അപ്‌ലോഡ് ചെയ്യാത്ത ഒരു അസറ്റ് കാസ്റ്റ് ചെയ്യാൻ കഴിയില്ല", + "local_assets": "പ്രാദേശിക അസറ്റുകൾ", + "local_media_summary": "പ്രാദേശിക മീഡിയ സംഗ്രഹം", + "local_network": "പ്രാദേശിക നെറ്റ്‌വർക്ക്", + "local_network_sheet_info": "നിർദ്ദിഷ്‌ട വൈ-ഫൈ നെറ്റ്‌വർക്ക് ഉപയോഗിക്കുമ്പോൾ ഈ URL വഴി ആപ്പ് സെർവറുമായി ബന്ധിപ്പിക്കും", + "location_permission": "ലൊക്കേഷൻ അനുമതി", + "location_permission_content": "യാന്ത്രിക-സ്വിച്ചിംഗ് ഫീച്ചർ ഉപയോഗിക്കുന്നതിന്, Immich-ന് കൃത്യമായ ലൊക്കേഷൻ അനുമതി ആവശ്യമാണ്, അതുവഴി നിലവിലെ വൈ-ഫൈ നെറ്റ്‌വർക്കിന്റെ പേര് വായിക്കാൻ കഴിയും", + "location_picker_choose_on_map": "മാപ്പിൽ തിരഞ്ഞെടുക്കുക", + "location_picker_latitude_error": "സാധുവായ അക്ഷാംശം നൽകുക", + "location_picker_latitude_hint": "നിങ്ങളുടെ അക്ഷാംശം ഇവിടെ നൽകുക", + "location_picker_longitude_error": "സാധുവായ രേഖാംശം നൽകുക", + "location_picker_longitude_hint": "നിങ്ങളുടെ രേഖാംശം ഇവിടെ നൽകുക", + "lock": "ലോക്ക് ചെയ്യുക", + "locked_folder": "ലോക്ക് ചെയ്ത ഫോൾഡർ", + "log_detail_title": "ലോഗ് വിശദാംശം", + "log_out": "ലോഗ് ഔട്ട്", + "log_out_all_devices": "എല്ലാ ഉപകരണങ്ങളിൽ നിന്നും ലോഗ് ഔട്ട് ചെയ്യുക", + "logged_in_as": "{user} ആയി ലോഗിൻ ചെയ്തു", + "logged_out_all_devices": "എല്ലാ ഉപകരണങ്ങളിൽ നിന്നും ലോഗ് ഔട്ട് ചെയ്തു", + "logged_out_device": "ഉപകരണത്തിൽ നിന്ന് ലോഗ് ഔട്ട് ചെയ്തു", + "login": "ലോഗിൻ", + "login_disabled": "ലോഗിൻ പ്രവർത്തനരഹിതമാക്കി", + "login_form_api_exception": "API എക്സെപ്ഷൻ. ദയവായി സെർവർ URL പരിശോധിച്ച് വീണ്ടും ശ്രമിക്കുക.", + "login_form_back_button_text": "തിരികെ", + "login_form_email_hint": "youremail@email.com", + "login_form_endpoint_hint": "http://your-server-ip:port", + "login_form_endpoint_url": "സെർവർ എൻഡ്‌പോയിന്റ് URL", + "login_form_err_http": "ദയവായി http:// അല്ലെങ്കിൽ https:// എന്ന് വ്യക്തമാക്കുക", + "login_form_err_invalid_email": "അസാധുവായ ഇമെയിൽ", + "login_form_err_invalid_url": "അസാധുവായ URL", + "login_form_err_leading_whitespace": "തുടക്കത്തിലെ ശൂന്യസ്ഥലം", + "login_form_err_trailing_whitespace": "അവസാനത്തെ ശൂന്യസ്ഥലം", + "login_form_failed_get_oauth_server_config": "OAuth ഉപയോഗിച്ച് ലോഗിൻ ചെയ്യുന്നതിൽ പിശക്, സെർവർ URL പരിശോധിക്കുക", + "login_form_failed_get_oauth_server_disable": "ഈ സെർവറിൽ OAuth ഫീച്ചർ ലഭ്യമല്ല", + "login_form_failed_login": "നിങ്ങളെ ലോഗിൻ ചെയ്യുന്നതിൽ പിശക്, സെർവർ URL, ഇമെയിൽ, പാസ്‌വേഡ് എന്നിവ പരിശോധിക്കുക", + "login_form_handshake_exception": "സെർവറുമായി ഒരു ഹാൻഡ്‌ഷേക്ക് എക്സെപ്ഷൻ ഉണ്ടായി. നിങ്ങൾ ഒരു സ്വയം ഒപ്പിട്ട സർട്ടിഫിക്കറ്റാണ് ഉപയോഗിക്കുന്നതെങ്കിൽ ക്രമീകരണങ്ങളിൽ സ്വയം ഒപ്പിട്ട സർട്ടിഫിക്കറ്റ് പിന്തുണ പ്രവർത്തനക്ഷമമാക്കുക.", + "login_form_password_hint": "പാസ്‌വേഡ്", + "login_form_save_login": "ലോഗിൻ ആയി തുടരുക", + "login_form_server_empty": "ഒരു സെർവർ URL നൽകുക.", + "login_form_server_error": "സെർവറിലേക്ക് കണക്റ്റുചെയ്യാൻ കഴിഞ്ഞില്ല.", + "login_has_been_disabled": "ലോഗിൻ പ്രവർത്തനരഹിതമാക്കി.", + "login_password_changed_error": "നിങ്ങളുടെ പാസ്‌വേഡ് അപ്ഡേറ്റ് ചെയ്യുന്നതിൽ ഒരു പിശകുണ്ടായി", + "login_password_changed_success": "പാസ്‌വേഡ് വിജയകരമായി അപ്ഡേറ്റ് ചെയ്തു", + "logout_all_device_confirmation": "എല്ലാ ഉപകരണങ്ങളിൽ നിന്നും ലോഗ് ഔട്ട് ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", + "logout_this_device_confirmation": "ഈ ഉപകരണത്തിൽ നിന്ന് ലോഗ് ഔട്ട് ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", + "logs": "ലോഗുകൾ", + "longitude": "രേഖാംശം", + "look": "കാഴ്ച", + "loop_videos": "വീഡിയോകൾ ലൂപ്പ് ചെയ്യുക", + "loop_videos_description": "വിശദാംശ വ്യൂവറിൽ ഒരു വീഡിയോ യാന്ത്രികമായി ലൂപ്പ് ചെയ്യാൻ പ്രവർത്തനക്ഷമമാക്കുക.", + "main_branch_warning": "നിങ്ങൾ ഒരു ഡെവലപ്‌മെന്റ് പതിപ്പാണ് ഉപയോഗിക്കുന്നത്; ഒരു റിലീസ് പതിപ്പ് ഉപയോഗിക്കാൻ ഞങ്ങൾ ശക്തമായി ശുപാർശ ചെയ്യുന്നു!", + "main_menu": "പ്രധാന മെനു", + "make": "നിർമ്മാതാവ്", + "manage_geolocation": "സ്ഥാനം കൈകാര്യം ചെയ്യുക", + "manage_shared_links": "പങ്കിട്ട ലിങ്കുകൾ കൈകാര്യം ചെയ്യുക", + "manage_sharing_with_partners": "പങ്കാളികളുമായുള്ള പങ്കിടൽ കൈകാര്യം ചെയ്യുക", + "manage_the_app_settings": "ആപ്പ് ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", + "manage_your_account": "നിങ്ങളുടെ അക്കൗണ്ട് കൈകാര്യം ചെയ്യുക", + "manage_your_api_keys": "നിങ്ങളുടെ API കീകൾ കൈകാര്യം ചെയ്യുക", + "manage_your_devices": "നിങ്ങൾ ലോഗിൻ ചെയ്ത ഉപകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", + "manage_your_oauth_connection": "നിങ്ങളുടെ OAuth കണക്ഷൻ കൈകാര്യം ചെയ്യുക", + "map": "മാപ്പ്", + "map_assets_in_bounds": "{count, plural, =0 {ഈ പ്രദേശത്ത് ഫോട്ടോകളൊന്നുമില്ല} one {# ഫോട്ടോ} other {# ഫോട്ടോകൾ}}", + "map_cannot_get_user_location": "ഉപയോക്താവിന്റെ സ്ഥാനം ലഭിക്കുന്നില്ല", + "map_location_dialog_yes": "അതെ", + "map_location_picker_page_use_location": "ഈ സ്ഥലം ഉപയോഗിക്കുക", + "map_location_service_disabled_content": "നിങ്ങളുടെ നിലവിലെ സ്ഥാനത്ത് നിന്നുള്ള അസറ്റുകൾ പ്രദർശിപ്പിക്കുന്നതിന് ലൊക്കേഷൻ സേവനം പ്രവർത്തനക്ഷമമാക്കേണ്ടതുണ്ട്. ഇപ്പോൾ പ്രവർത്തനക്ഷമമാക്കണോ?", + "map_location_service_disabled_title": "ലൊക്കേഷൻ സേവനം പ്രവർത്തനരഹിതമാക്കി", + "map_marker_for_images": "{city}, {country} എന്നിവിടങ്ങളിൽ എടുത്ത ചിത്രങ്ങൾക്കുള്ള മാപ്പ് മാർക്കർ", + "map_marker_with_image": "ചിത്രത്തോടുകൂടിയ മാപ്പ് മാർക്കർ", + "map_no_location_permission_content": "നിങ്ങളുടെ നിലവിലെ സ്ഥാനത്ത് നിന്നുള്ള അസറ്റുകൾ പ്രദർശിപ്പിക്കുന്നതിന് ലൊക്കേഷൻ അനുമതി ആവശ്യമാണ്. ഇപ്പോൾ അനുവദിക്കണോ?", + "map_no_location_permission_title": "ലൊക്കേഷൻ അനുമതി നിഷേധിച്ചു", + "map_settings": "മാപ്പ് ക്രമീകരണങ്ങൾ", + "map_settings_dark_mode": "ഡാർക്ക് മോഡ്", + "map_settings_date_range_option_day": "കഴിഞ്ഞ 24 മണിക്കൂർ", + "map_settings_date_range_option_days": "കഴിഞ്ഞ {days} ദിവസങ്ങൾ", + "map_settings_date_range_option_year": "കഴിഞ്ഞ വർഷം", + "map_settings_date_range_option_years": "കഴിഞ്ഞ {years} വർഷങ്ങൾ", + "map_settings_dialog_title": "മാപ്പ് ക്രമീകരണങ്ങൾ", + "map_settings_include_show_archived": "ആർക്കൈവുചെയ്‌തവ ഉൾപ്പെടുത്തുക", + "map_settings_include_show_partners": "പങ്കാളികളെ ഉൾപ്പെടുത്തുക", + "map_settings_only_show_favorites": "പ്രിയപ്പെട്ടവ മാത്രം കാണിക്കുക", + "map_settings_theme_settings": "മാപ്പ് തീം", + "map_zoom_to_see_photos": "ഫോട്ടോകൾ കാണാൻ സൂം ഔട്ട് ചെയ്യുക", + "mark_all_as_read": "എല്ലാം വായിച്ചതായി അടയാളപ്പെടുത്തുക", + "mark_as_read": "വായിച്ചതായി അടയാളപ്പെടുത്തുക", + "marked_all_as_read": "എല്ലാം വായിച്ചതായി അടയാളപ്പെടുത്തി", + "matches": "ചേർച്ചകൾ", + "matching_assets": "ചേർച്ചയുള്ള അസറ്റുകൾ", + "media_type": "മീഡിയ തരം", + "memories": "ഓർമ്മകൾ", + "memories_all_caught_up": "എല്ലാം കണ്ടുതീർത്തു", + "memories_check_back_tomorrow": "കൂടുതൽ ഓർമ്മകൾക്കായി നാളെ വീണ്ടും പരിശോധിക്കുക", + "memories_setting_description": "നിങ്ങളുടെ ഓർമ്മകളിൽ നിങ്ങൾ കാണുന്നത് കൈകാര്യം ചെയ്യുക", + "memories_start_over": "വീണ്ടും തുടങ്ങുക", + "memories_swipe_to_close": "അടയ്ക്കാൻ മുകളിലേക്ക് സ്വൈപ്പ് ചെയ്യുക", + "memory": "മെമ്മറി", + "memory_lane_title": "ഓർമ്മകളുടെ പാത {title}", + "menu": "മെനു", + "merge": "ലയിപ്പിക്കുക", + "merge_people": "ആളുകളെ ലയിപ്പിക്കുക", + "merge_people_limit": "ഒരു സമയം 5 മുഖങ്ങൾ വരെ മാത്രമേ ലയിപ്പിക്കാൻ കഴിയൂ", + "merge_people_prompt": "ഈ ആളുകളെ ലയിപ്പിക്കണോ? ഈ പ്രവർത്തനം പഴയപടിയാക്കാൻ കഴിയില്ല.", + "merge_people_successfully": "ആളുകളെ വിജയകരമായി ലയിപ്പിച്ചു", + "merged_people_count": "{count, plural, one {# വ്യക്തിയെ ലയിപ്പിച്ചു} other {# ആളുകളെ ലയിപ്പിച്ചു}}", + "minimize": "ചെറുതാക്കുക", + "minute": "മിനിറ്റ്", + "minutes": "മിനിറ്റുകൾ", + "missing": "കാണാനില്ല", + "model": "മോഡൽ", + "month": "മാസം", + "monthly_title_text_date_format": "MMMM y", + "more": "കൂടുതൽ", + "move": "നീക്കുക", + "move_off_locked_folder": "ലോക്ക് ചെയ്ത ഫോൾഡറിൽ നിന്ന് മാറ്റുക", + "move_to_lock_folder_action_prompt": "{count} എണ്ണം ലോക്ക് ചെയ്ത ഫോൾഡറിലേക്ക് ചേർത്തു", + "move_to_locked_folder": "ലോക്ക് ചെയ്ത ഫോൾഡറിലേക്ക് മാറ്റുക", + "move_to_locked_folder_confirmation": "ഈ ഫോട്ടോകളും വീഡിയോയും എല്ലാ ആൽബങ്ങളിൽ നിന്നും നീക്കം ചെയ്യപ്പെടും, ലോക്ക് ചെയ്ത ഫോൾഡറിൽ നിന്ന് മാത്രമേ കാണാൻ കഴിയൂ", + "moved_to_archive": "{count, plural, one {# അസറ്റ്} other {# അസറ്റുകൾ}} ആർക്കൈവിലേക്ക് മാറ്റി", + "moved_to_library": "{count, plural, one {# അസറ്റ്} other {# അസറ്റുകൾ}} ലൈബ്രറിയിലേക്ക് മാറ്റി", + "moved_to_trash": "ട്രാഷിലേക്ക് മാറ്റി", + "multiselect_grid_edit_date_time_err_read_only": "വായിക്കാൻ മാത്രമുള്ള അസറ്റി(കളു)ടെ തീയതി എഡിറ്റുചെയ്യാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", + "multiselect_grid_edit_gps_err_read_only": "വായിക്കാൻ മാത്രമുള്ള അസറ്റി(കളു)ടെ സ്ഥാനം എഡിറ്റുചെയ്യാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", + "mute_memories": "ഓർമ്മകൾ നിശ്ശബ്ദമാക്കുക", + "my_albums": "എന്റെ ആൽബങ്ങൾ", + "name": "പേര്", + "name_or_nickname": "പേര് അല്ലെങ്കിൽ വിളിപ്പേര്", + "network_requirement_photos_upload": "ഫോട്ടോകൾ ബാക്കപ്പ് ചെയ്യാൻ സെല്ലുലാർ ഡാറ്റ ഉപയോഗിക്കുക", + "network_requirement_videos_upload": "വീഡിയോകൾ ബാക്കപ്പ് ചെയ്യാൻ സെല്ലുലാർ ഡാറ്റ ഉപയോഗിക്കുക", + "network_requirements": "നെറ്റ്‌വർക്ക് ആവശ്യകതകൾ", + "network_requirements_updated": "നെറ്റ്‌വർക്ക് ആവശ്യകതകൾ മാറി, ബാക്കപ്പ് ക്യൂ പുനഃസജ്ജമാക്കുന്നു", + "networking_settings": "നെറ്റ്‌വർക്കിംഗ്", + "networking_subtitle": "സെർവർ എൻഡ്‌പോയിന്റ് ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", + "never": "ഒരിക്കലും ഇല്ല", + "new_album": "പുതിയ ആൽബം", + "new_api_key": "പുതിയ API കീ", + "new_password": "പുതിയ പാസ്‌വേഡ്", + "new_person": "പുതിയ വ്യക്തി", + "new_pin_code": "പുതിയ പിൻ കോഡ്", + "new_pin_code_subtitle": "നിങ്ങൾ ആദ്യമായിട്ടാണ് ലോക്ക് ചെയ്ത ഫോൾഡർ ആക്‌സസ് ചെയ്യുന്നത്. ഈ പേജ് സുരക്ഷിതമായി ആക്‌സസ് ചെയ്യാൻ ഒരു പിൻ കോഡ് ഉണ്ടാക്കുക", + "new_timeline": "പുതിയ ടൈംലൈൻ", + "new_user_created": "പുതിയ ഉപയോക്താവിനെ ഉണ്ടാക്കി", + "new_version_available": "പുതിയ പതിപ്പ് ലഭ്യമാണ്", + "newest_first": "ഏറ്റവും പുതിയത് ആദ്യം", + "next": "അടുത്തത്", + "next_memory": "അടുത്ത ഓർമ്മ", + "no": "ഇല്ല", + "no_albums_message": "നിങ്ങളുടെ ഫോട്ടോകളും വീഡിയോകളും ക്രമീകരിക്കാൻ ഒരു ആൽബം ഉണ്ടാക്കുക", + "no_albums_with_name_yet": "ഈ പേരിൽ നിങ്ങൾക്ക് ഇതുവരെ ആൽബങ്ങളൊന്നും ഇല്ലെന്ന് തോന്നുന്നു.", + "no_albums_yet": "നിങ്ങൾക്ക് ഇതുവരെ ആൽബങ്ങളൊന്നും ഇല്ലെന്ന് തോന്നുന്നു.", + "no_archived_assets_message": "നിങ്ങളുടെ ഫോട്ടോ കാഴ്‌ചയിൽ നിന്ന് മറയ്ക്കാൻ ഫോട്ടോകളും വീഡിയോകളും ആർക്കൈവ് ചെയ്യുക", + "no_assets_message": "നിങ്ങളുടെ ആദ്യ ഫോട്ടോ അപ്‌ലോഡ് ചെയ്യാൻ ക്ലിക്കുചെയ്യുക", + "no_assets_to_show": "കാണിക്കാൻ അസറ്റുകളൊന്നുമില്ല", + "no_cast_devices_found": "കാസ്റ്റ് ഉപകരണങ്ങളൊന്നും കണ്ടെത്തിയില്ല", + "no_checksum_local": "ചെക്ക്സം ലഭ്യമല്ല - പ്രാദേശിക അസറ്റുകൾ ലഭ്യമാക്കാൻ കഴിയില്ല", + "no_checksum_remote": "ചെക്ക്സം ലഭ്യമല്ല - റിമോട്ട് അസറ്റ് ലഭ്യമാക്കാൻ കഴിയില്ല", + "no_duplicates_found": "ഡ്യൂപ്ലിക്കേറ്റുകളൊന്നും കണ്ടെത്തിയില്ല.", + "no_exif_info_available": "Exif വിവരങ്ങൾ ലഭ്യമല്ല", + "no_explore_results_message": "നിങ്ങളുടെ ശേഖരം പര്യവേക്ഷണം ചെയ്യാൻ കൂടുതൽ ഫോട്ടോകൾ അപ്‌ലോഡ് ചെയ്യുക.", + "no_favorites_message": "നിങ്ങളുടെ മികച്ച ചിത്രങ്ങളും വീഡിയോകളും വേഗത്തിൽ കണ്ടെത്താൻ പ്രിയപ്പെട്ടവ ചേർക്കുക", + "no_libraries_message": "നിങ്ങളുടെ ഫോട്ടോകളും വീഡിയോകളും കാണുന്നതിന് ഒരു ബാഹ്യ ലൈബ്രറി ഉണ്ടാക്കുക", + "no_local_assets_found": "ഈ ചെക്ക്സം ഉപയോഗിച്ച് പ്രാദേശിക അസറ്റുകളൊന്നും കണ്ടെത്തിയില്ല", + "no_locked_photos_message": "ലോക്ക് ചെയ്‌ത ഫോൾഡറിലെ ഫോട്ടോകളും വീഡിയോകളും മറച്ചിരിക്കുന്നു, നിങ്ങൾ ലൈബ്രറി ബ്രൗസ് ചെയ്യുമ്പോഴോ തിരയുമ്പോഴോ അവ ദൃശ്യമാകില്ല.", + "no_name": "പേരില്ല", + "no_notifications": "അറിയിപ്പുകളൊന്നുമില്ല", + "no_people_found": "ചേർച്ചയുള്ള ആളുകളെ കണ്ടെത്തിയില്ല", + "no_places": "സ്ഥലങ്ങളില്ല", + "no_remote_assets_found": "ഈ ചെക്ക്സം ഉപയോഗിച്ച് റിമോട്ട് അസറ്റുകളൊന്നും കണ്ടെത്തിയില്ല", + "no_results": "ഫലങ്ങളൊന്നുമില്ല", + "no_results_description": "ഒരു പര്യായപദമോ കൂടുതൽ പൊതുവായ കീവേഡോ ഉപയോഗിച്ച് ശ്രമിക്കുക", + "no_shared_albums_message": "നിങ്ങളുടെ നെറ്റ്‌വർക്കിലെ ആളുകളുമായി ഫോട്ടോകളും വീഡിയോകളും പങ്കിടാൻ ഒരു ആൽബം ഉണ്ടാക്കുക", + "no_uploads_in_progress": "അപ്‌ലോഡുകളൊന്നും പുരോഗമിക്കുന്നില്ല", + "not_available": "ലഭ്യമല്ല", + "not_in_any_album": "ഒരു ആൽബത്തിലുമില്ല", + "not_selected": "തിരഞ്ഞെടുത്തിട്ടില്ല", + "note_apply_storage_label_to_previously_uploaded assets": "കുറിപ്പ്: മുമ്പ് അപ്‌ലോഡ് ചെയ്ത അസറ്റുകളിൽ സ്റ്റോറേജ് ലേബൽ പ്രയോഗിക്കാൻ, ഇത് പ്രവർത്തിപ്പിക്കുക", + "notes": "കുറിപ്പുകൾ", + "nothing_here_yet": "ഇവിടെ ഇതുവരെ ഒന്നുമില്ല", + "notification_permission_dialog_content": "അറിയിപ്പുകൾ പ്രവർത്തനക്ഷമമാക്കാൻ, ക്രമീകരണങ്ങളിലേക്ക് പോയി 'അനുവദിക്കുക' തിരഞ്ഞെടുക്കുക.", + "notification_permission_list_tile_content": "അറിയിപ്പുകൾ പ്രവർത്തനക്ഷമമാക്കാൻ അനുമതി നൽകുക.", + "notification_permission_list_tile_enable_button": "അറിയിപ്പുകൾ പ്രവർത്തനക്ഷമമാക്കുക", + "notification_permission_list_tile_title": "അറിയിപ്പ് അനുമതി", + "notification_toggle_setting_description": "ഇമെയിൽ അറിയിപ്പുകൾ പ്രവർത്തനക്ഷമമാക്കുക", + "notifications": "അറിയിപ്പുകൾ", + "notifications_setting_description": "അറിയിപ്പുകൾ കൈകാര്യം ചെയ്യുക", + "oauth": "OAuth", + "official_immich_resources": "Immich-ന്റെ ഔദ്യോഗിക ഉറവിടങ്ങൾ", + "offline": "ഓഫ്‌ലൈൻ", + "offset": "ഓഫ്സെറ്റ്", + "ok": "ശരി", + "oldest_first": "ഏറ്റവും പഴയത് ആദ്യം", + "on_this_device": "ഈ ഉപകരണത്തിൽ", + "onboarding": "ഓൺബോർഡിംഗ്", + "onboarding_locale_description": "നിങ്ങൾക്കിഷ്ടപ്പെട്ട ഭാഷ തിരഞ്ഞെടുക്കുക. ഇത് പിന്നീട് നിങ്ങളുടെ ക്രമീകരണങ്ങളിൽ മാറ്റാവുന്നതാണ്.", + "onboarding_privacy_description": "ഇനിപ്പറയുന്ന (ഓപ്ഷണൽ) ഫീച്ചറുകൾ ബാഹ്യ സേവനങ്ങളെ ആശ്രയിച്ചിരിക്കുന്നു, അവ എപ്പോൾ വേണമെങ്കിലും ക്രമീകരണങ്ങളിൽ പ്രവർത്തനരഹിതമാക്കാം.", + "onboarding_server_welcome_description": "ചില സാധാരണ ക്രമീകരണങ്ങൾ ഉപയോഗിച്ച് നിങ്ങളുടെ ഇൻസ്റ്റൻസ് സജ്ജമാക്കാം.", + "onboarding_theme_description": "നിങ്ങളുടെ ഇൻസ്റ്റൻസിനായി ഒരു കളർ തീം തിരഞ്ഞെടുക്കുക. ഇത് പിന്നീട് നിങ്ങളുടെ ക്രമീകരണങ്ങളിൽ മാറ്റാവുന്നതാണ്.", + "onboarding_user_welcome_description": "നമുക്ക് ആരംഭിക്കാം!", + "onboarding_welcome_user": "സ്വാഗതം, {user}", + "online": "ഓൺലൈൻ", + "only_favorites": "പ്രിയപ്പെട്ടവ മാത്രം", + "open": "തുറക്കുക", + "open_in_map_view": "മാപ്പ് കാഴ്‌ചയിൽ തുറക്കുക", + "open_in_openstreetmap": "OpenStreetMap-ൽ തുറക്കുക", + "open_the_search_filters": "തിരയൽ ഫിൽട്ടറുകൾ തുറക്കുക", + "options": "ഓപ്ഷനുകൾ", + "or": "അല്ലെങ്കിൽ", + "organize_into_albums": "ആൽബങ്ങളായി ക്രമീകരിക്കുക", + "organize_into_albums_description": "നിലവിലെ സിങ്ക് ക്രമീകരണങ്ങൾ ഉപയോഗിച്ച് നിലവിലുള്ള ഫോട്ടോകൾ ആൽബങ്ങളിലേക്ക് മാറ്റുക", + "organize_your_library": "നിങ്ങളുടെ ലൈബ്രറി ക്രമീകരിക്കുക", + "original": "യഥാർത്ഥം", + "other": "മറ്റുള്ളവ", + "other_devices": "മറ്റ് ഉപകരണങ്ങൾ", + "other_entities": "മറ്റ് എന്റിറ്റികൾ", + "other_variables": "മറ്റ് വേരിയബിളുകൾ", + "owned": "സ്വന്തമായുള്ളത്", + "owner": "ഉടമ", + "partner": "പങ്കാളി", + "partner_can_access": "{partner}-ക്ക് ആക്സസ് ചെയ്യാൻ കഴിയും", + "partner_can_access_assets": "ആർക്കൈവുചെയ്‌തതും ഇല്ലാതാക്കിയതുമായവ ഒഴികെ നിങ്ങളുടെ എല്ലാ ഫോട്ടോകളും വീഡിയോകളും", + "partner_can_access_location": "നിങ്ങളുടെ ഫോട്ടോകൾ എടുത്ത സ്ഥലം", + "partner_list_user_photos": "{user}-ന്റെ ഫോട്ടോകൾ", + "partner_list_view_all": "എല്ലാം കാണുക", + "partner_page_empty_message": "നിങ്ങളുടെ ഫോട്ടോകൾ ഇതുവരെ ഒരു പങ്കാളിയുമായും പങ്കിട്ടിട്ടില്ല.", + "partner_page_no_more_users": "ചേർക്കാൻ കൂടുതൽ ഉപയോക്താക്കളില്ല", + "partner_page_partner_add_failed": "പങ്കാളിയെ ചേർക്കുന്നതിൽ പരാജയപ്പെട്ടു", + "partner_page_select_partner": "പങ്കാളിയെ തിരഞ്ഞെടുക്കുക", + "partner_page_shared_to_title": "ഇതിലേക്ക് പങ്കിട്ടു", + "partner_page_stop_sharing_content": "{partner}-ക്ക് ഇനി നിങ്ങളുടെ ഫോട്ടോകൾ ആക്‌സസ് ചെയ്യാൻ കഴിയില്ല.", + "partner_sharing": "പങ്കാളിയുമായി പങ്കിടൽ", + "partners": "പങ്കാളികൾ", + "password": "പാസ്‌വേർഡ്", + "password_does_not_match": "പാസ്‌വേഡ് പൊരുത്തപ്പെടുന്നില്ല", + "password_required": "പാസ്‌വേഡ് ആവശ്യമാണ്", + "password_reset_success": "പാസ്‌വേഡ് റീസെറ്റ് വിജയിച്ചു", + "past_durations": { + "days": "കഴിഞ്ഞ {days, plural, one {ദിവസം} other {# ദിവസങ്ങൾ}}", + "hours": "കഴിഞ്ഞ {hours, plural, one {മണിക്കൂർ} other {# മണിക്കൂറുകൾ}}", + "years": "കഴിഞ്ഞ {years, plural, one {വർഷം} other {# വർഷങ്ങൾ}}" + }, + "path": "പാത്ത്", + "pattern": "പാറ്റേൺ", + "pause": "താൽക്കാലികമായി നിർത്തുക", + "pause_memories": "ഓർമ്മകൾ താൽക്കാലികമായി നിർത്തുക", + "paused": "താൽക്കാലികമായി നിർത്തി", + "pending": "കാത്തിരിക്കുന്നു", + "people": "ആളുകൾ", + "people_edits_count": "{count, plural, one {# വ്യക്തിയെ എഡിറ്റുചെയ്തു} other {# ആളുകളെ എഡിറ്റുചെയ്തു}}", + "people_feature_description": "ആളുകളനുസരിച്ച് ഗ്രൂപ്പ് ചെയ്ത ഫോട്ടോകളും വീഡിയോകളും ബ്രൗസുചെയ്യുന്നു", + "people_sidebar_description": "സൈഡ്‌ബാറിൽ 'ആളുകൾ' എന്നതിലേക്ക് ഒരു ലിങ്ക് പ്രദർശിപ്പിക്കുക", + "permanent_deletion_warning": "ശാശ്വതമായ ഇല്ലാതാക്കൽ മുന്നറിയിപ്പ്", + "permanent_deletion_warning_setting_description": "അസറ്റുകൾ ശാശ്വതമായി ഇല്ലാതാക്കുമ്പോൾ ഒരു മുന്നറിയിപ്പ് കാണിക്കുക", + "permanently_delete": "ശാശ്വതമായി ഇല്ലാതാക്കുക", + "permanently_delete_assets_count": "{count, plural, one {അസറ്റ്} other {അസറ്റുകൾ}} ശാശ്വതമായി ഇല്ലാതാക്കുക", + "permanently_delete_assets_prompt": "{count, plural, one {ഈ അസറ്റ് ശാശ്വതമായി ഇല്ലാതാക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?} other {ഈ # അസറ്റുകൾ ശാശ്വതമായി ഇല്ലാതാക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?}} ഇത് {count, plural, one {അതിനെ അതിന്റെ} other {അവയെ അവയുടെ}} ആൽബത്തിൽ(ങ്ങളിൽ) നിന്നും നീക്കം ചെയ്യും.", + "permanently_deleted_asset": "അസറ്റ് ശാശ്വതമായി ഇല്ലാതാക്കി", + "permanently_deleted_assets_count": "{count, plural, one {# അസറ്റ്} other {# അസറ്റുകൾ}} ശാശ്വതമായി ഇല്ലാതാക്കി", + "permission": "അനുമതി", + "permission_empty": "നിങ്ങളുടെ അനുമതി ശൂന്യമാകരുത്", + "permission_onboarding_back": "തിരികെ", + "permission_onboarding_continue_anyway": "എന്തായാലും തുടരുക", + "permission_onboarding_get_started": "തുടങ്ങാം", + "permission_onboarding_go_to_settings": "ക്രമീകരണങ്ങളിലേക്ക് പോകുക", + "permission_onboarding_permission_denied": "അനുമതി നിഷേധിച്ചു. Immich ഉപയോഗിക്കുന്നതിന്, ക്രമീകരണങ്ങളിൽ ഫോട്ടോ, വീഡിയോ അനുമതികൾ നൽകുക.", + "permission_onboarding_permission_granted": "അനുമതി ലഭിച്ചു! നിങ്ങൾ തയ്യാറാണ്.", + "permission_onboarding_permission_limited": "അനുമതി പരിമിതമാണ്. Immich-ന് നിങ്ങളുടെ മുഴുവൻ ഗാലറി ശേഖരവും ബാക്കപ്പ് ചെയ്യാനും കൈകാര്യം ചെയ്യാനും അനുവദിക്കുന്നതിന്, ക്രമീകരണങ്ങളിൽ ഫോട്ടോ, വീഡിയോ അനുമതികൾ നൽകുക.", + "permission_onboarding_request": "നിങ്ങളുടെ ഫോട്ടോകളും വീഡിയോകളും കാണുന്നതിന് Immich-ന് അനുമതി ആവശ്യമാണ്.", + "person": "വ്യക്തി", + "person_age_months": "{months, plural, one {# മാസം} other {# മാസം}} പ്രായം", + "person_age_year_months": "1 വർഷവും {months, plural, one {# മാസവും} other {# മാസവും}} പ്രായം", + "person_age_years": "{years, plural, other {# വയസ്സ്}}", + "person_birthdate": "{date}-ന് ജനിച്ചു", + "person_hidden": "{name}{hidden, select, true { (മറച്ചത്)} other {}}", + "photo_shared_all_users": "നിങ്ങൾ എല്ലാ ഉപയോക്താക്കളുമായും ഫോട്ടോകൾ പങ്കിട്ടു, അല്ലെങ്കിൽ നിങ്ങൾക്ക് പങ്കിടാൻ ഉപയോക്താക്കളാരും ഇല്ലെന്നു തോന്നുന്നു.", + "photos": "ഫോട്ടോകൾ", + "photos_and_videos": "ഫോട്ടോകളും വീഡിയോകളും", + "photos_count": "{count, plural, one {{count, number} ഫോട്ടോ} other {{count, number} ഫോട്ടോകൾ}}", + "photos_from_previous_years": "മുൻ വർഷങ്ങളിലെ ഫോട്ടോകൾ", + "pick_a_location": "ഒരു സ്ഥലം തിരഞ്ഞെടുക്കുക", + "pin_code_changed_successfully": "പിൻ കോഡ് വിജയകരമായി മാറ്റി", + "pin_code_reset_successfully": "പിൻ കോഡ് വിജയകരമായി റീസെറ്റ് ചെയ്തു", + "pin_code_setup_successfully": "പിൻ കോഡ് വിജയകരമായി സജ്ജീകരിച്ചു", + "pin_verification": "പിൻ കോഡ് പരിശോധന", + "place": "സ്ഥലം", + "places": "സ്ഥലങ്ങൾ", + "places_count": "{count, plural, one {{count, number} സ്ഥലം} other {{count, number} സ്ഥലങ്ങൾ}}", + "play": "പ്ലേ ചെയ്യുക", + "play_memories": "ഓർമ്മകൾ പ്ലേ ചെയ്യുക", + "play_motion_photo": "ചലിക്കുന്ന ഫോട്ടോ പ്ലേ ചെയ്യുക", + "play_or_pause_video": "വീഡിയോ പ്ലേ ചെയ്യുക അല്ലെങ്കിൽ താൽക്കാലികമായി നിർത്തുക", + "please_auth_to_access": "ആക്‌സസ് ചെയ്യുന്നതിന് ദയവായി പ്രാമാണീകരിക്കുക", + "port": "പോർട്ട്", + "preferences_settings_subtitle": "ആപ്പിന്റെ മുൻഗണനകൾ കൈകാര്യം ചെയ്യുക", + "preferences_settings_title": "മുൻഗണനകൾ", + "preparing": "തയ്യാറാക്കുന്നു", + "preset": "പ്രീസെറ്റ്", + "preview": "പ്രിവ്യൂ", + "previous": "മുമ്പത്തെ", + "previous_memory": "മുമ്പത്തെ ഓർമ്മ", + "previous_or_next_day": "ദിവസം മുന്നോട്ട്/പുറകോട്ട്", + "previous_or_next_month": "മാസം മുന്നോട്ട്/പുറകോട്ട്", + "previous_or_next_photo": "ഫോട്ടോ മുന്നോട്ട്/പുറകോട്ട്", + "previous_or_next_year": "വർഷം മുന്നോട്ട്/പുറകോട്ട്", + "primary": "പ്രധാനമായത്", + "privacy": "സ്വകാര്യത", + "profile": "പ്രൊഫൈൽ", + "profile_drawer_app_logs": "ലോഗുകൾ", + "profile_drawer_client_server_up_to_date": "ക്ലയിന്റും സെർവറും ഏറ്റവും പുതിയതാണ്", + "profile_drawer_github": "ഗിറ്റ്ഹബ്", + "profile_drawer_readonly_mode": "റീഡ്-ഓൺലി മോഡ് പ്രവർത്തനക്ഷമമാക്കി. പുറത്തുകടക്കാൻ ഉപയോക്തൃ അവതാർ ഐക്കണിൽ ദീർഘനേരം അമർത്തുക.", + "profile_image_of_user": "{user}-ന്റെ പ്രൊഫൈൽ ചിത്രം", + "profile_picture_set": "പ്രൊഫൈൽ ചിത്രം സജ്ജീകരിച്ചു.", + "public_album": "പൊതു ആൽബം", + "public_share": "പൊതു പങ്കിടൽ", + "purchase_account_info": "സഹായി", + "purchase_activated_subtitle": "Immich-നെയും ഓപ്പൺ സോഴ്‌സ് സോഫ്റ്റ്‌വെയറിനെയും പിന്തുണച്ചതിന് നന്ദി", + "purchase_activated_time": "{date}-ന് സജീവമാക്കി", + "purchase_activated_title": "നിങ്ങളുടെ കീ വിജയകരമായി സജീവമാക്കി", + "purchase_button_activate": "സജീവമാക്കുക", + "purchase_button_buy": "വാങ്ങുക", + "purchase_button_buy_immich": "Immich വാങ്ങുക", + "purchase_button_never_show_again": "ഇനി കാണിക്കരുത്", + "purchase_button_reminder": "30 ദിവസത്തിനുള്ളിൽ ഓർമ്മിപ്പിക്കുക", + "purchase_button_remove_key": "കീ നീക്കം ചെയ്യുക", + "purchase_button_select": "തിരഞ്ഞെടുക്കുക", + "purchase_failed_activation": "സജീവമാക്കുന്നതിൽ പരാജയപ്പെട്ടു! ശരിയായ പ്രൊഡക്റ്റ് കീക്കായി ദയവായി നിങ്ങളുടെ ഇമെയിൽ പരിശോധിക്കുക!", + "purchase_individual_description_1": "ഒരു വ്യക്തിക്ക്", + "purchase_individual_description_2": "സഹായിയുടെ നില", + "purchase_individual_title": "വ്യക്തിഗതം", + "purchase_input_suggestion": "പ്രൊഡക്റ്റ് കീ ഉണ്ടോ? താഴെ കീ നൽകുക", + "purchase_license_subtitle": "സേവനത്തിന്റെ തുടർ വികസനത്തെ പിന്തുണയ്ക്കാൻ Immich വാങ്ങുക", + "purchase_lifetime_description": "ആജീവനാന്ത വാങ്ങൽ", + "purchase_option_title": "വാങ്ങാനുള്ള ഓപ്ഷനുകൾ", + "purchase_panel_info_1": "Immich നിർമ്മിക്കാൻ ധാരാളം സമയവും പ്രയത്നവും ആവശ്യമാണ്, അത് കഴിയുന്നത്ര മികച്ചതാക്കാൻ മുഴുവൻ സമയ എഞ്ചിനീയർമാർ പ്രവർത്തിക്കുന്നു. ഓപ്പൺ സോഴ്‌സ് സോഫ്റ്റ്‌വെയറും ധാർമ്മികമായ ബിസിനസ്സ് രീതികളും ഡെവലപ്പർമാർക്ക് സുസ്ഥിരമായ വരുമാന മാർഗ്ഗമാക്കുക, ചൂഷണപരമായ ക്ലൗഡ് സേവനങ്ങൾക്ക് യഥാർത്ഥ ബദലുകളുള്ള സ്വകാര്യതയെ മാനിക്കുന്ന ഒരു ആവാസവ്യവസ്ഥ സൃഷ്ടിക്കുക എന്നതാണ് ഞങ്ങളുടെ ദൗത്യം.", + "purchase_panel_info_2": "പേവാളുകൾ ചേർക്കില്ലെന്ന് ഞങ്ങൾ പ്രതിജ്ഞാബദ്ധരായതിനാൽ, ഈ വാങ്ങൽ നിങ്ങൾക്ക് Immich-ൽ അധിക ഫീച്ചറുകളൊന്നും നൽകില്ല. Immich-ന്റെ തുടർ വികസനത്തെ പിന്തുണയ്ക്കാൻ നിങ്ങളെപ്പോലുള്ള ഉപയോക്താക്കളെയാണ് ഞങ്ങൾ ആശ്രയിക്കുന്നത്.", + "purchase_panel_title": "പദ്ധതിയെ പിന്തുണയ്ക്കുക", + "purchase_per_server": "ഓരോ സെർവറിനും", + "purchase_per_user": "ഓരോ ഉപയോക്താവിനും", + "purchase_remove_product_key": "പ്രൊഡക്റ്റ് കീ നീക്കം ചെയ്യുക", + "purchase_remove_product_key_prompt": "പ്രൊഡക്റ്റ് കീ നീക്കം ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", + "purchase_remove_server_product_key": "സെർവർ പ്രൊഡക്റ്റ് കീ നീക്കം ചെയ്യുക", + "purchase_remove_server_product_key_prompt": "സെർവർ പ്രൊഡക്റ്റ് കീ നീക്കം ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", + "purchase_server_description_1": "മുഴുവൻ സെർവറിനും", + "purchase_server_description_2": "സഹായിയുടെ നില", + "purchase_server_title": "സെർവർ", + "purchase_settings_server_activated": "സെർവർ പ്രൊഡക്റ്റ് കീ അഡ്മിൻ ആണ് കൈകാര്യം ചെയ്യുന്നത്", + "query_asset_id": "അസറ്റ് ഐഡി അന്വേഷിക്കുക", + "queue_status": "ക്യൂ ചെയ്യുന്നു {count}/{total}", + "rating": "സ്റ്റാർ റേറ്റിംഗ്", + "rating_clear": "റേറ്റിംഗ് ക്ലിയർ ചെയ്യുക", + "rating_count": "{count, plural, one {# സ്റ്റാർ} other {# സ്റ്റാറുകൾ}}", + "rating_description": "വിവര പാനലിൽ EXIF റേറ്റിംഗ് പ്രദർശിപ്പിക്കുക", + "reaction_options": "പ്രതികരണ ഓപ്ഷനുകൾ", + "read_changelog": "മാറ്റങ്ങളുടെ ലോഗ് വായിക്കുക", + "readonly_mode_disabled": "റീഡ്-ഓൺലി മോഡ് പ്രവർത്തനരഹിതമാക്കി", + "readonly_mode_enabled": "റീഡ്-ഓൺലി മോഡ് പ്രവർത്തനക്ഷമമാക്കി", + "ready_for_upload": "അപ്‌ലോഡിനായി തയ്യാറാണ്", + "reassign": "വീണ്ടും നൽകുക", + "reassigned_assets_to_existing_person": "{count, plural, one {# അസറ്റ്} other {# അസറ്റുകൾ}} {name, select, null {നിലവിലുള്ള ഒരു വ്യക്തിക്ക്} other {{name}-ന്}} വീണ്ടും നൽകി", + "reassigned_assets_to_new_person": "{count, plural, one {# അസറ്റ്} other {# അസറ്റുകൾ}} ഒരു പുതിയ വ്യക്തിക്ക് വീണ്ടും നൽകി", + "reassing_hint": "തിരഞ്ഞെടുത്ത അസറ്റുകൾ നിലവിലുള്ള ഒരു വ്യക്തിക്ക് നൽകുക", + "recent": "സമീപകാലം", + "recent-albums": "സമീപകാല ആൽബങ്ങൾ", + "recent_searches": "സമീപകാല തിരയലുകൾ", + "recently_added": "അടുത്തിടെ ചേർത്തത്", + "recently_added_page_title": "അടുത്തിടെ ചേർത്തത്", + "recently_taken": "അടുത്തിടെ എടുത്തത്", + "recently_taken_page_title": "അടുത്തിടെ എടുത്തത്", + "refresh": "പുതുക്കുക", + "refresh_encoded_videos": "എൻകോഡ് ചെയ്ത വീഡിയോകൾ പുതുക്കുക", + "refresh_faces": "മുഖങ്ങൾ പുതുക്കുക", + "refresh_metadata": "മെറ്റാഡാറ്റ പുതുക്കുക", + "refresh_thumbnails": "തംബ്നെയിലുകൾ പുതുക്കുക", + "refreshed": "പുതുക്കി", + "refreshes_every_file": "നിലവിലുള്ളതും പുതിയതുമായ എല്ലാ ഫയലുകളും വീണ്ടും വായിക്കുന്നു", + "refreshing_encoded_video": "എൻകോഡ് ചെയ്ത വീഡിയോ പുതുക്കുന്നു", + "refreshing_faces": "മുഖങ്ങൾ പുതുക്കുന്നു", + "refreshing_metadata": "മെറ്റാഡാറ്റ പുതുക്കുന്നു", + "regenerating_thumbnails": "തംബ്നെയിലുകൾ പുനർനിർമ്മിക്കുന്നു", + "remote": "റിമോട്ട്", + "remote_assets": "റിമോട്ട് അസറ്റുകൾ", + "remote_media_summary": "റിമോട്ട് മീഡിയ സംഗ്രഹം", + "remove": "നീക്കം ചെയ്യുക", + "remove_assets_album_confirmation": "ആൽബത്തിൽ നിന്ന് {count, plural, one {# അസറ്റ്} other {# അസറ്റുകൾ}} നീക്കം ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", + "remove_assets_shared_link_confirmation": "ഈ പങ്കിട്ട ലിങ്കിൽ നിന്ന് {count, plural, one {# അസറ്റ്} other {# അസറ്റുകൾ}} നീക്കം ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", + "remove_assets_title": "അസറ്റുകൾ നീക്കം ചെയ്യണോ?", + "remove_custom_date_range": "കസ്റ്റം തീയതി പരിധി നീക്കം ചെയ്യുക", + "remove_deleted_assets": "ഇല്ലാതാക്കിയ അസറ്റുകൾ നീക്കം ചെയ്യുക", + "remove_from_album": "ആൽബത്തിൽ നിന്ന് നീക്കം ചെയ്യുക", + "remove_from_album_action_prompt": "{count} എണ്ണം ആൽബത്തിൽ നിന്ന് നീക്കം ചെയ്തു", + "remove_from_favorites": "പ്രിയപ്പെട്ടവയിൽ നിന്ന് നീക്കം ചെയ്യുക", + "remove_from_lock_folder_action_prompt": "{count} എണ്ണം ലോക്ക് ചെയ്ത ഫോൾഡറിൽ നിന്ന് നീക്കം ചെയ്തു", + "remove_from_locked_folder": "ലോക്ക് ചെയ്ത ഫോൾഡറിൽ നിന്ന് നീക്കം ചെയ്യുക", + "remove_from_locked_folder_confirmation": "ഈ ഫോട്ടോകളും വീഡിയോകളും ലോക്ക് ചെയ്ത ഫോൾഡറിൽ നിന്ന് മാറ്റണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ? അവ നിങ്ങളുടെ ലൈബ്രറിയിൽ ദൃശ്യമാകും.", + "remove_from_shared_link": "പങ്കിട്ട ലിങ്കിൽ നിന്ന് നീക്കം ചെയ്യുക", + "remove_memory": "ഓർമ്മ നീക്കം ചെയ്യുക", + "remove_photo_from_memory": "ഈ ഓർമ്മയിൽ നിന്ന് ഫോട്ടോ നീക്കം ചെയ്യുക", + "remove_tag": "ടാഗ് നീക്കം ചെയ്യുക", + "remove_url": "URL നീക്കം ചെയ്യുക", + "remove_user": "ഉപയോക്താവിനെ നീക്കം ചെയ്യുക", + "removed_api_key": "API കീ നീക്കം ചെയ്തു: {name}", + "removed_from_archive": "ആർക്കൈവിൽ നിന്ന് നീക്കം ചെയ്തു", + "removed_from_favorites": "പ്രിയപ്പെട്ടവയിൽ നിന്ന് നീക്കം ചെയ്തു", + "removed_from_favorites_count": "{count, plural, other {# എണ്ണം}} പ്രിയപ്പെട്ടവയിൽ നിന്ന് നീക്കം ചെയ്തു", + "removed_memory": "ഓർമ്മ നീക്കം ചെയ്തു", + "removed_photo_from_memory": "ഓർമ്മയിൽ നിന്ന് ഫോട്ടോ നീക്കം ചെയ്തു", + "removed_tagged_assets": "{count, plural, one {# അസറ്റിൽ നിന്ന്} other {# അസറ്റുകളിൽ നിന്ന്}} ടാഗ് നീക്കം ചെയ്തു", + "rename": "പുനർനാമകരണം ചെയ്യുക", + "repair": "റിപ്പയർ ചെയ്യുക", + "repair_no_results_message": "ട്രാക്ക് ചെയ്യാത്തതും കാണാതായതുമായ ഫയലുകൾ ഇവിടെ കാണിക്കും", + "replace_with_upload": "അപ്‌ലോഡ് ഉപയോഗിച്ച് മാറ്റിസ്ഥാപിക്കുക", + "repository": "റെപ്പോസിറ്ററി", + "require_password": "പാസ്‌വേഡ് ആവശ്യമാണ്", + "require_user_to_change_password_on_first_login": "ആദ്യ ലോഗിൻ ചെയ്യുമ്പോൾ പാസ്‌വേഡ് മാറ്റാൻ ഉപയോക്താവിനോട് ആവശ്യപ്പെടുക", + "rescan": "വീണ്ടും സ്കാൻ ചെയ്യുക", + "reset": "റീസെറ്റ് ചെയ്യുക", + "reset_password": "പാസ്‌വേഡ് റീസെറ്റ് ചെയ്യുക", + "reset_people_visibility": "ആളുകളുടെ ദൃശ്യത പുനഃസജ്ജമാക്കുക", + "reset_pin_code": "പിൻ കോഡ് റീസെറ്റ് ചെയ്യുക", + "reset_pin_code_description": "നിങ്ങൾ പിൻ കോഡ് മറന്നെങ്കിൽ, അത് പുനഃസജ്ജമാക്കാൻ നിങ്ങൾക്ക് സെർവർ അഡ്മിനിസ്ട്രേറ്ററുമായി ബന്ധപ്പെടാം", + "reset_pin_code_success": "പിൻ കോഡ് വിജയകരമായി റീസെറ്റ് ചെയ്തു", + "reset_pin_code_with_password": "നിങ്ങളുടെ പാസ്‌വേഡ് ഉപയോഗിച്ച് നിങ്ങൾക്ക് എപ്പോഴും പിൻ കോഡ് റീസെറ്റ് ചെയ്യാം", + "reset_sqlite": "SQLite ഡാറ്റാബേസ് റീസെറ്റ് ചെയ്യുക", + "reset_sqlite_confirmation": "SQLite ഡാറ്റാബേസ് റീസെറ്റ് ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ? ഡാറ്റ വീണ്ടും സിങ്ക് ചെയ്യുന്നതിന് നിങ്ങൾ ലോഗ് ഔട്ട് ചെയ്ത് വീണ്ടും ലോഗിൻ ചെയ്യേണ്ടിവരും", + "reset_sqlite_success": "SQLite ഡാറ്റാബേസ് വിജയകരമായി റീസെറ്റ് ചെയ്തു", + "reset_to_default": "ഡിഫോൾട്ടിലേക്ക് പുനഃസജ്ജമാക്കുക", + "resolve_duplicates": "ഡ്യൂപ്ലിക്കേറ്റുകൾ പരിഹരിക്കുക", + "resolved_all_duplicates": "എല്ലാ ഡ്യൂപ്ലിക്കേറ്റുകളും പരിഹരിച്ചു", + "restore": "പുനഃസ്ഥാപിക്കുക", + "restore_all": "എല്ലാം പുനഃസ്ഥാപിക്കുക", + "restore_trash_action_prompt": "{count} എണ്ണം ട്രാഷിൽ നിന്ന് പുനഃസ്ഥാപിച്ചു", + "restore_user": "ഉപയോക്താവിനെ പുനഃസ്ഥാപിക്കുക", + "restored_asset": "അസറ്റ് പുനഃസ്ഥാപിച്ചു", + "resume": "പുനരാരംഭിക്കുക", + "resume_paused_jobs": "{count, plural, one {താൽക്കാലികമായി നിർത്തിയ # ജോലി} other {താൽക്കാലികമായി നിർത്തിയ # ജോലികൾ}} പുനരാരംഭിക്കുക", + "retry_upload": "അപ്‌ലോഡ് വീണ്ടും ശ്രമിക്കുക", + "review_duplicates": "ഡ്യൂപ്ലിക്കേറ്റുകൾ അവലോകനം ചെയ്യുക", + "review_large_files": "വലിയ ഫയലുകൾ അവലോകനം ചെയ്യുക", + "role": "റോൾ", + "role_editor": "എഡിറ്റർ", + "role_viewer": "കാണുന്നയാൾ", + "running": "പ്രവർത്തിക്കുന്നു", + "save": "സേവ് ചെയ്യുക", + "save_to_gallery": "ഗാലറിയിലേക്ക് സേവ് ചെയ്യുക", + "saved_api_key": "API കീ സേവ് ചെയ്തു", + "saved_profile": "പ്രൊഫൈൽ സേവ് ചെയ്തു", + "saved_settings": "ക്രമീകരണങ്ങൾ സേവ് ചെയ്തു", + "say_something": "എന്തെങ്കിലും പറയൂ", + "scaffold_body_error_occurred": "പിശക് സംഭവിച്ചു", + "scan_all_libraries": "എല്ലാ ലൈബ്രറികളും സ്കാൻ ചെയ്യുക", + "scan_library": "സ്കാൻ ചെയ്യുക", + "scan_settings": "സ്കാൻ ക്രമീകരണങ്ങൾ", + "scanning_for_album": "ആൽബത്തിനായി സ്കാൻ ചെയ്യുന്നു...", + "search": "തിരയുക", + "search_albums": "ആൽബങ്ങൾക്കായി തിരയുക", + "search_by_context": "സന്ദർഭം അനുസരിച്ച് തിരയുക", + "search_by_description": "വിവരണം അനുസരിച്ച് തിരയുക", + "search_by_description_example": "സപ്പയിലെ ഹൈക്കിംഗ് ദിനം", + "search_by_filename": "ഫയൽ നാമം അല്ലെങ്കിൽ എക്സ്റ്റൻഷൻ ഉപയോഗിച്ച് തിരയുക", + "search_by_filename_example": "ഉദാ. IMG_1234.JPG അല്ലെങ്കിൽ PNG", + "search_camera_make": "ക്യാമറ നിർമ്മാതാവിനായി തിരയുക...", + "search_camera_model": "ക്യാമറ മോഡലിനായി തിരയുക...", + "search_city": "നഗരത്തിനായി തിരയുക...", + "search_country": "രാജ്യത്തിനായി തിരയുക...", + "search_filter_apply": "ഫിൽട്ടർ പ്രയോഗിക്കുക", + "search_filter_camera_title": "ക്യാമറ തരം തിരഞ്ഞെടുക്കുക", + "search_filter_date": "തീയതി", + "search_filter_date_interval": "{start} മുതൽ {end} വരെ", + "search_filter_date_title": "ഒരു തീയതി പരിധി തിരഞ്ഞെടുക്കുക", + "search_filter_display_option_not_in_album": "ആൽബത്തിൽ ഇല്ല", + "search_filter_display_options": "പ്രദർശന ഓപ്ഷനുകൾ", + "search_filter_filename": "ഫയൽ നാമം ഉപയോഗിച്ച് തിരയുക", + "search_filter_location": "സ്ഥാനം", + "search_filter_location_title": "സ്ഥാനം തിരഞ്ഞെടുക്കുക", + "search_filter_media_type": "മീഡിയ തരം", + "search_filter_media_type_title": "മീഡിയ തരം തിരഞ്ഞെടുക്കുക", + "search_filter_people_title": "ആളുകളെ തിരഞ്ഞെടുക്കുക", + "search_for": "ഇതിനായി തിരയുക", + "search_for_existing_person": "നിലവിലുള്ള വ്യക്തിക്കായി തിരയുക", + "search_no_more_result": "കൂടുതൽ ഫലങ്ങളില്ല", + "search_no_people": "ആളുകളില്ല", + "search_no_people_named": "\"{name}\" എന്ന് പേരുള്ള ആളുകളില്ല", + "search_no_result": "ഫലങ്ങളൊന്നും കണ്ടെത്തിയില്ല, മറ്റൊരു തിരയൽ പദമോ സംയോജനമോ ഉപയോഗിച്ച് ശ്രമിക്കുക", + "search_options": "തിരയൽ ഓപ്ഷനുകൾ", + "search_page_categories": "വിഭാഗങ്ങൾ", + "search_page_motion_photos": "ചലിക്കുന്ന ഫോട്ടോകൾ", + "search_page_no_objects": "വസ്തുക്കളുടെ വിവരങ്ങൾ ലഭ്യമല്ല", + "search_page_no_places": "സ്ഥലങ്ങളുടെ വിവരങ്ങൾ ലഭ്യമല്ല", + "search_page_screenshots": "സ്ക്രീൻഷോട്ടുകൾ", + "search_page_search_photos_videos": "നിങ്ങളുടെ ഫോട്ടോകൾക്കും വീഡിയോകൾക്കുമായി തിരയുക", + "search_page_selfies": "സെൽഫികൾ", + "search_page_things": "വസ്തുക്കൾ", + "search_page_view_all_button": "എല്ലാം കാണുക", + "search_page_your_activity": "നിങ്ങളുടെ പ്രവർത്തനം", + "search_page_your_map": "നിങ്ങളുടെ മാപ്പ്", + "search_people": "ആളുകളെ തിരയുക", + "search_places": "സ്ഥലങ്ങൾ തിരയുക", + "search_rating": "റേറ്റിംഗ് അനുസരിച്ച് തിരയുക...", + "search_result_page_new_search_hint": "പുതിയ തിരയൽ", + "search_settings": "തിരയൽ ക്രമീകരണങ്ങൾ", + "search_state": "സംസ്ഥാനം തിരയുക...", + "search_suggestion_list_smart_search_hint_1": "സ്മാർട്ട് സെർച്ച് ഡിഫോൾട്ടായി പ്രവർത്തനക്ഷമമാക്കിയിരിക്കുന്നു, മെറ്റാഡാറ്റയ്ക്കായി തിരയാൻ ഈ വാക്യഘടന ഉപയോഗിക്കുക ", + "search_suggestion_list_smart_search_hint_2": "m:നിങ്ങളുടെ-തിരയൽ-പദം", + "search_tags": "ടാഗുകൾക്കായി തിരയുക...", + "search_timezone": "സമയമേഖലയ്ക്കായി തിരയുക...", + "search_type": "തിരയൽ തരം", + "search_your_photos": "നിങ്ങളുടെ ഫോട്ടോകൾ തിരയുക", + "searching_locales": "ലൊക്കേലുകൾക്കായി തിരയുന്നു...", + "second": "സെക്കന്റ്", + "see_all_people": "എല്ലാ ആളുകളെയും കാണുക", + "select": "തിരഞ്ഞെടുക്കുക", + "select_album_cover": "ആൽബം കവർ തിരഞ്ഞെടുക്കുക", + "select_all": "എല്ലാം തിരഞ്ഞെടുക്കുക", + "select_all_duplicates": "എല്ലാ ഡ്യൂപ്ലിക്കേറ്റുകളും തിരഞ്ഞെടുക്കുക", + "select_all_in": "{group}-ലെ എല്ലാം തിരഞ്ഞെടുക്കുക", + "select_avatar_color": "അവതാർ നിറം തിരഞ്ഞെടുക്കുക", + "select_face": "മുഖം തിരഞ്ഞെടുക്കുക", + "select_featured_photo": "ഫീച്ചർ ചെയ്ത ഫോട്ടോ തിരഞ്ഞെടുക്കുക", + "select_from_computer": "കമ്പ്യൂട്ടറിൽ നിന്ന് തിരഞ്ഞെടുക്കുക", + "select_keep_all": "എല്ലാം സൂക്ഷിക്കാൻ തിരഞ്ഞെടുക്കുക", + "select_library_owner": "ലൈബ്രറി ഉടമയെ തിരഞ്ഞെടുക്കുക", + "select_new_face": "പുതിയ മുഖം തിരഞ്ഞെടുക്കുക", + "select_person_to_tag": "ടാഗ് ചെയ്യാൻ ഒരു വ്യക്തിയെ തിരഞ്ഞെടുക്കുക", + "select_photos": "ഫോട്ടോകൾ തിരഞ്ഞെടുക്കുക", + "select_trash_all": "എല്ലാം ട്രാഷ് ചെയ്യാൻ തിരഞ്ഞെടുക്കുക", + "select_user_for_sharing_page_err_album": "ആൽബം ഉണ്ടാക്കുന്നതിൽ പരാജയപ്പെട്ടു", + "selected": "തിരഞ്ഞെടുത്തു", + "selected_count": "{count, plural, other {# എണ്ണം തിരഞ്ഞെടുത്തു}}", + "selected_gps_coordinates": "തിരഞ്ഞെടുത്ത GPS കോർഡിനേറ്റുകൾ", + "send_message": "സന്ദേശം അയക്കുക", + "send_welcome_email": "സ്വാഗത ഇമെയിൽ അയക്കുക", + "server_endpoint": "സെർവർ എൻഡ്‌പോയിന്റ്", + "server_info_box_app_version": "ആപ്പ് പതിപ്പ്", + "server_info_box_server_url": "സെർവർ URL", + "server_offline": "സെർവർ ഓഫ്‌ലൈൻ", + "server_online": "സെർവർ ഓൺലൈൻ", + "server_privacy": "സെർവർ സ്വകാര്യത", + "server_stats": "സെർവർ സ്ഥിതിവിവരക്കണക്കുകൾ", + "server_version": "സെർവർ പതിപ്പ്", + "set": "സജ്ജമാക്കുക", + "set_as_album_cover": "ആൽബം കവറായി സജ്ജമാക്കുക", + "set_as_featured_photo": "ഫീച്ചർ ചെയ്ത ഫോട്ടോയായി സജ്ജമാക്കുക", + "set_as_profile_picture": "പ്രൊഫൈൽ ചിത്രമായി സജ്ജമാക്കുക", + "set_date_of_birth": "ജനനത്തീയതി സജ്ജമാക്കുക", + "set_profile_picture": "പ്രൊഫൈൽ ചിത്രം സജ്ജമാക്കുക", + "set_slideshow_to_fullscreen": "സ്ലൈഡ്‌ഷോ ഫുൾസ്ക്രീനായി സജ്ജമാക്കുക", + "set_stack_primary_asset": "പ്രധാന അസറ്റായി സജ്ജമാക്കുക", + "setting_image_viewer_help": "വിശദാംശ വ്യൂവർ ആദ്യം ചെറിയ തംബ്നെയിൽ ലോഡുചെയ്യുന്നു, തുടർന്ന് ഇടത്തരം പ്രിവ്യൂ (പ്രവർത്തനക്ഷമമാക്കിയിട്ടുണ്ടെങ്കിൽ), ഒടുവിൽ യഥാർത്ഥ ചിത്രം (പ്രവർത്തനക്ഷമമാക്കിയിട്ടുണ്ടെങ്കിൽ) ലോഡുചെയ്യുന്നു.", + "setting_image_viewer_original_subtitle": "യഥാർത്ഥ പൂർണ്ണ-റെസല്യൂഷൻ ചിത്രം (വലുത്!) ലോഡുചെയ്യാൻ പ്രവർത്തനക്ഷമമാക്കുക. ഡാറ്റ ഉപയോഗം (നെറ്റ്‌വർക്കും ഉപകരണ കാഷെയും) കുറയ്ക്കാൻ പ്രവർത്തനരഹിതമാക്കുക.", + "setting_image_viewer_original_title": "യഥാർത്ഥ ചിത്രം ലോഡുചെയ്യുക", + "setting_image_viewer_preview_subtitle": "ഇടത്തരം റെസല്യൂഷനുള്ള ചിത്രം ലോഡുചെയ്യാൻ പ്രവർത്തനക്ഷമമാക്കുക. ഒന്നുകിൽ യഥാർത്ഥ ചിത്രം നേരിട്ട് ലോഡുചെയ്യുന്നതിനോ അല്ലെങ്കിൽ തംബ്നെയിൽ മാത്രം ഉപയോഗിക്കുന്നതിനോ പ്രവർത്തനരഹിതമാക്കുക.", + "setting_image_viewer_preview_title": "പ്രിവ്യൂ ചിത്രം ലോഡുചെയ്യുക", + "setting_image_viewer_title": "ചിത്രങ്ങൾ", + "setting_languages_apply": "പ്രയോഗിക്കുക", + "setting_languages_subtitle": "ആപ്പിന്റെ ഭാഷ മാറ്റുക", + "setting_notifications_notify_failures_grace_period": "പശ്ചാത്തല ബാക്കപ്പ് പരാജയങ്ങൾ അറിയിക്കുക: {duration}", + "setting_notifications_notify_hours": "{count} മണിക്കൂർ", + "setting_notifications_notify_immediately": "ഉടനടി", + "setting_notifications_notify_minutes": "{count} മിനിറ്റ്", + "setting_notifications_notify_never": "ഒരിക്കലും", + "setting_notifications_notify_seconds": "{count} സെക്കൻഡ്", + "setting_notifications_single_progress_subtitle": "ഓരോ അസറ്റിന്റെയും വിശദമായ അപ്‌ലോഡ് പുരോഗതി വിവരം", + "setting_notifications_single_progress_title": "പശ്ചാത്തല ബാക്കപ്പിന്റെ വിശദമായ പുരോഗതി കാണിക്കുക", + "setting_notifications_subtitle": "നിങ്ങളുടെ അറിയിപ്പ് മുൻഗണനകൾ ക്രമീകരിക്കുക", + "setting_notifications_total_progress_subtitle": "മൊത്തത്തിലുള്ള അപ്‌ലോഡ് പുരോഗതി (ചെയ്തത്/ആകെ അസറ്റുകൾ)", + "setting_notifications_total_progress_title": "പശ്ചാത്തല ബാക്കപ്പിന്റെ മൊത്തം പുരോഗതി കാണിക്കുക", + "setting_video_viewer_looping_title": "ലൂപ്പിംഗ്", + "setting_video_viewer_original_video_subtitle": "സെർവറിൽ നിന്ന് ഒരു വീഡിയോ സ്ട്രീം ചെയ്യുമ്പോൾ, ഒരു ട്രാൻസ്‌കോഡ് ലഭ്യമാണെങ്കിലും യഥാർത്ഥ വീഡിയോ പ്ലേ ചെയ്യുക. ഇത് ബഫറിംഗിന് കാരണമായേക്കാം. പ്രാദേശികമായി ലഭ്യമായ വീഡിയോകൾ ഈ ക്രമീകരണം പരിഗണിക്കാതെ തന്നെ യഥാർത്ഥ ഗുണമേന്മയിൽ പ്ലേ ചെയ്യും.", + "setting_video_viewer_original_video_title": "യഥാർത്ഥ വീഡിയോ നിർബന്ധമാക്കുക", + "settings": "ക്രമീകരണങ്ങൾ", + "settings_require_restart": "ഈ ക്രമീകരണം പ്രയോഗിക്കാൻ ദയവായി Immich പുനരാരംഭിക്കുക", + "settings_saved": "ക്രമീകരണങ്ങൾ സേവ് ചെയ്തു", + "setup_pin_code": "ഒരു പിൻ കോഡ് സജ്ജീകരിക്കുക", + "share": "പങ്കിടുക", + "share_action_prompt": "{count} അസറ്റുകൾ പങ്കിട്ടു", + "share_add_photos": "ഫോട്ടോകൾ ചേർക്കുക", + "share_assets_selected": "{count} എണ്ണം തിരഞ്ഞെടുത്തു", + "share_dialog_preparing": "തയ്യാറാക്കുന്നു...", + "share_link": "ലിങ്ക് പങ്കിടുക", + "shared": "പങ്കിട്ടത്", + "shared_album_activities_input_disable": "അഭിപ്രായം പ്രവർത്തനരഹിതമാക്കി", + "shared_album_activity_remove_content": "ഈ പ്രവർത്തനം ഇല്ലാതാക്കണോ?", + "shared_album_activity_remove_title": "പ്രവർത്തനം ഇല്ലാതാക്കുക", + "shared_album_section_people_action_error": "ആൽബത്തിൽ നിന്ന് വിട്ടുപോകുന്നതിനോ/നീക്കംചെയ്യുന്നതിനോ പിശക്", + "shared_album_section_people_action_leave": "ആൽബത്തിൽ നിന്ന് ഉപയോക്താവിനെ നീക്കം ചെയ്യുക", + "shared_album_section_people_action_remove_user": "ആൽബത്തിൽ നിന്ന് ഉപയോക്താവിനെ നീക്കം ചെയ്യുക", + "shared_album_section_people_title": "ആളുകൾ", + "shared_by": "പങ്കിട്ടത്", + "shared_by_user": "{user} പങ്കിട്ടു", + "shared_by_you": "നിങ്ങൾ പങ്കിട്ടത്", + "shared_from_partner": "{partner}-ൽ നിന്നുള്ള ഫോട്ടോകൾ", + "shared_intent_upload_button_progress_text": "{current}/{total} അപ്‌ലോഡ് ചെയ്തു", + "shared_link_app_bar_title": "പങ്കിട്ട ലിങ്കുകൾ", + "shared_link_clipboard_copied_massage": "ക്ലിപ്പ്ബോർഡിലേക്ക് പകർത്തി", + "shared_link_clipboard_text": "ലിങ്ക്: {link}\nപാസ്‌വേഡ്: {password}", + "shared_link_create_error": "പങ്കിട്ട ലിങ്ക് ഉണ്ടാക്കുന്നതിൽ പിശക്", + "shared_link_custom_url_description": "ഒരു കസ്റ്റം URL ഉപയോഗിച്ച് ഈ പങ്കിട്ട ലിങ്ക് ആക്‌സസ് ചെയ്യുക", + "shared_link_edit_description_hint": "പങ്കിടലിന്റെ വിവരണം നൽകുക", + "shared_link_edit_expire_after_option_day": "1 ദിവസം", + "shared_link_edit_expire_after_option_days": "{count} ദിവസങ്ങൾ", + "shared_link_edit_expire_after_option_hour": "1 മണിക്കൂർ", + "shared_link_edit_expire_after_option_hours": "{count} മണിക്കൂറുകൾ", + "shared_link_edit_expire_after_option_minute": "1 മിനിറ്റ്", + "shared_link_edit_expire_after_option_minutes": "{count} മിനിറ്റുകൾ", + "shared_link_edit_expire_after_option_months": "{count} മാസങ്ങൾ", + "shared_link_edit_expire_after_option_year": "{count} വർഷം", + "shared_link_edit_password_hint": "പങ്കിടലിന്റെ പാസ്‌വേഡ് നൽകുക", + "shared_link_edit_submit_button": "ലിങ്ക് അപ്ഡേറ്റ് ചെയ്യുക", + "shared_link_error_server_url_fetch": "സെർവർ url ലഭ്യമാക്കാൻ കഴിയില്ല", + "shared_link_expires_day": "{count} ദിവസത്തിനുള്ളിൽ കാലഹരണപ്പെടും", + "shared_link_expires_days": "{count} ദിവസങ്ങൾക്കുള്ളിൽ കാലഹരണപ്പെടും", + "shared_link_expires_hour": "{count} മണിക്കൂറിനുള്ളിൽ കാലഹരണപ്പെടും", + "shared_link_expires_hours": "{count} മണിക്കൂറുകൾക്കുള്ളിൽ കാലഹരണപ്പെടും", + "shared_link_expires_minute": "{count} മിനിറ്റിനുള്ളിൽ കാലഹരണപ്പെടും", + "shared_link_expires_minutes": "{count} മിനിറ്റുകൾക്കുള്ളിൽ കാലഹരണപ്പെടും", + "shared_link_expires_never": "കാലഹരണപ്പെടില്ല ∞", + "shared_link_expires_second": "{count} സെക്കൻഡിനുള്ളിൽ കാലഹരണപ്പെടും", + "shared_link_expires_seconds": "{count} സെക്കൻഡുകൾക്കുള്ളിൽ കാലഹരണപ്പെടും", + "shared_link_individual_shared": "വ്യക്തിഗതമായി പങ്കിട്ടത്", + "shared_link_info_chip_metadata": "EXIF", + "shared_link_manage_links": "പങ്കിട്ട ലിങ്കുകൾ കൈകാര്യം ചെയ്യുക", + "shared_link_options": "പങ്കിട്ട ലിങ്ക് ഓപ്ഷനുകൾ", + "shared_link_password_description": "ഈ പങ്കിട്ട ലിങ്ക് ആക്‌സസ് ചെയ്യാൻ ഒരു പാസ്‌വേഡ് ആവശ്യമാണ്", + "shared_links": "പങ്കിട്ട ലിങ്കുകൾ", + "shared_links_description": "ഒരു ലിങ്ക് ഉപയോഗിച്ച് ഫോട്ടോകളും വീഡിയോകളും പങ്കിടുക", + "shared_photos_and_videos_count": "{assetCount, plural, other {പങ്കിട്ട # ഫോട്ടോകളും വീഡിയോകളും.}}", + "shared_with_me": "എനിക്കായി പങ്കിട്ടത്", + "shared_with_partner": "{partner}-മായി പങ്കിട്ടു", + "sharing": "പങ്കിടൽ", + "sharing_enter_password": "ഈ പേജ് കാണുന്നതിന് ദയവായി പാസ്‌വേഡ് നൽകുക.", + "sharing_page_album": "പങ്കിട്ട ആൽബങ്ങൾ", + "sharing_page_description": "നിങ്ങളുടെ നെറ്റ്‌വർക്കിലെ ആളുകളുമായി ഫോട്ടോകളും വീഡിയോകളും പങ്കിടാൻ പങ്കിട്ട ആൽബങ്ങൾ ഉണ്ടാക്കുക.", + "sharing_page_empty_list": "ശൂന്യമായ ലിസ്റ്റ്", + "sharing_sidebar_description": "സൈഡ്‌ബാറിൽ 'പങ്കിടൽ' എന്നതിലേക്ക് ഒരു ലിങ്ക് പ്രദർശിപ്പിക്കുക", + "sharing_silver_appbar_create_shared_album": "പുതിയ പങ്കിട്ട ആൽബം", + "sharing_silver_appbar_share_partner": "പങ്കാളിയുമായി പങ്കിടുക", + "shift_to_permanent_delete": "അസറ്റ് ശാശ്വതമായി ഇല്ലാതാക്കാൻ ⇧ അമർത്തുക", + "show_album_options": "ആൽബം ഓപ്ഷനുകൾ കാണിക്കുക", + "show_albums": "ആൽബങ്ങൾ കാണിക്കുക", + "show_all_people": "എല്ലാ ആളുകളെയും കാണിക്കുക", + "show_and_hide_people": "ആളുകളെ കാണിക്കുകയും മറയ്ക്കുകയും ചെയ്യുക", + "show_file_location": "ഫയലിന്റെ സ്ഥാനം കാണിക്കുക", + "show_gallery": "ഗാലറി കാണിക്കുക", + "show_hidden_people": "മറച്ച ആളുകളെ കാണിക്കുക", + "show_in_timeline": "ടൈംലൈനിൽ കാണിക്കുക", + "show_in_timeline_setting_description": "ഈ ഉപയോക്താവിൽ നിന്നുള്ള ഫോട്ടോകളും വീഡിയോകളും നിങ്ങളുടെ ടൈംലൈനിൽ കാണിക്കുക", + "show_keyboard_shortcuts": "കീബോർഡ് കുറുക്കുവഴികൾ കാണിക്കുക", + "show_metadata": "മെറ്റാഡാറ്റ കാണിക്കുക", + "show_or_hide_info": "വിവരങ്ങൾ കാണിക്കുക അല്ലെങ്കിൽ മറയ്ക്കുക", + "show_password": "പാസ്‌വേഡ് കാണിക്കുക", + "show_person_options": "വ്യക്തിയുടെ ഓപ്ഷനുകൾ കാണിക്കുക", + "show_progress_bar": "പുരോഗതി ബാർ കാണിക്കുക", + "show_search_options": "തിരയൽ ഓപ്ഷനുകൾ കാണിക്കുക", + "show_shared_links": "പങ്കിട്ട ലിങ്കുകൾ കാണിക്കുക", + "show_slideshow_transition": "സ്ലൈഡ്‌ഷോ സംക്രമണം കാണിക്കുക", + "show_supporter_badge": "സഹായിയുടെ ബാഡ്ജ്", + "show_supporter_badge_description": "ഒരു സഹായിയുടെ ബാഡ്ജ് കാണിക്കുക", + "show_text_search_menu": "ടെക്സ്റ്റ് തിരയൽ മെനു കാണിക്കുക", + "shuffle": "ഷഫിൾ ചെയ്യുക", + "sidebar": "സൈഡ്‌ബാർ", + "sidebar_display_description": "സൈഡ്‌ബാറിലെ കാഴ്‌ചയിലേക്ക് ഒരു ലിങ്ക് പ്രദർശിപ്പിക്കുക", + "sign_out": "സൈൻ ഔട്ട്", + "sign_up": "സൈൻ അപ്പ് ചെയ്യുക", + "size": "വലിപ്പം", + "skip_to_content": "ഉള്ളടക്കത്തിലേക്ക് പോകുക", + "skip_to_folders": "ഫോൾഡറുകളിലേക്ക് പോകുക", + "skip_to_tags": "ടാഗുകളിലേക്ക് പോകുക", + "slideshow": "സ്ലൈഡ്‌ഷോ", + "slideshow_settings": "സ്ലൈഡ്‌ഷോ ക്രമീകരണങ്ങൾ", + "sort_albums_by": "ആൽബങ്ങളെ ഇതനുസരിച്ച് ക്രമീകരിക്കുക...", + "sort_created": "സൃഷ്ടിച്ച തീയതി", + "sort_items": "ഇനങ്ങളുടെ എണ്ണം", + "sort_modified": "മാറ്റം വരുത്തിയ തീയതി", + "sort_newest": "ഏറ്റവും പുതിയ ഫോട്ടോ", + "sort_oldest": "ഏറ്റവും പഴയ ഫോട്ടോ", + "sort_people_by_similarity": "സാമ്യം അനുസരിച്ച് ആളുകളെ ക്രമീകരിക്കുക", + "sort_recent": "ഏറ്റവും പുതിയ ഫോട്ടോ", + "sort_title": "ശീർഷകം", + "source": "ഉറവിടം", + "stack": "സ്റ്റാക്ക്", + "stack_action_prompt": "{count} എണ്ണം സ്റ്റാക്ക് ചെയ്തു", + "stack_duplicates": "ഡ്യൂപ്ലിക്കേറ്റുകൾ സ്റ്റാക്ക് ചെയ്യുക", + "stack_select_one_photo": "സ്റ്റാക്കിനായി ഒരു പ്രധാന ഫോട്ടോ തിരഞ്ഞെടുക്കുക", + "stack_selected_photos": "തിരഞ്ഞെടുത്ത ഫോട്ടോകൾ സ്റ്റാക്ക് ചെയ്യുക", + "stacked_assets_count": "{count, plural, one {# അസറ്റ്} other {# അസറ്റുകൾ}} സ്റ്റാക്ക് ചെയ്തു", + "stacktrace": "സ്റ്റാക്ക്ട്രേസ്", + "start": "ആരംഭിക്കുക", + "start_date": "ആരംഭ തീയതി", + "start_date_before_end_date": "ആരംഭ തീയതി അവസാന തീയതിക്ക് മുമ്പായിരിക്കണം", + "state": "സംസ്ഥാനം", + "status": "നില", + "stop_casting": "കാസ്റ്റിംഗ് നിർത്തുക", + "stop_motion_photo": "ചലിക്കുന്ന ഫോട്ടോ നിർത്തുക", + "stop_photo_sharing": "നിങ്ങളുടെ ഫോട്ടോകൾ പങ്കിടുന്നത് നിർത്തണോ?", + "stop_photo_sharing_description": "{partner}-ക്ക് ഇനി നിങ്ങളുടെ ഫോട്ടോകൾ ആക്‌സസ് ചെയ്യാൻ കഴിയില്ല.", + "stop_sharing_photos_with_user": "ഈ ഉപയോക്താവുമായി നിങ്ങളുടെ ഫോട്ടോകൾ പങ്കിടുന്നത് നിർത്തുക", + "storage": "സംഭരണ സ്ഥലം", + "storage_label": "സ്റ്റോറേജ് ലേബൽ", + "storage_quota": "സ്റ്റോറേജ് ക്വാട്ട", + "storage_usage": "{available}-ൽ {used} ഉപയോഗിച്ചു", + "submit": "സമർപ്പിക്കുക", + "success": "വിജയം", + "suggestions": "നിർദ്ദേശങ്ങൾ", + "sunrise_on_the_beach": "ബീച്ചിലെ സൂര്യോദയം", + "support": "പിന്തുണ", + "support_and_feedback": "പിന്തുണയും അഭിപ്രായവും", + "support_third_party_description": "നിങ്ങളുടെ Immich ഇൻസ്റ്റാളേഷൻ ഒരു മൂന്നാം കക്ഷി പാക്കേജ് ചെയ്തതാണ്. നിങ്ങൾ അനുഭവിക്കുന്ന പ്രശ്നങ്ങൾ ആ പാക്കേജ് കാരണമാകാം, അതിനാൽ താഴെയുള്ള ലിങ്കുകൾ ഉപയോഗിച്ച് ആദ്യം അവരുമായി പ്രശ്നങ്ങൾ ഉന്നയിക്കുക.", + "swap_merge_direction": "ലയിപ്പിക്കൽ ദിശ മാറ്റുക", + "sync": "സിങ്ക്", + "sync_albums": "ആൽബങ്ങൾ സിങ്ക് ചെയ്യുക", + "sync_albums_manual_subtitle": "അപ്‌ലോഡ് ചെയ്ത എല്ലാ വീഡിയോകളും ഫോട്ടോകളും തിരഞ്ഞെടുത്ത ബാക്കപ്പ് ആൽബങ്ങളിലേക്ക് സിങ്ക് ചെയ്യുക", + "sync_local": "പ്രാദേശികമായി സിങ്ക് ചെയ്യുക", + "sync_remote": "റിമോട്ടായി സിങ്ക് ചെയ്യുക", + "sync_status": "സിങ്ക് നില", + "sync_status_subtitle": "സിങ്ക് സിസ്റ്റം കാണുക, കൈകാര്യം ചെയ്യുക", + "sync_upload_album_setting_subtitle": "നിങ്ങളുടെ ഫോട്ടോകളും വീഡിയോകളും Immich-ലെ തിരഞ്ഞെടുത്ത ആൽബങ്ങളിലേക്ക് ഉണ്ടാക്കി അപ്‌ലോഡ് ചെയ്യുക", + "tag": "ടാഗ്", + "tag_assets": "അസറ്റുകൾ ടാഗ് ചെയ്യുക", + "tag_created": "{tag} എന്ന ടാഗ് ഉണ്ടാക്കി", + "tag_feature_description": "യുക്തിസഹമായ ടാഗ് വിഷയങ്ങൾ അനുസരിച്ച് ഗ്രൂപ്പ് ചെയ്ത ഫോട്ടോകളും വീഡിയോകളും ബ്രൗസുചെയ്യുന്നു", + "tag_not_found_question": "ഒരു ടാഗ് കണ്ടെത്താൻ കഴിയുന്നില്ലേ? ഒരു പുതിയ ടാഗ് ഉണ്ടാക്കുക.", + "tag_people": "ആളുകളെ ടാഗ് ചെയ്യുക", + "tag_updated": "{tag} എന്ന ടാഗ് അപ്ഡേറ്റ് ചെയ്തു", + "tagged_assets": "{count, plural, one {# അസറ്റ്} other {# അസറ്റുകൾ}} ടാഗ് ചെയ്തു", + "tags": "ടാഗുകൾ", + "tap_to_run_job": "ജോലി പ്രവർത്തിപ്പിക്കാൻ ടാപ്പുചെയ്യുക", + "template": "ടെംപ്ലേറ്റ്", + "theme": "തീം", + "theme_selection": "തീം തിരഞ്ഞെടുക്കൽ", + "theme_selection_description": "നിങ്ങളുടെ ബ്രൗസറിന്റെ സിസ്റ്റം മുൻഗണന അനുസരിച്ച് തീം ലൈറ്റ് അല്ലെങ്കിൽ ഡാർക്ക് ആയി യാന്ത്രികമായി സജ്ജമാക്കുക", + "theme_setting_asset_list_storage_indicator_title": "അസറ്റ് ടൈലുകളിൽ സ്റ്റോറേജ് ഇൻഡിക്കേറ്റർ കാണിക്കുക", + "theme_setting_asset_list_tiles_per_row_title": "ഓരോ വരിയിലും അസറ്റുകളുടെ എണ്ണം ({count})", + "theme_setting_colorful_interface_subtitle": "പശ്ചാത്തല പ്രതലങ്ങളിൽ പ്രധാന നിറം പ്രയോഗിക്കുക.", + "theme_setting_colorful_interface_title": "വർണ്ണാഭമായ ഇന്റർഫേസ്", + "theme_setting_image_viewer_quality_subtitle": "വിശദാംശ ഇമേജ് വ്യൂവറിന്റെ ഗുണമേന്മ ക്രമീകരിക്കുക", + "theme_setting_image_viewer_quality_title": "ഇമേജ് വ്യൂവർ ഗുണമേന്മ", + "theme_setting_primary_color_subtitle": "പ്രധാന പ്രവർത്തനങ്ങൾക്കും ആക്‌സന്റുകൾക്കുമായി ഒരു നിറം തിരഞ്ഞെടുക്കുക.", + "theme_setting_primary_color_title": "പ്രധാന നിറം", + "theme_setting_system_primary_color_title": "സിസ്റ്റം നിറം ഉപയോഗിക്കുക", + "theme_setting_system_theme_switch": "യാന്ത്രികം (സിസ്റ്റം ക്രമീകരണം പിന്തുടരുക)", + "theme_setting_theme_subtitle": "ആപ്പിന്റെ തീം ക്രമീകരണം തിരഞ്ഞെടുക്കുക", + "theme_setting_three_stage_loading_subtitle": "മൂന്ന്-ഘട്ട ലോഡിംഗ് പ്രകടനം വർദ്ധിപ്പിച്ചേക്കാം, പക്ഷേ നെറ്റ്‌വർക്ക് ലോഡ് ഗണ്യമായി വർദ്ധിപ്പിക്കുന്നു", + "theme_setting_three_stage_loading_title": "മൂന്ന്-ഘട്ട ലോഡിംഗ് പ്രവർത്തനക്ഷമമാക്കുക", + "they_will_be_merged_together": "അവയെ ഒരുമിച്ച് ലയിപ്പിക്കും", + "third_party_resources": "മൂന്നാം കക്ഷി ഉറവിടങ്ങൾ", + "time_based_memories": "സമയം അടിസ്ഥാനമാക്കിയുള്ള ഓർമ്മകൾ", + "timeline": "ടൈംലൈൻ", + "timezone": "സമയമേഖല", + "to_archive": "ആർക്കൈവ് ചെയ്യുക", + "to_change_password": "പാസ്‌വേഡ് മാറ്റുക", + "to_favorite": "പ്രിയപ്പെട്ടതാക്കുക", + "to_login": "ലോഗിൻ", + "to_multi_select": "ഒന്നിലധികം തിരഞ്ഞെടുക്കാൻ", + "to_parent": "പാരന്റിലേക്ക് പോകുക", + "to_select": "തിരഞ്ഞെടുക്കാൻ", + "to_trash": "ട്രാഷ് ചെയ്യുക", + "toggle_settings": "ക്രമീകരണങ്ങൾ ടോഗിൾ ചെയ്യുക", + "total": "ആകെ", + "total_usage": "ആകെ ഉപയോഗം", + "trash": "ട്രാഷ്", + "trash_action_prompt": "{count} എണ്ണം ട്രാഷിലേക്ക് മാറ്റി", + "trash_all": "എല്ലാം ട്രാഷ് ചെയ്യുക", + "trash_count": "ട്രാഷ് ({count, number})", + "trash_delete_asset": "അസറ്റ് ട്രാഷ് ചെയ്യുക/ഇല്ലാതാക്കുക", + "trash_emptied": "ട്രാഷ് ശൂന്യമാക്കി", + "trash_no_results_message": "ട്രാഷ് ചെയ്ത ഫോട്ടോകളും വീഡിയോകളും ഇവിടെ കാണിക്കും.", + "trash_page_delete_all": "എല്ലാം ഇല്ലാതാക്കുക", + "trash_page_empty_trash_dialog_content": "നിങ്ങളുടെ ട്രാഷ് ചെയ്ത അസറ്റുകൾ ശൂന്യമാക്കണോ? ഈ ഇനങ്ങൾ Immich-ൽ നിന്ന് ശാശ്വതമായി നീക്കം ചെയ്യപ്പെടും", + "trash_page_info": "ട്രാഷ് ചെയ്ത ഇനങ്ങൾ {days} ദിവസങ്ങൾക്ക് ശേഷം ശാശ്വതമായി ഇല്ലാതാക്കപ്പെടും", + "trash_page_no_assets": "ട്രാഷ് ചെയ്ത അസറ്റുകളൊന്നുമില്ല", + "trash_page_restore_all": "എല്ലാം പുനഃസ്ഥാപിക്കുക", + "trash_page_select_assets_btn": "അസറ്റുകൾ തിരഞ്ഞെടുക്കുക", + "trash_page_title": "ട്രാഷ് ({count})", + "trashed_items_will_be_permanently_deleted_after": "ട്രാഷ് ചെയ്ത ഇനങ്ങൾ {days, plural, one {# ദിവസത്തിന്} other {# ദിവസങ്ങൾക്ക്}} ശേഷം ശാശ്വതമായി ഇല്ലാതാക്കപ്പെടും.", + "troubleshoot": "ട്രബിൾഷൂട്ട്", + "type": "തരം", + "unable_to_change_pin_code": "പിൻ കോഡ് മാറ്റാൻ കഴിയില്ല", + "unable_to_setup_pin_code": "പിൻ കോഡ് സജ്ജീകരിക്കാൻ കഴിയില്ല", + "unarchive": "അൺആർക്കൈവ് ചെയ്യുക", + "unarchive_action_prompt": "{count} എണ്ണം ആർക്കൈവിൽ നിന്ന് നീക്കം ചെയ്തു", + "unarchived_count": "{count, plural, other {അൺആർക്കൈവ് ചെയ്തവ #}}", + "undo": "തിരികെചെയ്യുക", + "unfavorite": "പ്രിയപ്പെട്ടതല്ലാതാക്കുക", + "unfavorite_action_prompt": "{count} എണ്ണം പ്രിയപ്പെട്ടവയിൽ നിന്ന് നീക്കം ചെയ്തു", + "unhide_person": "വ്യക്തിയെ മറവിൽനിന്ന് മാറ്റുക", + "unknown": "അജ്ഞാതം", + "unknown_country": "അജ്ഞാത രാജ്യം", + "unknown_year": "അജ്ഞാത വർഷം", + "unlimited": "പരിധിയില്ലാത്തത്", + "unlink_motion_video": "ചലിക്കുന്ന വീഡിയോ അൺലിങ്ക് ചെയ്യുക", + "unlink_oauth": "OAuth അൺലിങ്ക് ചെയ്യുക", + "unlinked_oauth_account": "അൺലിങ്ക് ചെയ്ത OAuth അക്കൗണ്ട്", + "unmute_memories": "ഓർമ്മകൾ ശബ്ദമുള്ളതാക്കുക", + "unnamed_album": "പേരില്ലാത്ത ആൽബം", + "unnamed_album_delete_confirmation": "ഈ ആൽബം ഇല്ലാതാക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", + "unnamed_share": "പേരില്ലാത്ത പങ്കിടൽ", + "unsaved_change": "സേവ് ചെയ്യാത്ത മാറ്റം", + "unselect_all": "എല്ലാം തിരഞ്ഞെടുത്തത് മാറ്റുക", + "unselect_all_duplicates": "എല്ലാ ഡ്യൂപ്ലിക്കേറ്റുകളും തിരഞ്ഞെടുത്തത് മാറ്റുക", + "unselect_all_in": "{group}-ലെ എല്ലാം തിരഞ്ഞെടുത്തത് മാറ്റുക", + "unstack": "അൺ-സ്റ്റാക്ക് ചെയ്യുക", + "unstack_action_prompt": "{count} എണ്ണം അൺ-സ്റ്റാക്ക് ചെയ്തു", + "unstacked_assets_count": "{count, plural, one {# അസറ്റ്} other {# അസറ്റുകൾ}} അൺ-സ്റ്റാക്ക് ചെയ്തു", + "untagged": "ടാഗ് ചെയ്തിട്ടില്ല", + "up_next": "അടുത്തത്", + "update_location_action_prompt": "തിരഞ്ഞെടുത്ത {count} അസറ്റുകളുടെ സ്ഥാനം ഇതുപയോഗിച്ച് അപ്ഡേറ്റ് ചെയ്യുക:", + "updated_at": "അപ്ഡേറ്റ് ചെയ്തത്", + "updated_password": "പാസ്‌വേഡ് അപ്ഡേറ്റ് ചെയ്തു", + "upload": "അപ്‌ലോഡ്", + "upload_action_prompt": "{count} എണ്ണം അപ്‌ലോഡിനായി ക്യൂവിൽ ചേർത്തു", + "upload_concurrency": "അപ്‌ലോഡ് കോൺകറൻസി", + "upload_details": "അപ്‌ലോഡ് വിശദാംശങ്ങൾ", + "upload_dialog_info": "തിരഞ്ഞെടുത്ത അസറ്റ്(കൾ) സെർവറിലേക്ക് ബാക്കപ്പ് ചെയ്യണോ?", + "upload_dialog_title": "അസറ്റ് അപ്‌ലോഡ് ചെയ്യുക", + "upload_errors": "അപ്‌ലോഡ് {count, plural, one {# പിശകോടെ} other {# പിശകുകളോടെ}} പൂർത്തിയായി, പുതിയ അപ്‌ലോഡ് അസറ്റുകൾ കാണുന്നതിന് പേജ് പുതുക്കുക.", + "upload_finished": "അപ്‌ലോഡ് കഴിഞ്ഞു", + "upload_progress": "ശേഷിക്കുന്നത് {remaining, number} - പ്രോസസ്സ് ചെയ്തത് {processed, number}/{total, number}", + "upload_skipped_duplicates": "{count, plural, one {# ഡ്യൂപ്ലിക്കേറ്റ് അസറ്റ്} other {# ഡ്യൂപ്ലിക്കേറ്റ് അസറ്റുകൾ}} ഒഴിവാക്കി", + "upload_status_duplicates": "ഡ്യൂപ്ലിക്കേറ്റുകൾ", + "upload_status_errors": "പിശകുകൾ", + "upload_status_uploaded": "അപ്‌ലോഡ് ചെയ്തു", + "upload_success": "അപ്‌ലോഡ് വിജയിച്ചു, പുതിയ അപ്‌ലോഡ് അസറ്റുകൾ കാണുന്നതിന് പേജ് പുതുക്കുക.", + "upload_to_immich": "Immich-ലേക്ക് അപ്‌ലോഡ് ചെയ്യുക ({count})", + "uploading": "അപ്‌ലോഡ് ചെയ്യുന്നു", + "uploading_media": "മീഡിയ അപ്‌ലോഡ് ചെയ്യുന്നു", + "url": "യുആർഎൽ", + "usage": "ഉപയോഗം", + "use_biometric": "ബയോമെട്രിക് ഉപയോഗിക്കുക", + "use_current_connection": "നിലവിലെ കണക്ഷൻ ഉപയോഗിക്കുക", + "use_custom_date_range": "പകരം കസ്റ്റം തീയതി പരിധി ഉപയോഗിക്കുക", + "user": "ഉപയോക്താവ്", + "user_has_been_deleted": "ഈ ഉപയോക്താവിനെ ഇല്ലാതാക്കി.", + "user_id": "ഉപയോക്തൃ ഐഡി", + "user_liked": "{user} {type, select, photo {ഈ ഫോട്ടോ} video {ഈ വീഡിയോ} asset {ഈ അസറ്റ്} other {ഇത്}} ഇഷ്ടപ്പെട്ടു", + "user_pin_code_settings": "പിൻ കോഡ്", + "user_pin_code_settings_description": "നിങ്ങളുടെ പിൻ കോഡ് കൈകാര്യം ചെയ്യുക", + "user_privacy": "ഉപയോക്തൃ സ്വകാര്യത", + "user_purchase_settings": "വാങ്ങൽ", + "user_purchase_settings_description": "നിങ്ങളുടെ വാങ്ങൽ കൈകാര്യം ചെയ്യുക", + "user_role_set": "{user}-നെ {role} ആയി സജ്ജമാക്കി", + "user_usage_detail": "ഉപയോക്തൃ ഉപയോഗ വിശദാംശം", + "user_usage_stats": "അക്കൗണ്ട് ഉപയോഗ സ്ഥിതിവിവരക്കണക്കുകൾ", + "user_usage_stats_description": "അക്കൗണ്ട് ഉപയോഗ സ്ഥിതിവിവരക്കണക്കുകൾ കാണുക", + "username": "ഉപയോക്തൃനാമം", + "users": "ഉപയോക്താക്കൾ", + "users_added_to_album_count": "{count, plural, one {# ഉപയോക്താവിനെ} other {# ഉപയോക്താക്കളെ}} ആൽബത്തിലേക്ക് ചേർത്തു", + "utilities": "യൂട്ടിലിറ്റികൾ", + "validate": "സാധൂകരിക്കുക", + "validate_endpoint_error": "ദയവായി സാധുവായ ഒരു URL നൽകുക", + "variables": "വേരിയബിളുകൾ", + "version": "പതിപ്പ്", + "version_announcement_closing": "നിങ്ങളുടെ സുഹൃത്ത്, അലക്സ്", + "version_announcement_message": "നമസ്കാരം! Immich-ന്റെ ഒരു പുതിയ പതിപ്പ് ലഭ്യമാണ്. നിങ്ങളുടെ സജ്ജീകരണം ഏറ്റവും പുതിയതാണെന്ന് ഉറപ്പാക്കാൻ ദയവായി റിലീസ് നോട്ടുകൾ വായിക്കാൻ കുറച്ച് സമയമെടുക്കുക. ഇത് തെറ്റായ കോൺഫിഗറേഷനുകൾ ഒഴിവാക്കാൻ സഹായിക്കും, പ്രത്യേകിച്ചും നിങ്ങൾ വാച്ച്ടവർ അല്ലെങ്കിൽ നിങ്ങളുടെ Immich ഇൻസ്റ്റൻസ് യാന്ത്രികമായി അപ്ഡേറ്റ് ചെയ്യുന്ന ഏതെങ്കിലും സംവിധാനം ഉപയോഗിക്കുന്നുണ്ടെങ്കിൽ.", + "version_history": "പതിപ്പ് ചരിത്രം", + "version_history_item": "{date}-ന് {version} ഇൻസ്റ്റാൾ ചെയ്തു", + "video": "വീഡിയോ", + "video_hover_setting": "ഹോവർ ചെയ്യുമ്പോൾ വീഡിയോ തംബ്നെയിൽ പ്ലേ ചെയ്യുക", + "video_hover_setting_description": "മൗസ് ഒരു ഇനത്തിന് മുകളിലൂടെ ഹോവർ ചെയ്യുമ്പോൾ വീഡിയോ തംബ്നെയിൽ പ്ലേ ചെയ്യുക. പ്രവർത്തനരഹിതമാക്കിയാലും, പ്ലേ ഐക്കണിന് മുകളിലൂടെ ഹോവർ ചെയ്ത് പ്ലേബാക്ക് ആരംഭിക്കാൻ കഴിയും.", + "videos": "വീഡിയോകൾ", + "videos_count": "{count, plural, one {# വീഡിയോ} other {# വീഡിയോകൾ}}", + "view": "കാണുക", + "view_album": "ആൽബം കാണുക", + "view_all": "എല്ലാം കാണുക", + "view_all_users": "എല്ലാ ഉപയോക്താക്കളെയും കാണുക", + "view_details": "വിശദാംശങ്ങൾ കാണുക", + "view_in_timeline": "ടൈംലൈനിൽ കാണുക", + "view_link": "ലിങ്ക് കാണുക", + "view_links": "ലിങ്കുകൾ കാണുക", + "view_name": "കാണുക", + "view_next_asset": "അടുത്ത അസറ്റ് കാണുക", + "view_previous_asset": "മുമ്പത്തെ അസറ്റ് കാണുക", + "view_qr_code": "QR കോഡ് കാണുക", + "view_similar_photos": "സമാനമായ ഫോട്ടോകൾ കാണുക", + "view_stack": "സ്റ്റാക്ക് കാണുക", + "view_user": "ഉപയോക്താവിനെ കാണുക", + "viewer_remove_from_stack": "സ്റ്റാക്കിൽ നിന്ന് നീക്കം ചെയ്യുക", + "viewer_stack_use_as_main_asset": "പ്രധാന അസറ്റായി ഉപയോഗിക്കുക", + "viewer_unstack": "അൺ-സ്റ്റാക്ക് ചെയ്യുക", + "visibility_changed": "{count, plural, one {# വ്യക്തിയുടെ} other {# ആളുകളുടെ}} ദൃശ്യത മാറ്റി", "waiting": "കാത്തിരിക്കുന്നു", "warning": "മുന്നറിയിപ്പ്", "week": "ആഴ്ച", "welcome": "സ്വാഗതം", + "welcome_to_immich": "Immich-ലേക്ക് സ്വാഗതം", + "wifi_name": "വൈ-ഫൈയുടെ പേര്", + "wrong_pin_code": "തെറ്റായ പിൻ കോഡ്", "year": "വർഷം", - "yes": "അതെ" + "years_ago": "{years, plural, one {# വർഷം} other {# വർഷങ്ങൾ}} മുമ്പ്", + "yes": "അതെ", + "you_dont_have_any_shared_links": "നിങ്ങൾക്ക് പങ്കിട്ട ലിങ്കുകളൊന്നുമില്ല", + "your_wifi_name": "നിങ്ങളുടെ വൈ-ഫൈയുടെ പേര്", + "zoom_image": "ചിത്രം വലുതാക്കുക", + "zoom_to_bounds": "പരിധികളിലേക്ക് സൂം ചെയ്യുക" } diff --git a/i18n/mr.json b/i18n/mr.json index dbc2e3d114..54cac4b0dc 100644 --- a/i18n/mr.json +++ b/i18n/mr.json @@ -28,8 +28,10 @@ "add_to_album": "संग्रहात टाका", "add_to_album_bottom_sheet_added": "{album} मध्ये जोडले गेले", "add_to_album_bottom_sheet_already_exists": "आधीच {album} मध्ये आहे", - "add_to_album_toggle": "अल्बमसाठी निवड टॉगल करा", + "add_to_album_bottom_sheet_some_local_assets": "काही स्थानिक माध्यमे अल्बममध्ये जोडणे शक्य झाले नाही", + "add_to_album_toggle": "{album} साठी निवड बदला", "add_to_albums": "अल्बममध्ये जोडा", + "add_to_albums_count": "अल्बमांमध्ये जोडा ({count})", "add_to_shared_album": "सामायिक संग्रहात टाका", "add_url": "URL प्रविष्ट करा", "added_to_archive": "संग्रहित केले", @@ -357,6 +359,9 @@ "trash_number_of_days_description": "कायमस्वरीत्या काढून टाकण्यापूर्वी ट्रॅशमध्ये सामग्री किती दिवस ठेवायची ते क्रम", "trash_settings": "ट्रॅश सेटिंग्ज", "trash_settings_description": "ट्रॅश सेटिंग्ज व्यवस्थापित करा", + "unlink_all_oauth_accounts": "सर्व OAuth खात्यांची जोडणी तोडा", + "unlink_all_oauth_accounts_description": "नव्या सेवा-प्रदात्याकडे स्थलांतर करण्यापूर्वी सर्व OAuth खात्यांची जोडणी तोडायला विसरू नका.", + "unlink_all_oauth_accounts_prompt": "तुम्ही खरोखर सर्व OAuth खात्यांची जोडणी तोडू इच्छिता का? यामुळे प्रत्येक वापरकर्त्याचा OAuth ID रीसेट होईल आणि ही कृती पूर्वस्थितीत आणता येणार नाही.", "user_cleanup_job": "वापरकर्ता स्वच्छता", "user_delete_delay": "{user} यांचे खाते आणि मालमत्ता कायमची हटविण्यासाठी {delay, plural, one {# दिवस} other {# दिवस}} नंतर शेड्यूल केली जातील.", "user_delete_delay_settings": "हटविण्याची विलंबीत कालावधी", @@ -390,6 +395,8 @@ "advanced_settings_prefer_remote_title": "रिमोट प्रतिमा पसंत करा", "advanced_settings_proxy_headers_subtitle": "प्रत्येक नेटवर्क विनंतीसोबत Immich पाठवावयाचे प्रॉक्सी हेडर येथे परिभाषित करा", "advanced_settings_proxy_headers_title": "प्रॉक्सी हेडर", + "advanced_settings_readonly_mode_subtitle": "या मोडमध्ये फोटो फक्त पाहता येतात - अनेक फोटो निवडणे, शेअर करणे, कास्ट करणे आणि हटवणे अशा क्रिया निष्क्रिय राहतात. मुख्य स्क्रीनवरील वापरकर्ता अवतारातून हा मोड चालू किंवा बंद करा", + "advanced_settings_readonly_mode_title": "फक्त पाहण्याचा मोड", "advanced_settings_self_signed_ssl_subtitle": "सर्व्हर एंडपॉइंटसाठी SSL प्रमाणपत्र सत्यापन वगळते. स्वाक्षरीत प्रमाणपत्रांसाठी आवश्यक.", "advanced_settings_self_signed_ssl_title": "स्वतः स्वाक्षरीत SSL प्रमाणपत्रांना परवानगी द्या", "advanced_settings_sync_remote_deletions_subtitle": "वेबवर ही क्रिया केली गेल्यावर या उपकरणावर असलेले अॅसेट आपोआप हटवा किंवा पुनर्संचयित करा", @@ -417,6 +424,7 @@ "album_remove_user_confirmation": "आपण निश्चितच वापरकर्ता {user} काढून टाकणार आहात का?", "album_search_not_found": "तुमच्या शोधाशी जुळणारे कोणतेही अल्बम आढळले नाहीत", "album_share_no_users": "असा दिसते की हा अल्बम तुम्ही सर्व वापरकर्त्यांसोबत शेअर केला आहे किंवा शेअर करण्यासाठी कुठलाही वापरकर्ता उपलब्ध नाही.", + "album_summary": "अल्बम सारांश", "album_updated": "अल्बम अद्यतनित", "album_updated_setting_description": "शेअर केलेल्या अल्बममध्ये नवीन फाईल्स आल्यास ईमेल सूचनार्थ प्राप्त करा", "album_user_left": "सोडले: {album}", @@ -455,6 +463,7 @@ "app_bar_signout_dialog_title": "साइन आउट", "app_settings": "अ‍ॅप सेटिंग्ज", "appears_in": "दिसते (कुठे दिसते)", + "apply_count": "लागू करा ({count, number})", "archive": "आर्काइव्ह", "archive_action_prompt": "{count} आर्काइव्हमध्ये जोडले", "archive_or_unarchive_photo": "फोटो आर्काइव्ह करा किंवा अनआर्काइव्ह करा", @@ -487,6 +496,8 @@ "asset_restored_successfully": "साधन यशस्वीपणे पुनर्संचयित केले गेले", "asset_skipped": "वगळले", "asset_skipped_in_trash": "ट्रॅशमध्ये", + "asset_trashed": "मीडिया घटक कचरापेटीत हलवला", + "asset_troubleshoot": "मीडिया घटक समस्यानिवारण", "asset_uploaded": "अपलोड झाले", "asset_uploading": "अपलोड करत आहे…", "asset_viewer_settings_subtitle": "आपल्या गॅलरी व्ह्यूअरच्या सेटिंग्ज व्यवस्थापित करा", @@ -494,7 +505,9 @@ "assets": "साधने", "assets_added_count": "{count, plural, one {# साधन जोडले} other {# साधने जोडले}}", "assets_added_to_album_count": "{count, plural, one {# साधन अल्बममध्ये जोडले} other {# साधने अल्बममध्ये जोडले}}", + "assets_added_to_albums_count": "{albumTotal, plural, one {# अल्बममध्ये} other {# अल्बममध्ये}} {assetTotal, plural, one {# मीडिया घटक} other {# मीडिया घटक}} जोडले", "assets_cannot_be_added_to_album_count": "{count, plural, one {# साधन अल्बममध्ये जोडता येणार नाही} other {# साधने अल्बममध्ये जोडता येणार नाहीत}}", + "assets_cannot_be_added_to_albums": "{count, plural, one {# मीडिया घटक कोणत्याही अल्बममध्ये जोडता येत नाही} other {# मीडिया घटक कोणत्याही अल्बममध्ये जोडता येत नाहीत}}", "assets_count": "{count, plural, one {# साधन} other {# साधने}}", "assets_deleted_permanently": "{count} साधन(े) कायमचे हटविले", "assets_deleted_permanently_from_server": "Immich सर्व्हरवरून {count} साधन(े) कायमचे हटविले", @@ -502,6 +515,20 @@ "assets_downloaded_successfully": "{count, plural, one {एक फाईल यशस्वीरित्या डाउनलोड झाली} other {# फाईल्स यशस्वीरित्या डाउनलोड झाल्या}}", "assets_moved_to_trash_count": "{count, plural, one {एक फाईल ट्रॅशमध्ये हलवली} other {# फाईल्स ट्रॅशमध्ये हलवल्या}}", "assets_permanently_deleted_count": "{count, plural, one {एक फाईल कायमस्वरूपी हटवली} other {# फाईल्स कायमस्वरूपी हटवल्या}}", + "assets_removed_count": "{count, plural, one {# मीडिया घटक काढून टाकला} other {# मीडिया घटक काढून टाकले}}", + "assets_removed_permanently_from_device": "{count} मीडिया घटक तुमच्या डिव्हाइसवरून कायमचे काढले गेले", + "assets_restore_confirmation": "कचरापेटीतले सर्व मीडिया घटक पुनर्संचयित करायचे आहेत का? ही कृती पूर्ववत करता येणार नाही. लक्षात ठेवा - ऑफलाइन मीडिया घटक अशा प्रकारे पुनर्संचयित करता येत नाहीत.", + "assets_restored_count": "{count, plural, one {# मीडिया घटक पुनर्संचयित केला} other {# मीडिया घटक पुनर्संचयित केले}}", + "assets_restored_successfully": "{count} मीडिया घटक यशस्वीरित्या पुनर्संचयित झाले", + "assets_trashed": "{count} मीडिया घटक कचरापेटीत हलवले", + "assets_trashed_count": "{count, plural, one {# मीडिया घटक कचरापेटीत हलवला} other {# मीडिया घटक कचरापेटीत हलवले}}", + "assets_trashed_from_server": "{count} मीडिया घटक Immich सर्व्हरवरून कचरापेटीत हलवले", + "assets_were_part_of_album_count": "{count, plural, one {मीडिया घटक आधीच त्या अल्बमचा भाग होता} other {मीडिया घटक आधीच त्या अल्बमचा भाग होते}}", + "assets_were_part_of_albums_count": "{count, plural, one {मीडिया घटक आधीच अल्बम्सचा भाग होता} other {मीडिया घटक आधीच अल्बम्सचा भाग होते}}", + "authorized_devices": "अधिकृत उपकरणे", + "automatic_endpoint_switching_subtitle": "उपलब्ध असल्यास निश्‍चित Wi-Fi वर स्थानिकरित्या कनेक्ट करा आणि इतर ठिकाणी पर्यायी कनेक्शन वापरा", + "automatic_endpoint_switching_title": "स्वयंचलित URL स्विचिंग", + "autoplay_slideshow": "स्वयंचलित स्लाइडशो", "back": "मागे", "back_close_deselect": "मागे किंवा बंद करा / निवड रद्द करा", "background_location_permission": "बॅकग्राउंडमध्ये स्थान परवानगी द्या", @@ -562,6 +589,7 @@ "backup_controller_page_turn_on": "फोरग्राउंड बॅकअप चालू करा", "backup_controller_page_uploading_file_info": "फाईल माहिती अपलोड करत आहे", "backup_err_only_album": "अंतिम अल्बम काढता येणार नाही", + "backup_error_sync_failed": "समक्रमण अयशस्वी. बॅकअप प्रक्रिया करता येत नाही.", "backup_info_card_assets": "फाईल्स", "backup_manual_cancelled": "रद्द केले", "backup_manual_in_progress": "अपलोड आधीच चालू आहे. थोड्यावेळेनंतर पुन्हा प्रयत्न करा", @@ -629,6 +657,8 @@ "change_pin_code": "PIN कोड बदला", "change_your_password": "आपला संकेतशब्द बदला", "changed_visibility_successfully": "दृश्यमानता यशस्वीरित्या बदलली", + "charging": "चार्जिंग", + "charging_requirement_mobile_backup": "बॅकग्राउंड बॅकअपसाठी उपकरण चार्ज होत असणे आवश्यक आहे", "check_corrupt_asset_backup": "भ्रष्ट फाईल बॅकअप तपासा", "check_corrupt_asset_backup_button": "तपासणी करा", "check_corrupt_asset_backup_description": "फक्त Wi-Fi वर हा तपास चालवा आणि सर्व फाईल्स बॅकअप झाल्यावरच. प्रक्रिया काही मिनिटे लागू शकते.", @@ -660,7 +690,6 @@ "comments_and_likes": "टिप्पण्या & लाईक्स", "comments_are_disabled": "टिप्पण्या अक्षम आहेत", "common_create_new_album": "नवीन अल्बम तयार करा", - "common_server_error": "तुमचे नेटवर्क कनेक्शन तपासा. सर्व्हर पोहोचण्यायोग्य आहे का व अॅप/सर्व्हर आवृत्ती जुळत आहे का ते पाहा.", "completed": "पूर्ण झाले", "confirm": "पुष्टी करा", "confirm_admin_password": "ऍडमिन संकेतशब्द पुष्टी करा", @@ -715,6 +744,7 @@ "create_user": "वापरकर्ता तयार करा", "created": "तयार केले", "created_at": "निर्मिती तारीख", + "creating_linked_albums": "लिंक केलेले अल्बम तयार करत आहे...", "crop": "छाटणी करा", "curated_object_page_title": "गोष्टी", "current_device": "वर्तमान उपकरण", @@ -838,7 +868,6 @@ "edit_tag": "टॅग संपादित करा", "edit_title": "शीर्षक संपादित करा", "edit_user": "वापरकर्ता संपादित करा", - "edited": "संपादित झाले", "editor": "एडिटर", "editor_close_without_save_prompt": "बदल जतन होणार नाही", "editor_close_without_save_title": "एडिटर बंद करायचा का?", @@ -1032,6 +1061,7 @@ "filter_people": "लोक फिल्टर करा", "filter_places": "ठिकाणे फिल्टर करा", "find_them_fast": "नावाने पटकन शोधा", + "first": "प्रथम", "fix_incorrect_match": "चुकीची जुळणी दुरुस्त करा", "folder": "फोल्डर", "folder_not_found": "फोल्डर सापडला नाही", @@ -1042,18 +1072,71 @@ "gcast_enabled": "Google Cast", "gcast_enabled_description": "ही सुविधा चालण्यासाठी Google कडील बाह्य संसाधने लोड करते.", "general": "सामान्य", + "geolocation_instruction_location": "GPS निर्देशांक असलेल्या मीडिया घटकावर क्लिक करून त्याचे स्थान वापरा, किंवा थेट नकाशावरून स्थान निवडा", "get_help": "मदत घ्या", "get_wifiname_error": "Wi-Fi चे नाव मिळाले नाही. आवश्यक परवानग्या दिल्या आहेत आणि Wi-Fi नेटवर्कशी जोडले आहात याची खात्री करा", "getting_started": "सुरुवात करा", "go_back": "मागे जा", "go_to_folder": "फोल्डरकडे जा", "go_to_search": "शोधाकडे जा", + "gps": "जीपीएस", + "gps_missing": "GPS उपलब्ध नाही", "grant_permission": "परवानगी द्या", "group_albums_by": "अल्बम गटबद्ध करा: …", "group_country": "देशानुसार गट करा", "group_no": "गटबद्ध नाही", "group_owner": "मालकानुसार गट करा", "group_places_by": "स्थळे गटबद्ध करा: …", + "group_year": "वर्षानुसार गटबद्ध करा", + "haptic_feedback_switch": "हॅप्टिक फीडबॅक सक्षम करा", + "haptic_feedback_title": "हॅप्टिक फीडबॅक", + "has_quota": "कोटा आहे", + "hash_asset": "मीडिया घटकाचा हॅश तयार करा", + "hashed_assets": "हॅश केलेले मीडिया घटक", + "hashing": "हॅशिंग", + "header_settings_add_header_tip": "हेडर जोडा", + "header_settings_field_validator_msg": "मूल्य रिकामे असू शकत नाही", + "header_settings_header_name_input": "हेडरचे नाव", + "header_settings_header_value_input": "हेडरचे मूल्य", + "headers_settings_tile_title": "सानुकूल प्रॉक्सी हेडर्स", + "hi_user": "नमस्कार {name} ({email})", + "hide_all_people": "सर्व व्यक्ती लपवा", + "hide_gallery": "गॅलरी लपवा", + "hide_named_person": "व्यक्ती {name} लपवा", + "hide_password": "संकेतशब्द लपवा", + "hide_person": "व्यक्ती लपवा", + "hide_unnamed_people": "नाव नसलेल्या व्यक्ती लपवा", + "home_page_add_to_album_conflicts": "अल्बम {album} मध्ये {added} मीडिया घटक जोडले. {failed} मीडिया घटक आधीच त्या अल्बममध्ये आहेत.", + "home_page_add_to_album_err_local": "स्थानिक मीडिया घटक अजून अल्बममध्ये जोडता येत नाहीत, वगळत आहे", + "home_page_add_to_album_success": "अल्बम {album} मध्ये {added} मीडिया घटक जोडले.", + "home_page_album_err_partner": "भागीदारचे मीडिया घटक अजून अल्बममध्ये जोडता येत नाहीत, वगळत आहे", + "home_page_archive_err_local": "स्थानिक मीडिया घटक अजून संग्रहित करता येत नाहीत, वगळत आहे", + "home_page_archive_err_partner": "भागीदारचे मीडिया घटक संग्रहित करता येत नाहीत, वगळत आहे", + "home_page_building_timeline": "टाइमलाइन तयार करत आहे", + "home_page_delete_err_partner": "भागीदारचे मीडिया घटक हटवता येत नाहीत, वगळत आहे", + "home_page_delete_remote_err_local": "दूरस्थ हटवण्याच्या निवडीत स्थानिक मीडिया घटक आहेत, वगळत आहे", + "home_page_favorite_err_local": "स्थानिक मीडिया घटकांना अजून आवडीमध्ये जोडता येत नाही, वगळत आहे", + "home_page_favorite_err_partner": "भागीदारचे मीडिया घटक अजून आवडीमध्ये जोडता येत नाहीत, वगळत आहे", + "home_page_first_time_notice": "अ‍ॅप प्रथमच वापरत असाल तर टाइमलाइनमध्ये फोटो आणि व्हिडिओ भरण्यासाठी कृपया बॅकअप अल्बम निवडा", + "home_page_locked_error_local": "स्थानिक मीडिया घटक लॉक केलेल्या फोल्डरमध्ये हलवता येत नाहीत, वगळत आहे", + "home_page_locked_error_partner": "भागीदारचे मीडिया घटक लॉक केलेल्या फोल्डरमध्ये हलवता येत नाहीत, वगळत आहे", + "home_page_share_err_local": "स्थानिक मीडिया घटक लिंकद्वारे शेअर करता येत नाहीत, वगळत आहे", + "home_page_upload_err_limit": "एकावेळी कमाल 30 मीडिया घटकच अपलोड करता येतात, वगळत आहे", + "host": "होस्ट", + "hour": "तास", + "hours": "तास", + "id": "ID", + "idle": "निष्क्रिय", + "ignore_icloud_photos": "iCloud वरील फोटो दुर्लक्षित करा", + "ignore_icloud_photos_description": "iCloud वर साठवलेले फोटो Immich सर्व्हरवर अपलोड केले जाणार नाहीत", + "image": "फोटो", + "image_alt_text_date": "{isVideo, select, true {व्हिडिओ} other {फोटो}} {date} ला घेतले", + "image_alt_text_date_1_person": "{isVideo, select, true {व्हिडिओ} other {फोटो}} {person1} सोबत {date} ला घेतले", + "image_alt_text_date_2_people": "{isVideo, select, true {व्हिडिओ} other {फोटो}} {person1} आणि {person2} सोबत {date} ला घेतले", + "image_alt_text_date_3_people": "{isVideo, select, true {व्हिडिओ} other {फोटो}} {person1}, {person2} आणि {person3} सोबत {date} ला घेतले", + "image_alt_text_date_4_or_more_people": "{isVideo, select, true {व्हिडिओ} other {फोटो}} {person1}, {person2} आणि आणखी {additionalCount, number} जणांसोबत {date} ला घेतले", + "image_alt_text_date_place": "{isVideo, select, true {व्हिडिओ} other {फोटो}} {city}, {country} येथे {date} ला घेतले", + "image_alt_text_date_place_1_person": "{isVideo, select, true {व्हिडिओ} other {फोटो}} {city}, {country} येथे {person1} सोबत {date} ला घेतले", "notification_permission_dialog_content": "सूचना सक्षम करण्यासाठी सेटिंग्जमध्ये जा आणि अनुमती द्या.", "notification_permission_list_tile_content": "सूचना सक्षम करण्यासाठी परवानगी द्या.", "notification_permission_list_tile_enable_button": "सूचना सक्षम करा", @@ -1182,13 +1265,9 @@ "privacy": "गोपनीयता", "profile": "प्रोफाइल", "profile_drawer_app_logs": "लॉग्स", - "profile_drawer_client_out_of_date_major": "मोबाइल अ‍ॅप कालबाह्य आहे. कृपया नवीनतम मेजर आवृत्तीवर अद्यतन करा.", - "profile_drawer_client_out_of_date_minor": "मोबाइल अ‍ॅप कालबाह्य आहे. कृपया नवीनतम माइनर आवृत्तीवर अद्यतन करा.", "profile_drawer_client_server_up_to_date": "क्लायंट आणि सर्व्हर अद्ययावत आहेत", "profile_drawer_github": "गिटहब", "profile_drawer_readonly_mode": "फक्त-वाचन मोड सक्षम. बाहेर पडण्यासाठी वापरकर्त्याच्या अवतार आयकॉनवर लांब-प्रेस करा.", - "profile_drawer_server_out_of_date_major": "सर्व्हर कालबाह्य आहे. कृपया नवीनतम मेजर आवृत्तीवर अद्यतन करा.", - "profile_drawer_server_out_of_date_minor": "सर्व्हर कालबाह्य आहे. कृपया नवीनतम माइनर आवृत्तीवर अद्यतन करा.", "profile_image_of_user": "{user} ची प्रोफाइल प्रतिमा", "profile_picture_set": "प्रोफाइल चित्र सेट केले.", "public_album": "सार्वजनिक अल्बम", @@ -1764,5 +1843,6 @@ "year": "वर्ष", "yes": "हो", "you_dont_have_any_shared_links": "आपल्याकडे कोणतेही सामायिक दुवे नाहीत", - "zoom_image": "प्रतिमा झूम करा" + "zoom_image": "प्रतिमा झूम करा", + "zoom_to_bounds": "सीमेपर्यंत झूम करा" } diff --git a/i18n/ms.json b/i18n/ms.json index b87c547920..c72b1ff688 100644 --- a/i18n/ms.json +++ b/i18n/ms.json @@ -14,6 +14,7 @@ "add_a_location": "Tambah lokasi", "add_a_name": "Tambah nama", "add_a_title": "Tambah tajuk", + "add_birthday": "Tambah hari jadi", "add_endpoint": "Tambah titik akhir", "add_exclusion_pattern": "Tambahkan corak pengecualian", "add_import_path": "Tambahkan laluan import", @@ -27,6 +28,8 @@ "add_to_album": "Tambah ke album", "add_to_album_bottom_sheet_added": "Dimasukkan ke {album}", "add_to_album_bottom_sheet_already_exists": "Sudah ada di {album}", + "add_to_albums": "Tambah pada album", + "add_to_albums_count": "Tambah pada album ({count})", "add_to_shared_album": "Tambah ke album yang dikongsi", "add_url": "Tambah URL", "added_to_archive": "Tambah ke arkib", @@ -44,6 +47,9 @@ "backup_database": "Buat Salinan Pangkalan Data", "backup_database_enable_description": "Dayakan salinan pangkalan data", "backup_keep_last_amount": "Jumlah salinan pangkalan data sebelumnya untuk disimpan", + "backup_onboarding_1_description": "salinan luar tapak di awan atau di lokasi fizikal lain", + "backup_onboarding_2_description": "salinan tempatan pada peranti yang berbeza. Ini termasuk fail utama dan sandaran fail tersebut secara setempat.", + "backup_onboarding_3_description": "jumlah salinan data anda, termasuk fail asal. Ini termasuk 1 salinan luar tapak dan 2 salinan tempatan.", "backup_settings": "Tetapan Salinan Pangkalan Data", "backup_settings_description": "Urus tetapan salinan pangkalan data.", "cleared_jobs": "Kerja telah dibersihkan untuk: {job}", diff --git a/i18n/nb_NO.json b/i18n/nb_NO.json index d6eb4944c2..071dd27a16 100644 --- a/i18n/nb_NO.json +++ b/i18n/nb_NO.json @@ -23,23 +23,25 @@ "add_partner": "Legg til partner", "add_path": "Legg til sti", "add_photos": "Legg til bilder", - "add_tag": "Legg til tagg", + "add_tag": "Legg til merkelapp", "add_to": "Legg til i…", "add_to_album": "Legg til album", "add_to_album_bottom_sheet_added": "Lagt til i {album}", "add_to_album_bottom_sheet_already_exists": "Allerede i {album}", + "add_to_album_bottom_sheet_some_local_assets": "Noen lokale elementer kunne ikke legges til i albumet", "add_to_album_toggle": "Avhuking for {album}", "add_to_albums": "Legg til i album", - "add_to_albums_count": "Legg til i albumer ({count})", + "add_to_albums_count": "Legg til i album ({count})", "add_to_shared_album": "Legg til delt album", + "add_upload_to_stack": "Legg til opplasting i stakken", "add_url": "Legg til URL", - "added_to_archive": "Lagt til i arkiv", - "added_to_favorites": "Lagt til i favoritter", + "added_to_archive": "Lagt til i arkivet", + "added_to_favorites": "Lagt til favoritter", "added_to_favorites_count": "Lagt til {count, number} i favoritter", "admin": { "add_exclusion_pattern_description": "Legg til ekskluderingsmønstre. Globbing med *, ** og ? støttes. For å ignorere alle filer i en hvilken som helst mappe som heter \"Raw\", bruk \"**/Raw/**\". For å ignorere alle filer som slutter på \".tif\", bruk \"**/*.tif\". For å ignorere en absolutt filplassering, bruk \"/filsti/til/ignorer/**\".", "admin_user": "Administrasjonsbruker", - "asset_offline_description": "Dette eksterne biblioteksobjektet finnes ikke lenger på disk og har blitt flyttet til papirkurven. Hvis filen ble flyttet innad i biblioteket, se etter det tilsvarende objektet i tidslinjen din. For å gjenopprette objektet, vennligst sørg for at filstien under er tilgjengelig for Immich og skann biblioteket.", + "asset_offline_description": "Dette eksterne bibliotekselementet finnes ikke lenger på disk og har blitt flyttet til papirkurven. Hvis filen ble flyttet innad i biblioteket, se etter det tilsvarende elementet i tidslinjen din. For å gjenopprette elementet, vennligst sørg for at filstien under er tilgjengelig for Immich og skann biblioteket.", "authentication_settings": "Godkjenningsinnstillinger", "authentication_settings_description": "Administrer passord, OAuth, og andre innstillinger for autentisering", "authentication_settings_disable_all": "Er du sikker på at du ønsker å deaktivere alle innloggingsmetoder? Innlogging vil bli fullstendig deaktivert.", @@ -59,12 +61,12 @@ "backup_settings_description": "Håndter innstillinger for database-dump.", "cleared_jobs": "Ryddet opp jobber for: {job}", "config_set_by_file": "Konfigurasjonen er for øyeblikket satt av en konfigurasjonsfil", - "confirm_delete_library": "Er du sikker på at du vil slette biblioteket {library}?", - "confirm_delete_library_assets": "Er du sikker på at du vil slette dette biblioteket? Dette vil slette alt innhold ({count, plural, one {# objekt} other {# objekter}}) og tilhørende eiendeler fra Immich og kan ikke angres. Filene vil forbli på disken.", + "confirm_delete_library": "Vil du virkelig slette biblioteket {library}?", + "confirm_delete_library_assets": "Vil du virkelig slette dette biblioteket? Dette vil slette alt innhold ({count, plural, one {# element} other {# elementer}}) og tilhørende eiendeler fra Immich og kan ikke angres. Filene vil forbli på disken.", "confirm_email_below": "For å bekrefte, skriv inn \"{email}\" nedenfor", - "confirm_reprocess_all_faces": "Er du sikker på at du vil behandle alle ansikter på nytt? Dette vil også fjerne navngitte personer.", - "confirm_user_password_reset": "Er du sikker på at du vil tilbakestille passordet til {user}?", - "confirm_user_pin_code_reset": "Er du sikker på at du vil tilbakestille PIN-koden til {user} ?", + "confirm_reprocess_all_faces": "Vil du virkelig behandle alle ansikter på nytt? Dette vil også fjerne navngitte personer.", + "confirm_user_password_reset": "Vil du virkelig tilbakestille passordet til {user}?", + "confirm_user_pin_code_reset": "Vil du virkelig tilbakestille PIN-koden til {user} ?", "create_job": "Lag jobb", "cron_expression": "Cron uttrykk", "cron_expression_description": "Still inn skanneintervallet med cron-formatet. For mer informasjon henvises til f.eks. Crontab Guru", @@ -118,11 +120,11 @@ "library_settings_description": "Administrer innstillinger for eksterne bibliotek", "library_tasks_description": "Skann eksterne biblioteker for nye og/eller endrede ressurser", "library_watching_enable_description": "Overvåk eksterne bibliotek for filendringer", - "library_watching_settings": "Overvåkning av bibliotek (EKSPERIMENTELL)", + "library_watching_settings": "Overvåkning av bibliotek [EKSPERIMENTELL]", "library_watching_settings_description": "Se automatisk etter endrede filer", "logging_enable_description": "Aktiver logging", "logging_level_description": "Hvis aktivert, hvilket loggnivå som skal brukes.", - "logging_settings": "Logger", + "logging_settings": "Loggføring", "machine_learning_availability_checks": "Tilgjengelighetssjekk", "machine_learning_availability_checks_description": "Automatisk oppdag og velg tilgjengelige maskinlæring-servere", "machine_learning_availability_checks_enabled": "Aktiver tilgjengelighetssjekk", @@ -152,6 +154,18 @@ "machine_learning_min_detection_score_description": "Minimum tillitspoeng for at et ansikt skal bli oppdaget, på en skala fra 0 til 1. Lavere verdier vil oppdage flere ansikter, men kan føre til falske positiver.", "machine_learning_min_recognized_faces": "Minimum antall gjenkjente ansikter", "machine_learning_min_recognized_faces_description": "Det minste antallet gjenkjente ansikter som kreves for å opprette en person. Å øke dette gjør ansiktsgjenkjenning mer presis på bekostning av å øke sjansen for at et ansikt ikke tilordnes til en person.", + "machine_learning_ocr": "OCR", + "machine_learning_ocr_description": "Bruk maskinlæring til å gjenkjenne tekst i bilder", + "machine_learning_ocr_enabled": "Aktiver OCR", + "machine_learning_ocr_enabled_description": "Hvis deaktivert, vil ikke bilder bli tekstgjenkjent.", + "machine_learning_ocr_max_resolution": "Maksimal oppløsning", + "machine_learning_ocr_max_resolution_description": "Forhåndsvisninger over denne oppløsningen vil bli endret i størrelse samtidig som sideforholdet bevares. Høyere verdier er mer nøyaktige, men tar lengre tid å behandle og bruker mer minne.", + "machine_learning_ocr_min_detection_score": "Minimum deteksjonspoengsum", + "machine_learning_ocr_min_detection_score_description": "Minimum konfidenspoeng for at tekst skal kunne oppdages er fra 0–1. Lavere verdier vil oppdage mer tekst, men kan føre til falske positiver.", + "machine_learning_ocr_min_recognition_score": "Minste deteksjonspoengsum", + "machine_learning_ocr_min_score_recognition_description": "Minste deteksjonspoengsum for at tekst skal bli gjenkjent fra 0-1. Lavere verdier vil gjenkjenne mer tekst, men kan gi falske positiver.", + "machine_learning_ocr_model": "OCR modell", + "machine_learning_ocr_model_description": "Server modeller er mer nøyaktige enn mobilmodeller, men de bruker lengre tid og mer minne.", "machine_learning_settings": "Innstillinger for maskinlæring", "machine_learning_settings_description": "Administrer maskinlæringsfunksjoner og innstillinger", "machine_learning_smart_search": "Smart søk", @@ -189,9 +203,9 @@ "nightly_tasks_database_cleanup_setting": "Opprydningsjobber for databasen", "nightly_tasks_database_cleanup_setting_description": "Rydder opp i gamle, utgåtte data fra databasen", "nightly_tasks_generate_memories_setting": "Genererer minner", - "nightly_tasks_generate_memories_setting_description": "Generer nye minner fra objekter", + "nightly_tasks_generate_memories_setting_description": "Generer nye minner fra elementer", "nightly_tasks_missing_thumbnails_setting": "Generer manglende miniatyrbilder", - "nightly_tasks_missing_thumbnails_setting_description": "Legg til objekter i kø som mangler miniatyrbilder for generering", + "nightly_tasks_missing_thumbnails_setting_description": "Legg til elementer i kø som mangler miniatyrbilder for generering", "nightly_tasks_settings": "Innstillinger for nattjobber", "nightly_tasks_settings_description": "Endre på nattjobber", "nightly_tasks_start_time_setting": "Starttid", @@ -209,6 +223,8 @@ "notification_email_ignore_certificate_errors_description": "Ignorer valideringsfeil for TLS-sertifikat (ikke anbefalt)", "notification_email_password_description": "Passordet som skal brukes ved autentisering med e-posts serveren", "notification_email_port_description": "Porten til e-postserveren (f.eks. 25, 465 eller 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "Bruk SMTPS ( SMTP over TLS)", "notification_email_sent_test_email_button": "Send test e-post og lagre", "notification_email_setting_description": "Innstillinger for å sende e-postvarsler", "notification_email_test_email": "Send test e-post", @@ -241,6 +257,7 @@ "oauth_storage_quota_default_description": "Kvote i GiB som skal brukes når ingen krav er oppgitt.", "oauth_timeout": "Forespørselen tok for lang tid", "oauth_timeout_description": "Tidsavbrudd for forespørsel i millisekunder", + "ocr_job_description": "Bruk maskinlæring for å gjenkjenne tekst i bilder", "password_enable_description": "Logg inn med e-post og passord", "password_settings": "Passordinnlogging", "password_settings_description": "Administrer innstillinger for passordinnlogging", @@ -275,7 +292,7 @@ "storage_template_hash_verification_enabled_description": "Aktiver hasjverifisering. Ikke deaktiver dette med mindre du er sikker på konsekvensene", "storage_template_migration": "Implementer lagringsmal", "storage_template_migration_description": "Bruk gjeldende {template} på tidligere opplastede bilder", - "storage_template_migration_info": "Lagringsmalen vil endre filtypen til små bokstaver. Malendringer vil kun gjelde nye objekter. For å anvende malen på tidligere opplastede objekter, kjør {job}.", + "storage_template_migration_info": "Lagringsmalen vil endre filtypen til små bokstaver. Malendringer vil kun gjelde nye elementer. For å anvende malen på tidligere opplastede elementer, kjør {job}.", "storage_template_migration_job": "Migreringsjobb for lagringsmal", "storage_template_more_details": "For mer informasjon om denne funksjonen, se lagringsmalen og dens konsekvenser", "storage_template_onboarding_description_v2": "Når aktivert vil denne funksjonen automatisk organisere filer basert på en brukerdefinert mal. For mer informasjon, se denne linken dokumentasjon.", @@ -331,7 +348,7 @@ "transcoding_max_b_frames": "Maksimalt antall B-frames", "transcoding_max_b_frames_description": "Høyere verdier forbedrer komprimeringseffektiviteten, men senker ned kodingen. Kan være inkompatibelt med maskinvareakselerasjon på eldre enheter. 0 deaktiverer B-rammer, mens -1 setter verdien automatisk.", "transcoding_max_bitrate": "Maksimal bithastighet", - "transcoding_max_bitrate_description": "Å sette en maksimal bithastighet kan gjøre filstørrelsene mer forutsigbare med en liten kostnad for kvaliteten. For 720p er typiske verdier 2600 kbit/s for VP9 eller HEVC, eller 4500 kbit/s for H.264. Deaktivert hvis satt til 0.", + "transcoding_max_bitrate_description": "Å sette en maksimal bithastighet kan gjøre filstørrelsene mer forutsigbare med en liten kostnad for kvaliteten. For 720p er typiske verdier 2600 kbit/s for VP9 eller HEVC, eller 4500 kbit/s for H.264. Deaktivert hvis satt til 0. Når ingen verdi er spesifisert er k (for kbit/s) forventet slik at: 5000,5000k og 5M (for Mbit/s) er like verdier.", "transcoding_max_keyframe_interval": "Maksimal referansebilde intervall", "transcoding_max_keyframe_interval_description": "Setter maksimalt antall bilder mellom referansebilder. Lavere verdier reduserer kompresjonseffektiviteten, men forbedrer søketider og kan forbedre kvaliteten i scener med rask bevegelse. 0 setter verdien automatisk.", "transcoding_optimal_description": "Videoer som har høyere oppløsning enn målopppløsningen eller som ikke er i et akseptert format", @@ -367,7 +384,7 @@ "trash_settings_description": "Administrer papirkurv-innstillinger", "unlink_all_oauth_accounts": "Koble fra alle OAuth-kontoer", "unlink_all_oauth_accounts_description": "Husk å koble fra alle OAuth-kontoer før du migrerer til ny leverandør.", - "unlink_all_oauth_accounts_prompt": "Er du sikker på at du vil koble fra alle OAuth-kontoer? Dette vil nullstille OAuth ID for hver bruker, og kan ikke angres.", + "unlink_all_oauth_accounts_prompt": "Vil du virkelig koble fra alle OAuth-kontoer? Dette vil nullstille OAuth ID for hver bruker, og kan ikke angres.", "user_cleanup_job": "Bruker opprydning", "user_delete_delay": "{user}s konto og elementer vil legges i kø for permanent sletting om {delay, plural, one {# dag} other {# dager}}.", "user_delete_delay_settings": "Sletteforsinkelse", @@ -390,8 +407,8 @@ "video_conversion_job": "Transkod videoer", "video_conversion_job_description": "Konverter videoer for bedre kompatibilitet med nettlesere og enheter" }, - "admin_email": "Administrator E-post", - "admin_password": "Administrator Passord", + "admin_email": "Administrator e-post", + "admin_password": "Administratorpassord", "administration": "Administrasjon", "advanced": "Avansert", "advanced_settings_enable_alternate_media_filter_subtitle": "Bruk denne innstillingen for å filtrere mediefiler under synkronisering basert på alternative kriterier. Bruk kun denne innstillingen dersom man opplever problemer med at applikasjonen ikke oppdager alle album.", @@ -400,11 +417,11 @@ "advanced_settings_prefer_remote_subtitle": "Noen enheter er veldige trege til å hente miniatyrbilder fra enheten. Aktiver denne innstillingen for å hente de eksternt istedenfor.", "advanced_settings_prefer_remote_title": "Foretrekk eksterne bilder", "advanced_settings_proxy_headers_subtitle": "Definer proxy headere som Immich skal benytte ved enhver nettverksrequest", - "advanced_settings_proxy_headers_title": "Proxy headere", + "advanced_settings_proxy_headers_title": "Proxy headere[EKSPERIMENTELL]", "advanced_settings_readonly_mode_subtitle": "Aktiverer skrivebeskyttet modus der bildene bare kan vises. Ting som å velge flere bilder, dele, caste og slette er deaktivert. Aktiver/deaktiver skrivebeskyttet modus via brukerens avatar fra hovedskjermen", "advanced_settings_readonly_mode_title": "Skrivebeskyttet modus", "advanced_settings_self_signed_ssl_subtitle": "Hopper over SSL sertifikatverifikasjon for server-endepunkt. Påkrevet for selvsignerte sertifikater.", - "advanced_settings_self_signed_ssl_title": "Tillat selvsignerte SSL sertifikater", + "advanced_settings_self_signed_ssl_title": "Tillat selvsignerte SSL sertifikater [EKSPERIMENTELL]", "advanced_settings_sync_remote_deletions_subtitle": "Automatisk slette eller gjenopprette filer på denne enheten hvis den handlingen har blitt gjort på nettsiden", "advanced_settings_sync_remote_deletions_title": "Synk sletting fra nettsiden [EKSPERIMENTELT]", "advanced_settings_tile_subtitle": "Avanserte brukerinnstillinger", @@ -413,43 +430,43 @@ "age_months": "Alder {months, plural, one {# måned} other {# måneder}}", "age_year_months": "Alder 1 år, {months, plural, one {# måned} other {# måneder}}", "age_years": "{years, plural, other {Alder #}}", - "album_added": "Album lagt til", + "album_added": "Album opprettet", "album_added_notification_setting_description": "Motta en e-postvarsling når du legges til i et delt album", "album_cover_updated": "Albumomslag oppdatert", - "album_delete_confirmation": "Er du sikker på at du vil slette albumet {album}?", + "album_delete_confirmation": "Vil du virkelig slette albumet {album}?", "album_delete_confirmation_description": "Hvis dette albumet deles, vil andre brukere miste tilgangen til dette.", "album_deleted": "Album slettet", "album_info_card_backup_album_excluded": "EKSKLUDERT", "album_info_card_backup_album_included": "INKLUDERT", "album_info_updated": "Albuminformasjon oppdatert", "album_leave": "Forlate album?", - "album_leave_confirmation": "Er du sikker på at du vil forlate {album}?", - "album_name": "Album Navn", + "album_leave_confirmation": "Vil du virkelig forlate {album}?", + "album_name": "Albumnavn", "album_options": "Albumalternativer", "album_remove_user": "Fjerne bruker?", - "album_remove_user_confirmation": "Er du sikker på at du vil fjerne {user}?", + "album_remove_user_confirmation": "Vil du virkelig fjerne {user}?", "album_search_not_found": "Ingen album ble funnet som traff ditt søk", - "album_share_no_users": "Ser ut til at du har delt dette albumet med alle brukere, eller du ikke har noen brukere å dele det med.", + "album_share_no_users": "Dette albumet er allerede delt med du har delt dette albumet med alle brukere, eller du ikke har noen brukere å dele det med.", "album_summary": "Oppsummering av album", "album_updated": "Album oppdatert", "album_updated_setting_description": "Motta e-postvarsling når et delt album får nye filer", "album_user_left": "Forlot {album}", "album_user_removed": "Fjernet {user}", - "album_viewer_appbar_delete_confirm": "Er du sikker på at du vil slette dette albumet fra kontoen din?", + "album_viewer_appbar_delete_confirm": "Vil du virkelig slette dette albumet fra kontoen din?", "album_viewer_appbar_share_err_delete": "Kunne ikke slette albumet", "album_viewer_appbar_share_err_leave": "Kunne ikke forlate albumet", - "album_viewer_appbar_share_err_remove": "Det oppstod et problem ved fjerning av objekter fra albumet", - "album_viewer_appbar_share_err_title": "Feilet ved endring av albumtittel", + "album_viewer_appbar_share_err_remove": "Det oppstod et problem ved fjerning av elementer fra albumet", + "album_viewer_appbar_share_err_title": "Mislyktes ved endring av albumtittel", "album_viewer_appbar_share_leave": "Forlat album", "album_viewer_appbar_share_to": "Del til", "album_viewer_page_share_add_users": "Legg til brukere", - "album_with_link_access": "La hvem som helst med lenken se bilder og folk i dette albumet.", - "albums": "Albumer", - "albums_count": "{count, plural, one {{count, number} Album} other {{count, number} Albumer}}", - "albums_default_sort_order": "Standard sorteringsrekkefølge for albumer", + "album_with_link_access": "La hvem som helst med lenken se bilder og personer i dette albumet.", + "albums": "Album", + "albums_count": "{count, plural, one {{count, number} Album} other {{count, number} Album}}", + "albums_default_sort_order": "Standard sorteringsrekkefølge for album", "albums_default_sort_order_description": "Standard sorteringsrekkefølge for bilder når man lager et nytt album.", "albums_feature_description": "Samlinger av bilder som kan deles med andre brukere.", - "albums_on_device_count": "Albumer på enheten {count}", + "albums_on_device_count": "Album på enheten {count}", "all": "Alle", "all_albums": "Alle album", "all_people": "Alle personer", @@ -460,29 +477,33 @@ "allow_public_user_to_upload": "Tillat uautentiserte brukere å laste opp", "alt_text_qr_code": "QR-kodebilde", "anti_clockwise": "Mot klokken", - "api_key": "API Nøkkel", + "api_key": "API-nøkkel", "api_key_description": "Denne verdien vil vises kun én gang. Pass på å kopiere den før du lukker vinduet.", "api_key_empty": "API-nøkkelnavnet bør ikke være tomt", "api_keys": "API-nøkler", - "app_bar_signout_dialog_content": "Er du sikker på at du vil logge ut?", + "app_architecture_variant": "Variant (Arkitektur)", + "app_bar_signout_dialog_content": "Vil du virkelig logge ut?", "app_bar_signout_dialog_ok": "Ja", "app_bar_signout_dialog_title": "Logg ut", + "app_download_links": "Nedlastingslinker for App", "app_settings": "Appinstillinger", + "app_stores": "App butikker", + "app_update_available": "Appoppdatering er tilgjengelig", "appears_in": "Vises i", "apply_count": "Bruk ({count, number})", "archive": "Arkiv", "archive_action_prompt": "{count} lagt til i arkivet", "archive_or_unarchive_photo": "Arkiver eller ta ut av arkivet", - "archive_page_no_archived_assets": "Ingen arkiverte objekter funnet", + "archive_page_no_archived_assets": "Ingen arkiverte elementer funnet", "archive_page_title": "Arkiv ({count})", "archive_size": "Arkivstørrelse", "archive_size_description": "Konfigurer arkivstørrelsen for nedlastinger (i GiB)", "archived": "Arkivert", "archived_count": "{count, plural, other {Arkivert #}}", "are_these_the_same_person": "Er disse samme person?", - "are_you_sure_to_do_this": "Er du sikker på at du vil gjøre dette?", - "asset_action_delete_err_read_only": "Kan ikke slette objekt(er) med kun lese-rettighet, hopper over", - "asset_action_share_err_offline": "Kan ikke hente offline objekt(er), hopper over", + "are_you_sure_to_do_this": "Vil du virkelig gjøre dette?", + "asset_action_delete_err_read_only": "Kunne ikke slette element(er) med kun lese-rettighet, hopper over", + "asset_action_share_err_offline": "Kunne ikke hente offline element(er), hopper over", "asset_added_to_album": "Lagt til i album", "asset_adding_to_album": "Legger til i album…", "asset_description_updated": "Elementbeskrivelse har blitt oppdatert", @@ -498,37 +519,37 @@ "asset_list_settings_subtitle": "Innstillinger for layout av fotorutenett", "asset_list_settings_title": "Fotorutenett", "asset_offline": "Fil utilgjengelig", - "asset_offline_description": "Dette elementet er offline. Immich kan ikke aksessere dets lokasjon. Vennlist påse at elementet er tilgijengelig og skann så biblioteket på nytt.", + "asset_offline_description": "Dette elementet er offline. Immich kan ikke aksessere dets lokasjon. Vennligst påse at elementet er tilgjengelig og skann så biblioteket på nytt.", "asset_restored_successfully": "Objekt(er) gjenopprettet", "asset_skipped": "Hoppet over", - "asset_skipped_in_trash": "I søppelbøtten", + "asset_skipped_in_trash": "I papirkurven", "asset_trashed": "Objekt slettet", - "asset_troubleshoot": "Feilsøk objekt", + "asset_troubleshoot": "Feilsøk element", "asset_uploaded": "Lastet opp", "asset_uploading": "Laster opp…", "asset_viewer_settings_subtitle": "Endre dine visningsinnstillinger for galleriet", "asset_viewer_settings_title": "Objektviser", "assets": "Filer", - "assets_added_count": "Lagt til {count, plural, one {# objekt} other {# objekter}}", - "assets_added_to_album_count": "Lagt til {count, plural, one {# objekter} other {# objekt}} i album", + "assets_added_count": "Lagt til {count, plural, one {# element} other {# elementer}}", + "assets_added_to_album_count": "Lagt til {count, plural, one {# elementer} other {# element}} i album", "assets_added_to_albums_count": "Lagt til {assetTotal, plural, one {# asset} other {# assets}} til {albumTotal, plural, one {# album} other {# albums}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {Objektet} other {Objektene}} kan ikke legges til i albumet", "assets_cannot_be_added_to_albums": "{count, plural, one {Asset} other {Assets}} kan ikke legges til i noen av albumene", "assets_count": "{count, plural, one {# fil} other {# filer}}", - "assets_deleted_permanently": "{count} objekt(er) slettet permanent", - "assets_deleted_permanently_from_server": "{count} objekt(er) slettet permanent fra Immich-serveren", + "assets_deleted_permanently": "{count} element(er) slettet permanent", + "assets_deleted_permanently_from_server": "{count} element(er) slettet permanent fra Immich-serveren", "assets_downloaded_failed": "{count, plural, one {Nedlasting av # fil - {error} fil feilet} other {Nedlastede # filer - {error} filer feilet}}", "assets_downloaded_successfully": "{count, plural, one {Nedlastet # fil vellykket} other {Nedlastede # filer vellykket}}", - "assets_moved_to_trash_count": "Flyttet {count, plural, one {# objekt} other {# objekter}} til søppel", - "assets_permanently_deleted_count": "Slettet {count, plural, one {# objekt} other {# objekter}} permanent", - "assets_removed_count": "Slettet {count, plural, one {# objekt} other {# objekter}}", - "assets_removed_permanently_from_device": "{count} objekt(er) slettet permanent fra enheten din", - "assets_restore_confirmation": "Er du sikker på at du vil gjenopprette alle slettede eiendeler? Denne handlingen kan ikke angres! Vær oppmerksom på at frakoblede ressurser ikke kan gjenopprettes på denne måten.", - "assets_restored_count": "Gjenopprettet {count, plural, one {# objekt} other {# objekter}}", - "assets_restored_successfully": "{count} objekt(er) gjenopprettet", - "assets_trashed": "{count} objekt(er) slettet", - "assets_trashed_count": "Kastet {count, plural, one {# objekt} other {# objekter}}", - "assets_trashed_from_server": "{count} objekt(er) slettet fra Immich serveren", + "assets_moved_to_trash_count": "Flyttet {count, plural, one {# element} other {# elementer}} til søppel", + "assets_permanently_deleted_count": "Slettet {count, plural, one {# element} other {# elementer}} permanent", + "assets_removed_count": "Slettet {count, plural, one {# element} other {# elementer}}", + "assets_removed_permanently_from_device": "{count} element(er) slettet permanent fra enheten din", + "assets_restore_confirmation": "Vil du virkelig gjenopprette alle slettede eiendeler? Denne handlingen kan ikke angres! Vær oppmerksom på at frakoblede ressurser ikke kan gjenopprettes på denne måten.", + "assets_restored_count": "Gjenopprettet {count, plural, one {# element} other {# elementer}}", + "assets_restored_successfully": "{count} element(er) gjenopprettet", + "assets_trashed": "{count} element(er) slettet", + "assets_trashed_count": "Kastet {count, plural, one {# element} other {# elementer}}", + "assets_trashed_from_server": "{count} element(er) slettet fra Immich serveren", "assets_were_part_of_album_count": "{count, plural, one {Objektet} other {Objektene}} er allerede lagt til i albumet", "assets_were_part_of_albums_count": "{count, plural, one {Asset was} other {Assets were}} allerede inkludert i albumet", "authorized_devices": "Autoriserte enheter", @@ -547,17 +568,18 @@ "backup_album_selection_page_assets_scatter": "Objekter kan bli spredd over flere album. Album kan derfor bli inkludert eller ekskludert under sikkerhetskopieringen.", "backup_album_selection_page_select_albums": "Velg album", "backup_album_selection_page_selection_info": "Valginformasjon", - "backup_album_selection_page_total_assets": "Totalt antall unike objekter", + "backup_album_selection_page_total_assets": "Totalt antall unike elementer", "backup_albums_sync": "Synkronisering av sikkerhetskopialbum", "backup_all": "Alle", - "backup_background_service_backup_failed_message": "Sikkerhetskopiering av objekter feilet. Prøver på nytt…", + "backup_background_service_backup_failed_message": "Sikkerhetskopiering av elementer feilet. Prøver på nytt…", + "backup_background_service_complete_notification": "Backup av elementer fullført", "backup_background_service_connection_failed_message": "Tilkobling til server feilet. Prøver på nytt…", "backup_background_service_current_upload_notification": "Laster opp {filename}", - "backup_background_service_default_notification": "Ser etter nye objekter…", - "backup_background_service_error_title": "Sikkerhetskopieringsfeil", - "backup_background_service_in_progress_notification": "Sikkerhetskopierer objekter…", + "backup_background_service_default_notification": "Ser etter nye elementer…", + "backup_background_service_error_title": "Feil under sikkerhetskopiering", + "backup_background_service_in_progress_notification": "Sikkerhetskopierer elementer…", "backup_background_service_upload_failure_notification": "Opplasting feilet {filename}", - "backup_controller_page_albums": "Sikkerhetskopier albumer", + "backup_controller_page_albums": "Sikkerhetskopier album", "backup_controller_page_background_app_refresh_disabled_content": "Aktiver bakgrunnsoppdatering i Innstillinger > Generelt > Bakgrunnsoppdatering for å bruke sikkerhetskopiering i bakgrunnen.", "backup_controller_page_background_app_refresh_disabled_title": "Bakgrunnsoppdateringer er deaktivert", "backup_controller_page_background_app_refresh_enable_button_text": "Gå til innstillinger", @@ -567,8 +589,8 @@ "backup_controller_page_background_battery_info_title": "Batterioptimalisering", "backup_controller_page_background_charging": "Kun ved lading", "backup_controller_page_background_configure_error": "Konfigurering av bakgrunnstjenesten feilet", - "backup_controller_page_background_delay": "Forsink sikkerhetskopiering av nye objekter: {duration}", - "backup_controller_page_background_description": "Skru på bakgrunnstjenesten for å automatisk sikkerhetskopiere alle nye objekter uten å måtte åpne appen", + "backup_controller_page_background_delay": "Forsink sikkerhetskopiering av nye elementer: {duration}", + "backup_controller_page_background_description": "Skru på bakgrunnstjenesten for å automatisk sikkerhetskopiere alle nye elementer uten å måtte åpne appen", "backup_controller_page_background_is_off": "Automatisk sikkerhetskopiering i bakgrunnen er deaktivert", "backup_controller_page_background_is_on": "Automatisk sikkerhetskopiering i bakgrunnen er aktivert", "backup_controller_page_background_turn_off": "Skru av bakgrunnstjenesten", @@ -578,9 +600,9 @@ "backup_controller_page_backup_selected": "Valgte: ", "backup_controller_page_backup_sub": "Opplastede bilder og videoer", "backup_controller_page_created": "Opprettet: {date}", - "backup_controller_page_desc_backup": "Slå på sikkerhetskopiering i forgrunnen for automatisk å laste opp nye objekter til serveren når du åpner appen.", + "backup_controller_page_desc_backup": "Slå på sikkerhetskopiering i forgrunnen for automatisk å laste opp nye elementer til serveren når du åpner appen.", "backup_controller_page_excluded": "Ekskludert: ", - "backup_controller_page_failed": "Feilet ({count})", + "backup_controller_page_failed": "Mislyktes ({count})", "backup_controller_page_filename": "Filnavn: {filename} [{size}]", "backup_controller_page_id": "ID: {id}", "backup_controller_page_info": "Informasjon om sikkerhetskopi", @@ -592,13 +614,14 @@ "backup_controller_page_status_off": "Automatisk sikkerhetskopiering i forgrunnen er av", "backup_controller_page_status_on": "Automatisk sikkerhetskopiering i forgrunnen er på", "backup_controller_page_storage_format": "{used} av {total} brukt", - "backup_controller_page_to_backup": "Albumer som skal sikkerhetskopieres", + "backup_controller_page_to_backup": "Album som skal sikkerhetskopieres", "backup_controller_page_total_sub": "Alle unike bilder og videoer fra valgte album", "backup_controller_page_turn_off": "Slå av sikkerhetskopiering i forgrunnen", "backup_controller_page_turn_on": "Slå på sikkerhetskopiering i forgrunnen", "backup_controller_page_uploading_file_info": "Laster opp filinformasjon", - "backup_err_only_album": "Kan ikke fjerne det eneste albumet", - "backup_info_card_assets": "objekter", + "backup_err_only_album": "Kunne ikke fjerne det eneste albumet", + "backup_error_sync_failed": "Synkronisering feilet. Kunne ikke fortsette sikkerhetskopiering.", + "backup_info_card_assets": "elementer", "backup_manual_cancelled": "Avbrutt", "backup_manual_in_progress": "Opplasting er allerede i gang. Prøv igjen om litt", "backup_manual_success": "Vellykket", @@ -618,15 +641,15 @@ "bugs_and_feature_requests": "Feil og funksjonsforespørsler", "build": "Bygg", "build_image": "Lag Bilde", - "bulk_delete_duplicates_confirmation": "Er du sikker på at du vil slette {count, plural, one {# duplisert fil} other {# dupliserte filer}}? Dette vil beholde største filen fra hver gruppe og vil permanent slette alle andre duplikater. Du kan ikke angre denne handlingen!", - "bulk_keep_duplicates_confirmation": "Er du sikker på at du vil beholde {count, plural, one {# duplikat} other {# duplikater}}? Dette vil løse alle duplikatgrupper uten å slette noe.", - "bulk_trash_duplicates_confirmation": "Er du sikker på ønsker å slette {count, plural, one {# duplisert objekt} other {# dupliserte objekter}}? Dette vil beholde største filen fra hver gruppe, samt slette alle andre duplikater.", + "bulk_delete_duplicates_confirmation": "Vil du virkelig slette {count, plural, one {# duplisert fil} other {# dupliserte filer}}? Dette vil beholde største filen fra hver gruppe og vil permanent slette alle andre duplikater. Du kan ikke angre denne handlingen!", + "bulk_keep_duplicates_confirmation": "Vil du virkelig beholde {count, plural, one {# duplikat} other {# duplikater}}? Dette vil løse alle duplikatgrupper uten å slette noe.", + "bulk_trash_duplicates_confirmation": "Vil du virkelig å slette {count, plural, one {# duplisert element} other {# dupliserte elementer}}? Dette vil beholde største filen fra hver gruppe, samt slette alle andre duplikater.", "buy": "Kjøp Immich", "cache_settings_clear_cache_button": "Tøm buffer", "cache_settings_clear_cache_button_title": "Tømmer app-ens buffer. Dette vil ha betydelig innvirkning på appens ytelse inntil bufferen er gjenoppbygd.", "cache_settings_duplicated_assets_clear_button": "TØM", "cache_settings_duplicated_assets_subtitle": "Bilder og videoer som er ignorert av app'en", - "cache_settings_duplicated_assets_title": "Dupliserte objekter ({count})", + "cache_settings_duplicated_assets_title": "Dupliserte elementer ({count})", "cache_settings_statistics_album": "Bibliotekminiatyrbilder", "cache_settings_statistics_full": "Originalbilder", "cache_settings_statistics_shared": "Delte albumminiatyrbilder", @@ -643,9 +666,9 @@ "cancel_search": "Avbryt søk", "canceled": "Avbrutt", "canceling": "Avbryter", - "cannot_merge_people": "Kan ikke slå sammen personer", + "cannot_merge_people": "Kunne ikke slå sammen personer", "cannot_undo_this_action": "Du kan ikke gjøre om denne handlingen!", - "cannot_update_the_description": "Kan ikke oppdatere beskrivelsen", + "cannot_update_the_description": "Kunne ikke oppdatere beskrivelsen", "cast": "Strøm", "cast_description": "Konfigurer tilgjengelige cast-destinasjoner", "change_date": "Endre dato", @@ -655,21 +678,23 @@ "change_location": "Endre sted", "change_name": "Endre navn", "change_name_successfully": "Navneendring vellykket", - "change_password": "Endre Passord", + "change_password": "Endre passord", "change_password_description": "Dette er enten første gang du logger inn i systemet, eller det har blitt gjort en forespørsel om å endre passordet ditt. Vennligst skriv inn det nye passordet nedenfor.", "change_password_form_confirm_password": "Bekreft passord", "change_password_form_description": "Hei {name}!\n\nDette er enten første gang du logger på systemet, eller det er sendt en forespørsel om å endre passordet ditt. Vennligst skriv inn det nye passordet nedenfor.", + "change_password_form_log_out": "Logg ut av alle andre enheter", + "change_password_form_log_out_description": "Det er anbefalt å logge ut av alle andre enheter", "change_password_form_new_password": "Nytt passord", "change_password_form_password_mismatch": "Passordene stemmer ikke", "change_password_form_reenter_new_password": "Skriv nytt passord igjen", - "change_pin_code": "Endre PIN kode", + "change_pin_code": "Endre PIN-kode", "change_your_password": "Endre passordet ditt", "changed_visibility_successfully": "Endret synlighet vellykket", "charging": "Lading", "charging_requirement_mobile_backup": "Bakgrunnsbackup krever at enheten lader", - "check_corrupt_asset_backup": "Sjekk etter korrupte backupobjekter", + "check_corrupt_asset_backup": "Sjekk etter korrupte backupelementer", "check_corrupt_asset_backup_button": "Utfør sjekk", - "check_corrupt_asset_backup_description": "Kjør denne sjekken kun over Wi-Fi og når alle objekter har blitt lastet opp. Denne sjekken kan ta noen minutter.", + "check_corrupt_asset_backup_description": "Kjør denne sjekken kun over Wi-Fi og når alle elementer har blitt lastet opp. Denne sjekken kan ta noen minutter.", "check_logs": "Sjekk Logger", "choose_matching_people_to_merge": "Velg personer som skal slås sammen", "city": "By", @@ -686,7 +711,7 @@ "client_cert_invalid_msg": "Ugyldig sertifikat eller feil passord", "client_cert_remove_msg": "Klient sertifikat er fjernet", "client_cert_subtitle": "Støtter kun PKCS12 (.p12, .pfx) formater. Importering/Fjerning av sertifikater er kun mulig før innlogging", - "client_cert_title": "SSL Klient sertifikat", + "client_cert_title": "SSL Klient sertifikat [EKSPERIMENTELL]", "clockwise": "Med urviseren", "close": "Lukk", "collapse": "Trekk sammen", @@ -698,12 +723,11 @@ "comments_and_likes": "Kommentarer & likes", "comments_are_disabled": "Kommentarer er deaktivert", "common_create_new_album": "Lag nytt album", - "common_server_error": "Sjekk nettverkstilkoblingen din, forsikre deg om at serveren er mulig å nå, og at app-/server-versjonene er kompatible.", "completed": "Fullført", "confirm": "Bekreft", "confirm_admin_password": "Bekreft administratorpassord", - "confirm_delete_face": "Er du sikker på at du vil slette {name} sitt ansikt fra ativia?", - "confirm_delete_shared_link": "Er du sikker på at du vil slette denne delte lenken?", + "confirm_delete_face": "Vil du virkelig slette {name} sitt ansikt fra ativia?", + "confirm_delete_shared_link": "Vil du virkelig slette denne delte lenken?", "confirm_keep_this_delete_others": "Alle andre ressurser i denne stabelen vil bli slettet bortsett fra denne ressursen. Er du sikker på at du vil fortsette?", "confirm_new_pin_code": "Bekreft ny PIN kode", "confirm_password": "Bekreft passord", @@ -721,7 +745,7 @@ "control_bottom_app_bar_edit_time": "Endre Dato og tid", "control_bottom_app_bar_share_link": "Del Lenke", "control_bottom_app_bar_share_to": "Del til", - "control_bottom_app_bar_trash_from_immich": "Flytt til søppelkasse", + "control_bottom_app_bar_trash_from_immich": "Flytt til papirkurv", "copied_image_to_clipboard": "Bildet er kopiert til utklippstavlen.", "copied_to_clipboard": "Kopiert til utklippstavlen!", "copy_error": "Kopi feil", @@ -736,9 +760,10 @@ "covers": "Omslag", "create": "Opprett", "create_album": "Opprett album", - "create_album_page_untitled": "Uten navn", + "create_album_page_untitled": "Navnløst", + "create_api_key": "Opprett API nøkkel", "create_library": "Opprett Bibliotek", - "create_link": "Opprett link", + "create_link": "Opprett lenke", "create_link_to_share": "Opprett delelink", "create_link_to_share_description": "La alle med lenken se de(t) valgte bildet/bildene", "create_new": "LAG NY", @@ -747,13 +772,13 @@ "create_new_user": "Opprett ny bruker", "create_shared_album_page_share_add_assets": "LEGG TIL OBJEKTER", "create_shared_album_page_share_select_photos": "Velg bilder", - "create_shared_link": "Opprett delt link", - "create_tag": "Lag tag", + "create_shared_link": "Opprett delt lenke", + "create_tag": "Lag merkelapp", "create_tag_description": "Lag en ny tag. For undertag, vennligst fullfør hele stien til taggen, inkludert forovervendt skråstrek.", "create_user": "Opprett Bruker", "created": "Opprettet", "created_at": "Laget", - "creating_linked_albums": "Oppretter sammenkoblede albumer...", + "creating_linked_albums": "Oppretter sammenkoblede album...", "crop": "Beskjær", "curated_object_page_title": "Ting", "current_device": "Nåværende enhet", @@ -766,6 +791,7 @@ "daily_title_text_date_year": "E MMM. dddd, yyyy", "dark": "Mørk", "dark_theme": "Aktiver mørk-modus", + "date": "Dato", "date_after": "Dato etter", "date_and_time": "Dato og tid", "date_before": "Dato før", @@ -782,23 +808,23 @@ "default_locale": "Standard språkinnstilling", "default_locale_description": "Formater datoer og tall basert på nettleserens språkinnstilling", "delete": "Slett", - "delete_action_confirmation_message": "Er du sikker på at du vil slette dette objektet? Dette vil flytte objektet til søppelkassen og vil gi deg beskjed om du vil slette det lokalt", + "delete_action_confirmation_message": "Vil du virkelig slette dette elementet? Dette vil flytte elementet til papirkurvn og vil gi deg beskjed om du vil slette det lokalt", "delete_action_prompt": "{count} slettet", "delete_album": "Slett album", - "delete_api_key_prompt": "Er du sikker på at du vil slette denne API-nøkkelen?", - "delete_dialog_alert": "Disse objektene vil bli slettet permanent fra Immich og fra enheten din", - "delete_dialog_alert_local": "Disse objektene vil bli permanent slettet fra enheten din, men vil fortsatt være tilgjengelige fra Immich serveren", - "delete_dialog_alert_local_non_backed_up": "Noen av objektene er ikke sikkerhetskopiert til Immich og vil bli permanent fjernet fra enheten din", - "delete_dialog_alert_remote": "Disse objektene vil bli permanent slettet fra Immich serveren", + "delete_api_key_prompt": "Vil du virkelig slette denne API-nøkkelen?", + "delete_dialog_alert": "Disse elementene vil bli slettet permanent fra Immich og fra enheten din", + "delete_dialog_alert_local": "Disse elementene vil bli permanent slettet fra enheten din, men vil fortsatt være tilgjengelige fra Immich serveren", + "delete_dialog_alert_local_non_backed_up": "Noen av elementene er ikke sikkerhetskopiert til Immich og vil bli permanent fjernet fra enheten din", + "delete_dialog_alert_remote": "Disse elementene vil bli permanent slettet fra Immich serveren", "delete_dialog_ok_force": "Slett uansett", "delete_dialog_title": "Slett permanent", - "delete_duplicates_confirmation": "Er du sikker på at du vil slette disse duplikatene permanent?", + "delete_duplicates_confirmation": "Vil du virkelig slette disse duplikatene permanent?", "delete_face": "Slett ansik", "delete_key": "Slett nøkkel", "delete_library": "Slett bibliotek", "delete_link": "Slett lenke", "delete_local_action_prompt": "{count} slettet lokalt", - "delete_local_dialog_ok_backed_up_only": "Slett kun sikkerhetskopierte objekter", + "delete_local_dialog_ok_backed_up_only": "Slett kun sikkerhetskopierte elementer", "delete_local_dialog_ok_force": "Slett uansett", "delete_others": "Slett andre", "delete_permanently": "Slett permanent", @@ -806,7 +832,7 @@ "delete_shared_link": "Slett delt lenke", "delete_shared_link_dialog_title": "Slett delt link", "delete_tag": "Slett tag", - "delete_tag_confirmation_prompt": "Er du sikker på at du vil slette {tagName} tag?", + "delete_tag_confirmation_prompt": "Vil du virkelig slette {tagName} tag?", "delete_user": "Slett bruker", "deleted_shared_link": "Slettet delt lenke", "deletes_missing_assets": "Slett eiendeler som mangler fra disk", @@ -831,7 +857,7 @@ "documentation": "Dokumentasjon", "done": "Ferdig", "download": "Last ned", - "download_action_prompt": "Laster ned {count} objekter", + "download_action_prompt": "Laster ned {count} elementer", "download_canceled": "Nedlasting avbrutt", "download_complete": "Nedlasting fullført", "download_enqueue": "Nedlasting satt i kø", @@ -880,7 +906,6 @@ "edit_tag": "Rediger etikett", "edit_title": "Rediger tittel", "edit_user": "Rediger bruker", - "edited": "Redigert", "editor": "Redaktør", "editor_close_without_save_prompt": "Endringene vil ikke bli lagret", "editor_close_without_save_title": "Lukk redigering?", @@ -890,7 +915,7 @@ "email_notifications": "Epostvarsler", "empty_folder": "Denne mappen er tom", "empty_trash": "Tøm papirkurv", - "empty_trash_confirmation": "Er du sikker på at du vil tømme søppelbøtta? Dette vil slette alle filene i søppelbøtta permanent fra Immich.\nDu kan ikke angre denne handlingen!", + "empty_trash_confirmation": "Vil du virkelig Tømme søppelbøtta? Dette vil slette alle filene i søppelbøtta permanent fra Immich.\nDu kan ikke angre denne handlingen!", "enable": "Aktivere", "enable_backup": "Aktiver backup", "enable_biometric_auth_description": "Skriv inn PINkoden for å aktivere biometrisk autentisering", @@ -901,7 +926,7 @@ "enter_your_pin_code": "Skriv inn din PIN kode", "enter_your_pin_code_subtitle": "Skriv inn din PIN kode for å få tilgang til låst mappe", "error": "Feil", - "error_change_sort_album": "Feilet ved endring av sorteringsrekkefølge på albumer", + "error_change_sort_album": "Mislyktes ved endring av sorteringsrekkefølge på album", "error_delete_face": "Feil ved sletting av ansikt fra aktivia", "error_getting_places": "Feil ved henting av steder", "error_loading_image": "Feil ved lasting av bilde", @@ -910,16 +935,16 @@ "error_tag_face_bounding_box": "Feil ved merking av ansikt - klarte ikke å få koordinatene på omrisset", "error_title": "Feil - Noe gikk galt", "errors": { - "cannot_navigate_next_asset": "Kan ikke navigere til neste fil", - "cannot_navigate_previous_asset": "Kan ikke navigere til forrige fil", - "cant_apply_changes": "Kan ikke legge til endringene", - "cant_change_activity": "Kan ikke {enabled, select, true {disable} other {enable}} aktivitet", - "cant_change_asset_favorite": "Kan ikke endre favoritt til filen", - "cant_change_metadata_assets_count": "Kan ikke endre metadata for {count, plural, one {# objekt} other {# objekter}}", - "cant_get_faces": "Kan ikke finne ansikter", - "cant_get_number_of_comments": "Kan ikke hente antall kommentarer", - "cant_search_people": "Kan ikke søke etter mennesker", - "cant_search_places": "Kan ikke søke etter plasser", + "cannot_navigate_next_asset": "Kunne ikke navigere til neste fil", + "cannot_navigate_previous_asset": "Kunne ikke navigere til forrige fil", + "cant_apply_changes": "Kunne ikke legge til endringene", + "cant_change_activity": "Kunne ikke {enabled, select, true {disable} other {enable}} aktivitet", + "cant_change_asset_favorite": "Kunne ikke endre favoritt til filen", + "cant_change_metadata_assets_count": "Kunne ikke endre metadata for {count, plural, one {# element} other {# elementer}}", + "cant_get_faces": "Kunne ikke finne ansikter", + "cant_get_number_of_comments": "Kunne ikke hente antall kommentarer", + "cant_search_people": "Kunne ikke søke etter mennesker", + "cant_search_places": "Kunne ikke søke etter plasser", "error_adding_assets_to_album": "Feil med å legge til bilder til album", "error_adding_users_to_album": "Feil, kan ikke legge til brukere til album", "error_deleting_shared_user": "Feil med å slette delt bruker", @@ -930,17 +955,17 @@ "exclusion_pattern_already_exists": "Dette eksklusjonsmønsteret eksisterer allerede.", "failed_to_create_album": "Feil med å lage album", "failed_to_create_shared_link": "Feil med å lage delt lenke", - "failed_to_edit_shared_link": "Feilet med å redigere delt lenke", - "failed_to_get_people": "Feilet med å finne mennesker", - "failed_to_keep_this_delete_others": "Feilet med å beholde dette bilde og slette de andre", - "failed_to_load_asset": "Feilet med å laste bilder", - "failed_to_load_assets": "Feilet med å laste bilde", + "failed_to_edit_shared_link": "Mislyktes med å redigere delt lenke", + "failed_to_get_people": "Mislyktes med å finne mennesker", + "failed_to_keep_this_delete_others": "Mislyktes med å beholde dette bilde og slette de andre", + "failed_to_load_asset": "Mislyktes med å laste bilder", + "failed_to_load_assets": "Mislyktes med å laste bilde", "failed_to_load_notifications": "Kunne ikke laste inn varsler", "failed_to_load_people": "Feilen med å laste mennesker", - "failed_to_remove_product_key": "Feilet med å ta bort produkt nøkkel", + "failed_to_remove_product_key": "Mislyktes med å ta bort produkt nøkkel", "failed_to_reset_pin_code": "Kunne ikke tilbakestille PIN-koden", - "failed_to_stack_assets": "Feilet med å stable bilder", - "failed_to_unstack_assets": "Feilet med å avstable bilder", + "failed_to_stack_assets": "Mislyktes med å stable bilder", + "failed_to_unstack_assets": "Mislyktes med å avstable bilder", "failed_to_update_notification_status": "Kunne ikke oppdatere varslingsstatusen", "import_path_already_exists": "Denne importstien eksisterer allerede.", "incorrect_email_or_password": "Feil epost eller passord", @@ -948,87 +973,87 @@ "profile_picture_transparent_pixels": "Profil bilde kan ikke ha gjennomsiktige piksler. Vennligst zoom inn og/eller flytt bilde.", "quota_higher_than_disk_size": "Du har satt kvoten større enn diskstørrelsen", "something_went_wrong": "Noe gikk galt", - "unable_to_add_album_users": "Kan ikke legge til brukere i albumet", - "unable_to_add_assets_to_shared_link": "Kan ikke legge til bilder til delt lenke", - "unable_to_add_comment": "Kan ikke legge til kommentar", - "unable_to_add_exclusion_pattern": "Kan ikke legge til eksklusjonsmønster", - "unable_to_add_import_path": "Kan ikke legge til importsti", - "unable_to_add_partners": "Kan ikke legge til partnere", - "unable_to_add_remove_archive": "Kan ikke {archived, select, true {fjerne objekt fra} other {flytte objekt til}} arkivet", - "unable_to_add_remove_favorites": "Kan ikke {favorite, select, true {legge til objekt til} other {fjerne objekt fra}} favoritter", - "unable_to_archive_unarchive": "Kan ikke {archived, select, true {archive} other {unarchive}}", - "unable_to_change_album_user_role": "Kan ikke endre brukerens rolle i albumet", - "unable_to_change_date": "Kan ikke endre dato", + "unable_to_add_album_users": "Kunne ikke legge til brukere i albumet", + "unable_to_add_assets_to_shared_link": "Kunne ikke legge til bilder til delt lenke", + "unable_to_add_comment": "Kunne ikke legge til kommentar", + "unable_to_add_exclusion_pattern": "Kunne ikke legge til eksklusjonsmønster", + "unable_to_add_import_path": "Kunne ikke legge til importsti", + "unable_to_add_partners": "Kunne ikke legge til partnere", + "unable_to_add_remove_archive": "Kunne ikke {archived, select, true {fjerne element fra} other {flytte element til}} arkivet", + "unable_to_add_remove_favorites": "Kunne ikke {favorite, select, true {legge til element til} other {fjerne element fra}} favoritter", + "unable_to_archive_unarchive": "Kunne ikke {archived, select, true {archive} other {unarchive}}", + "unable_to_change_album_user_role": "Kunne ikke endre brukerens rolle i albumet", + "unable_to_change_date": "Kunne ikke endre dato", "unable_to_change_description": "Klarte ikke å oppdatere beskrivelse", - "unable_to_change_favorite": "Kan ikke endre favoritt for bildet", - "unable_to_change_location": "Kan ikke endre plassering", - "unable_to_change_password": "Kan ikke endre passord", - "unable_to_change_visibility": "Kan ikke endre synlighet for {count, plural, one {# person} other {# people}}", + "unable_to_change_favorite": "Kunne ikke endre favoritt for bildet", + "unable_to_change_location": "Kunne ikke endre plassering", + "unable_to_change_password": "Kunne ikke endre passord", + "unable_to_change_visibility": "Kunne ikke endre synlighet for {count, plural, one {# person} other {# people}}", "unable_to_complete_oauth_login": "Kunne ikke fullføre OAuth innlogging", - "unable_to_connect": "Kan ikke koble til", - "unable_to_copy_to_clipboard": "Kan ikke kopiere til utklippstavlen, sørg for at du får tilgang til siden via HTTPS", - "unable_to_create_admin_account": "Kan ikke opprette administrator bruker", - "unable_to_create_api_key": "Kan ikke opprette en ny API-nøkkel", - "unable_to_create_library": "Kan ikke opprette bibliotek", - "unable_to_create_user": "Kan ikke opprette bruker", - "unable_to_delete_album": "Kan ikke slette album", - "unable_to_delete_asset": "Kan ikke slette filen", + "unable_to_connect": "Kunne ikke koble til", + "unable_to_copy_to_clipboard": "Kunne ikke kopiere til utklippstavlen, sørg for at du får tilgang til siden via HTTPS", + "unable_to_create_admin_account": "Kunne ikke opprette administrator bruker", + "unable_to_create_api_key": "Kunne ikke opprette en ny API-nøkkel", + "unable_to_create_library": "Kunne ikke opprette bibliotek", + "unable_to_create_user": "Kunne ikke opprette bruker", + "unable_to_delete_album": "Kunne ikke slette album", + "unable_to_delete_asset": "Kunne ikke slette filen", "unable_to_delete_assets": "Feil med å slette bilde", - "unable_to_delete_exclusion_pattern": "Kan ikke slette eksklusjonsmønster", - "unable_to_delete_import_path": "Kan ikke slette importsti", - "unable_to_delete_shared_link": "Kan ikke slette delt lenke", - "unable_to_delete_user": "Kan ikke slette bruker", - "unable_to_download_files": "Kan ikke laste ned filer", - "unable_to_edit_exclusion_pattern": "Kan ikke redigere eksklusjonsmønster", - "unable_to_edit_import_path": "Kan ikke redigere importsti", - "unable_to_empty_trash": "Kan ikke tømme papirkurven", - "unable_to_enter_fullscreen": "Kan ikke gå inn i fullskjerm", - "unable_to_exit_fullscreen": "Kan ikke gå ut fra fullskjerm", - "unable_to_get_comments_number": "Kan ikke hente antall kommentarer", - "unable_to_get_shared_link": "Kan ikke hente delt lenke", - "unable_to_hide_person": "Kan ikke skjule person", - "unable_to_link_motion_video": "Kan ikke lenke bevegelig video", - "unable_to_link_oauth_account": "Kan ikke lenke til OAuth-konto", - "unable_to_log_out_all_devices": "Kan ikke logge ut fra alle enheter", - "unable_to_log_out_device": "Kan ikke logge ut av enhet", - "unable_to_login_with_oauth": "Kan ikke logge inn med OAuth", - "unable_to_play_video": "Kan ikke spille av video", + "unable_to_delete_exclusion_pattern": "Kunne ikke slette eksklusjonsmønster", + "unable_to_delete_import_path": "Kunne ikke slette importsti", + "unable_to_delete_shared_link": "Kunne ikke slette delt lenke", + "unable_to_delete_user": "Kunne ikke slette bruker", + "unable_to_download_files": "Kunne ikke laste ned filer", + "unable_to_edit_exclusion_pattern": "Kunne ikke redigere eksklusjonsmønster", + "unable_to_edit_import_path": "Kunne ikke redigere importsti", + "unable_to_empty_trash": "Kunne ikke Tømme papirkurven", + "unable_to_enter_fullscreen": "Kunne ikke gå inn i fullskjerm", + "unable_to_exit_fullscreen": "Kunne ikke gå ut fra fullskjerm", + "unable_to_get_comments_number": "Kunne ikke hente antall kommentarer", + "unable_to_get_shared_link": "Kunne ikke hente delt lenke", + "unable_to_hide_person": "Kunne ikke skjule person", + "unable_to_link_motion_video": "Kunne ikke lenke bevegelig video", + "unable_to_link_oauth_account": "Kunne ikke lenke til OAuth-konto", + "unable_to_log_out_all_devices": "Kunne ikke logge ut fra alle enheter", + "unable_to_log_out_device": "Kunne ikke logge ut av enhet", + "unable_to_login_with_oauth": "Kunne ikke logge inn med OAuth", + "unable_to_play_video": "Kunne ikke spille av video", "unable_to_reassign_assets_existing_person": "Kunne ikke endre bruker på bildene til {name, select, null {an existing person} other {{name}}}", "unable_to_reassign_assets_new_person": "Kunne ikke tildele bildene til en ny person", - "unable_to_refresh_user": "Kan ikke oppdatere bruker", - "unable_to_remove_album_users": "Kan ikke fjerne brukere fra album", - "unable_to_remove_api_key": "Kan ikke fjerne API-nøkkel", + "unable_to_refresh_user": "Kunne ikke oppdatere bruker", + "unable_to_remove_album_users": "Kunne ikke fjerne brukere fra album", + "unable_to_remove_api_key": "Kunne ikke fjerne API-nøkkel", "unable_to_remove_assets_from_shared_link": "Kunne ikke fjerne bilder fra delt lenke", - "unable_to_remove_library": "Kan ikke fjerne bibliotek", - "unable_to_remove_partner": "Kan ikke fjerne partner", - "unable_to_remove_reaction": "Kan ikke fjerne reaksjon", - "unable_to_reset_password": "Kan ikke tilbakestille passord", + "unable_to_remove_library": "Kunne ikke fjerne bibliotek", + "unable_to_remove_partner": "Kunne ikke fjerne partner", + "unable_to_remove_reaction": "Kunne ikke fjerne reaksjon", + "unable_to_reset_password": "Kunne ikke tilbakestille passord", "unable_to_reset_pin_code": "Klarte ikke å resette PIN kode", - "unable_to_resolve_duplicate": "Kan ikke løse duplikat", - "unable_to_restore_assets": "Kan ikke gjenopprette filer", - "unable_to_restore_trash": "Kan ikke gjenopprette papirkurven", - "unable_to_restore_user": "Kan ikke gjenopprette bruker", - "unable_to_save_album": "Kan ikke lagre album", - "unable_to_save_api_key": "Kan ikke lagre API-nøkkel", + "unable_to_resolve_duplicate": "Kunne ikke løse duplikat", + "unable_to_restore_assets": "Kunne ikke gjenopprette filer", + "unable_to_restore_trash": "Kunne ikke gjenopprette papirkurven", + "unable_to_restore_user": "Kunne ikke gjenopprette bruker", + "unable_to_save_album": "Kunne ikke lagre album", + "unable_to_save_api_key": "Kunne ikke lagre API-nøkkel", "unable_to_save_date_of_birth": "Kunne ikke lagre bursdag", - "unable_to_save_name": "Kan ikke lagre navn", - "unable_to_save_profile": "Kan ikke lagre profil", - "unable_to_save_settings": "Kan ikke lagre instillinger", - "unable_to_scan_libraries": "Kan ikke skanne biblioteker", - "unable_to_scan_library": "Kan ikke skanne bibliotek", + "unable_to_save_name": "Kunne ikke lagre navn", + "unable_to_save_profile": "Kunne ikke lagre profil", + "unable_to_save_settings": "Kunne ikke lagre instillinger", + "unable_to_scan_libraries": "Kunne ikke skanne biblioteker", + "unable_to_scan_library": "Kunne ikke skanne bibliotek", "unable_to_set_feature_photo": "Kunne ikke sette funksjonsbilde", - "unable_to_set_profile_picture": "Kan ikke sette profilbilde", - "unable_to_submit_job": "Kan ikke sende inn jobb", - "unable_to_trash_asset": "Kan ikke flytte filen til papirkurven", - "unable_to_unlink_account": "Kan ikke fjerne kobling til konto", + "unable_to_set_profile_picture": "Kunne ikke sette profilbilde", + "unable_to_submit_job": "Kunne ikke sende inn jobb", + "unable_to_trash_asset": "Kunne ikke flytte filen til papirkurven", + "unable_to_unlink_account": "Kunne ikke fjerne kobling til konto", "unable_to_unlink_motion_video": "Kunne ikke ta på kobling på bevegelig video", "unable_to_update_album_cover": "Kunne ikke oppdatere album bilde", "unable_to_update_album_info": "Kunne ikke oppdatere informasjon i album", - "unable_to_update_library": "Kan ikke oppdatere bibliotek", - "unable_to_update_location": "Kan ikke oppdatere plassering", - "unable_to_update_settings": "Kan ikke oppdatere innstillinger", - "unable_to_update_timeline_display_status": "Kan ikke oppdatere visningsstatus for tidslinje", - "unable_to_update_user": "Kan ikke oppdatere bruker", + "unable_to_update_library": "Kunne ikke oppdatere bibliotek", + "unable_to_update_location": "Kunne ikke oppdatere plassering", + "unable_to_update_settings": "Kunne ikke oppdatere innstillinger", + "unable_to_update_timeline_display_status": "Kunne ikke oppdatere visningsstatus for tidslinje", + "unable_to_update_user": "Kunne ikke oppdatere bruker", "unable_to_upload_file": "Kunne ikke laste opp fil" }, "exif": "EXIF", @@ -1036,6 +1061,7 @@ "exif_bottom_sheet_description_error": "Feil ved oppdatering av beskrivelsen", "exif_bottom_sheet_details": "DETALJER", "exif_bottom_sheet_location": "PLASSERING", + "exif_bottom_sheet_no_description": "Ingen beskrivelse", "exif_bottom_sheet_people": "MENNESKER", "exif_bottom_sheet_person_add_person": "Legg til navn", "exit_slideshow": "Avslutt lysbildefremvisning", @@ -1059,21 +1085,22 @@ "external_network": "Eksternt nettverk", "external_network_sheet_info": "Når du ikke er på det foretrukne Wi-Fi-nettverket, vil appen koble seg til serveren via den første av URL-ene nedenfor den kan nå, fra topp til bunn", "face_unassigned": "Ikke tilordnet", - "failed": "Feilet", + "failed": "Mislyktes", "failed_to_authenticate": "Kunne ikke autentisere", - "failed_to_load_assets": "Feilet med å laste fil", + "failed_to_load_assets": "Mislyktes med å laste fil", "failed_to_load_folder": "Kunne ikke laste inn mappe", "favorite": "Favoritt", "favorite_action_prompt": "{count} lagt til i favoritter", "favorite_or_unfavorite_photo": "Merk som favoritt eller fjern som favoritt", "favorites": "Favoritter", - "favorites_page_no_favorites": "Ingen favorittobjekter funnet", + "favorites_page_no_favorites": "Ingen favorittelementer funnet", "feature_photo_updated": "Fremhevet bilde oppdatert", "features": "Funksjoner", "features_in_development": "Funksjoner under utvikling", "features_setting_description": "Administrerer funksjoner for appen", "file_name": "Filnavn", "file_name_or_extension": "Filnavn eller filtype", + "file_size": "Filstørrelse", "filename": "Filnavn", "filetype": "Filtype", "filter": "Filter", @@ -1091,7 +1118,7 @@ "gcast_enabled": "Google Cast", "gcast_enabled_description": "Denne funksjonen laster eksterne ressurser fra Google for å fungere.", "general": "Generelt", - "geolocation_instruction_location": "Klikk på et objekt med GPS-koordinater for å bruke posisjonen, eller velg en posisjon direkte fra kartet", + "geolocation_instruction_location": "Klikk på et element med GPS-koordinater for å bruke posisjonen, eller velg en posisjon direkte fra kartet", "get_help": "Få Hjelp", "get_wifiname_error": "Kunne ikke hente Wi-Fi-navnet. Sørg for at du har gitt de nødvendige tillatelsene og er koblet til et Wi-Fi-nettverk", "getting_started": "Kom i gang", @@ -1110,14 +1137,13 @@ "haptic_feedback_switch": "Aktivert haptisk tilbakemelding", "haptic_feedback_title": "Haptisk tilbakemelding", "has_quota": "Kvote", - "hash_asset": "Hash objekter", - "hashed_assets": "Hashede objekter", + "hash_asset": "Hash elementer", + "hashed_assets": "Hashede elementer", "hashing": "Hasher", "header_settings_add_header_tip": "Legg til header", "header_settings_field_validator_msg": "Verdi kan ikke være null", "header_settings_header_name_input": "Header navn", "header_settings_header_value_input": "Header verdi", - "headers_settings_tile_subtitle": "Definer proxy headere som appen skal benytte ved enhver nettverksrequest", "headers_settings_tile_title": "Egendefinerte proxy headere", "hi_user": "Hei {name} ({email})", "hide_all_people": "Skjul alle mennesker", @@ -1126,22 +1152,22 @@ "hide_password": "Skjul passord", "hide_person": "Skjul person", "hide_unnamed_people": "Skjul mennesker uten navn", - "home_page_add_to_album_conflicts": "Lagt til {added} objekter til album {album}. {failed} objekter er allerede i albumet.", - "home_page_add_to_album_err_local": "Kan ikke legge til lokale objekter til album enda, hopper over", - "home_page_add_to_album_success": "Lagt til {added} objekter til album {album}.", - "home_page_album_err_partner": "Kan ikke legge til partnerobjekter i album enda, hopper over", - "home_page_archive_err_local": "Kan ikke arkivere lokale objekter enda, hopper over", - "home_page_archive_err_partner": "Kan ikke arkivere partnerobjekter, hopper over", + "home_page_add_to_album_conflicts": "Lagt til {added} elementer til album {album}. {failed} elementer er allerede i albumet.", + "home_page_add_to_album_err_local": "Kunne ikke legge til lokale elementer til album enda, hopper over", + "home_page_add_to_album_success": "Lagt til {added} elementer til album {album}.", + "home_page_album_err_partner": "Kunne ikke legge til partnerelementer i album enda, hopper over", + "home_page_archive_err_local": "Kunne ikke arkivere lokale elementer enda, hopper over", + "home_page_archive_err_partner": "Kunne ikke arkivere partnerelementer, hopper over", "home_page_building_timeline": "Genererer tidslinjen", - "home_page_delete_err_partner": "Kan ikke slette partnerobjekter, hopper over", - "home_page_delete_remote_err_local": "Lokale objekter i fjernslettingsvalgene, hopper over", - "home_page_favorite_err_local": "Kan ikke sette favoritt på lokale objekter enda, hopper over", - "home_page_favorite_err_partner": "Kan ikke merke partnerobjekter som favoritt enda, hopper over", + "home_page_delete_err_partner": "Kunne ikke slette partnerelementer, hopper over", + "home_page_delete_remote_err_local": "Lokale elementer i fjernslettingsvalgene, hopper over", + "home_page_favorite_err_local": "Kunne ikke sette favoritt på lokale elementer enda, hopper over", + "home_page_favorite_err_partner": "Kunne ikke merke partnerelementer som favoritt enda, hopper over", "home_page_first_time_notice": "Hvis dette er første gangen du benytter appen, velg et album (eller flere) for sikkerhetskopiering, slik at tidslinjen kan fylles med dine bilder og videoer", - "home_page_locked_error_local": "Kunne ikke flytte lokale objekter til låst mappe, hopper over", - "home_page_locked_error_partner": "Kunne ikke flytte partner objekter til låst mappe, hopper over", - "home_page_share_err_local": "Kan ikke dele lokale objekter via link, hopper over", - "home_page_upload_err_limit": "Maksimalt 30 objekter kan lastes opp om gangen, hopper over", + "home_page_locked_error_local": "Kunne ikke flytte lokale elementer til låst mappe, hopper over", + "home_page_locked_error_partner": "Kunne ikke flytte partner elementer til låst mappe, hopper over", + "home_page_share_err_local": "Kunne ikke dele lokale elementer via link, hopper over", + "home_page_upload_err_limit": "Maksimalt 30 elementer kan lastes opp om gangen, hopper over", "host": "Vert", "hour": "Time", "hours": "Timer", @@ -1197,7 +1223,7 @@ "keep": "Behold", "keep_all": "Behold alle", "keep_this_delete_others": "Behold denne, slett de andre", - "kept_this_deleted_others": "Behold denne filen og slett {count, plural, one {# objekt} other {# objekter}}", + "kept_this_deleted_others": "Behold denne filen og slett {count, plural, one {# element} other {# elementer}}", "keyboard_shortcuts": "Tastatursnarveier", "language": "Språk", "language_no_results_subtitle": "Prøv å endre søkeord", @@ -1216,9 +1242,9 @@ "level": "Nivå", "library": "Bibliotek", "library_options": "Bibliotekalternativer", - "library_page_device_albums": "Albumer på enheten", + "library_page_device_albums": "Album på enheten", "library_page_new_album": "Nytt album", - "library_page_sort_asset_count": "Antall objekter", + "library_page_sort_asset_count": "Antall elementer", "library_page_sort_created": "Nylig opplastet", "library_page_sort_last_modified": "Sist endret", "library_page_sort_title": "Albumtittel", @@ -1233,11 +1259,12 @@ "loading": "Laster", "loading_search_results_failed": "Klarte ikke å laste inn søkeresultater", "local": "Lokal", - "local_asset_cast_failed": "Kan ikke caste et bilde som ikke er lastet opp til serveren", - "local_assets": "Lokale objekter", + "local_asset_cast_failed": "Kunne ikke caste et bilde som ikke er lastet opp til serveren", + "local_assets": "Lokale elementer", "local_media_summary": "Oppsummering av lokale media", "local_network": "Lokalt nettverk", "local_network_sheet_info": "Appen vil koble til serveren via denne URL-en når du bruker det angitte Wi-Fi-nettverket", + "location": "Lokasjon", "location_permission": "Stedstillatelse", "location_permission_content": "For å bruke funksjonen for automatisk veksling trenger Immich nøyaktig plasseringstillatelse slik at den kan lese navnet på det gjeldende Wi-Fi-nettverket", "location_picker_choose_on_map": "Velg på kart", @@ -1272,12 +1299,12 @@ "login_form_password_hint": "passord", "login_form_save_login": "Forbli innlogget", "login_form_server_empty": "Skriv inn en server-URL.", - "login_form_server_error": "Kan ikke koble til server.", + "login_form_server_error": "Kunne ikke koble til server.", "login_has_been_disabled": "Innlogging har blitt deaktivert.", "login_password_changed_error": "Det skjedde en feil ved oppdatering av passordet", "login_password_changed_success": "Passord oppdatert", - "logout_all_device_confirmation": "Er du sikker på at du vil logge ut av alle enheter?", - "logout_this_device_confirmation": "Er du sikker på at du vil logge ut av denne enheten?", + "logout_all_device_confirmation": "Vil du virkelig logge ut av alle enheter?", + "logout_this_device_confirmation": "Vil du virkelig logge ut av denne enheten?", "logs": "Logger", "longitude": "Lengdegrad", "look": "Se", @@ -1296,14 +1323,14 @@ "manage_your_oauth_connection": "Administrer tilkoblingen din med OAuth", "map": "Kart", "map_assets_in_bounds": "{count, plural, =0 {Ingen bilder i dette området} one {# photo} other {# photos}}", - "map_cannot_get_user_location": "Kan ikke hente brukerlokasjon", + "map_cannot_get_user_location": "Kunne ikke hente brukerlokasjon", "map_location_dialog_yes": "Ja", "map_location_picker_page_use_location": "Bruk denne lokasjonen", - "map_location_service_disabled_content": "Lokasjonstjeneste må være aktivert for å vise objekter fra din nåværende lokasjon. Vil du aktivere det nå?", + "map_location_service_disabled_content": "Lokasjonstjeneste må være aktivert for å vise elementer fra din nåværende lokasjon. Vil du aktivere det nå?", "map_location_service_disabled_title": "Lokasjonstjeneste deaktivert", "map_marker_for_images": "Kart makeringer for bilder tatt i {city}, {country}", "map_marker_with_image": "Kartmarkør med bilde", - "map_no_location_permission_content": "Lokasjonstilgang er påkrevet for å vise objekter fra din nåværende lokasjon. Vil du tillate det nå?", + "map_no_location_permission_content": "Lokasjonstilgang er påkrevet for å vise elementer fra din nåværende lokasjon. Vil du tillate det nå?", "map_no_location_permission_title": "Lokasjonstilgang avvist", "map_settings": "Kartinnstillinger", "map_settings_dark_mode": "Mørk modus", @@ -1321,7 +1348,7 @@ "mark_as_read": "Merk som lest", "marked_all_as_read": "Merket alle som lest", "matches": "Samsvarende", - "matching_assets": "Matchende objekter", + "matching_assets": "Matchende elementer", "media_type": "Mediatype", "memories": "Minner", "memories_all_caught_up": "Alt utført", @@ -1342,6 +1369,8 @@ "minute": "Minutt", "minutes": "Minutter", "missing": "Mangler", + "mobile_app": "Mobilapp", + "mobile_app_download_onboarding_note": "Last ned den tilhørende mobilappen ved å bruke følgende alternativer", "model": "Modell", "month": "Måned", "monthly_title_text_date_format": "MMMM y", @@ -1350,16 +1379,18 @@ "move_off_locked_folder": "Flytt ut av låst mappe", "move_to_lock_folder_action_prompt": "{count} lagt til i låst mappe", "move_to_locked_folder": "Flytt til låst mappe", - "move_to_locked_folder_confirmation": "Disse bildene og videoene vil bli fjernet fra alle albumer, og kun tilgjengelige via den låste mappen", - "moved_to_archive": "Flyttet {count, plural, one {# objekt} other {# objekter}} til arkivet", - "moved_to_library": "Flyttet {count, plural, one {# objekt} other {# objekter}} til biblioteket", + "move_to_locked_folder_confirmation": "Disse bildene og videoene vil bli fjernet fra alle album, og kun tilgjengelige via den låste mappen", + "moved_to_archive": "Flyttet {count, plural, one {# element} other {# elementer}} til arkivet", + "moved_to_library": "Flyttet {count, plural, one {# element} other {# elementer}} til biblioteket", "moved_to_trash": "Flyttet til papirkurven", - "multiselect_grid_edit_date_time_err_read_only": "Kan ikke endre dato på objekt(er) med kun lese-rettigheter, hopper over", - "multiselect_grid_edit_gps_err_read_only": "Kan ikke endre lokasjon på objekt(er) med kun lese-rettigheter, hopper over", + "multiselect_grid_edit_date_time_err_read_only": "Kunne ikke endre dato på element(er) med kun lese-rettigheter, hopper over", + "multiselect_grid_edit_gps_err_read_only": "Kunne ikke endre lokasjon på element(er) med kun lese-rettigheter, hopper over", "mute_memories": "Demp minner", "my_albums": "Mine album", "name": "Navn", "name_or_nickname": "Navn eller kallenavn", + "navigate": "Naviger", + "navigate_to_time": "Naviger til tid", "network_requirement_photos_upload": "Bruk mobildata for backup av bilder", "network_requirement_videos_upload": "Bruk mobildata for backup av videoer", "network_requirements": "Nettverkskrav", @@ -1369,6 +1400,7 @@ "never": "aldri", "new_album": "Nytt Album", "new_api_key": "Ny API-nøkkel", + "new_date_range": "Nytt datointervall", "new_password": "Nytt passord", "new_person": "Ny person", "new_pin_code": "Ny PIN-kode", @@ -1385,22 +1417,22 @@ "no_albums_yet": "Det ser ut som om du ikke har noen album enda.", "no_archived_assets_message": "Arkiver bilder og videoer for å skjule dem fra visningen av bildene dine", "no_assets_message": "KLIKK FOR Å LASTE OPP DITT FØRSTE BILDE", - "no_assets_to_show": "Ingen objekter å vise", + "no_assets_to_show": "Ingen elementer å vise", "no_cast_devices_found": "Ingen caste-enheter oppdaget", - "no_checksum_local": "Ingen sjekksum tilgjengelig - Kan ikke hente lokale objekter", - "no_checksum_remote": "Ingen sjekksum tilgjengelig - Kan ikke hente eksterne objekter", + "no_checksum_local": "Ingen sjekksum tilgjengelig - Kunne ikke hente lokale elementer", + "no_checksum_remote": "Ingen sjekksum tilgjengelig - Kunne ikke hente eksterne elementer", "no_duplicates_found": "Ingen duplikater ble funnet.", "no_exif_info_available": "Ingen EXIF-informasjon tilgjengelig", "no_explore_results_message": "Last opp flere bilder for å utforske samlingen din.", "no_favorites_message": "Legg til favoritter for å finne dine beste bilder og videoer raskt", "no_libraries_message": "Opprett et eksternt bibliotek for å se bildene og videoene dine", - "no_local_assets_found": "Ingen lokale objekter funnet med denne sjekksummen", + "no_local_assets_found": "Ingen lokale elementer funnet med denne sjekksummen", "no_locked_photos_message": "Bilder og videoer i den låste mappen er skjult og vil ikke vises når du blar i biblioteket.", "no_name": "Ingen navn", "no_notifications": "Ingen varsler", "no_people_found": "Ingen samsvarende personer funnet", "no_places": "Ingen steder", - "no_remote_assets_found": "Ingen eksterne objekter funnet med denne sjekksummen", + "no_remote_assets_found": "Ingen eksterne elementer funnet med denne sjekksummen", "no_results": "Ingen resultater", "no_results_description": "Prøv et synonym eller mer generelt søkeord", "no_shared_albums_message": "Opprett et album for å dele bilder og videoer med personer i nettverket ditt", @@ -1419,6 +1451,9 @@ "notifications": "Notifikasjoner", "notifications_setting_description": "Administrer varsler", "oauth": "OAuth", + "obtainium_configurator": "Obtainium konfigurator", + "obtainium_configurator_instructions": "Bruk Obtainium for å installere og oppdatere Android appen direkte fra Immich sin Github utgivelse. Opprett en API nøkkel og velg en variant for å lage din Obtainium konfigurasjonslink", + "ocr": "OCR", "official_immich_resources": "Offisielle Immich-ressurser", "offline": "Frakoblet", "offset": "Forskyving", @@ -1440,13 +1475,13 @@ "open_the_search_filters": "Åpne søkefiltrene", "options": "Valg", "or": "eller", - "organize_into_albums": "Organiser til albumer", - "organize_into_albums_description": "Plasser eksisterende bilder i albumer ved å bruke synkroniseringsinnstillinger", + "organize_into_albums": "Organiser til album", + "organize_into_albums_description": "Plasser eksisterende bilder i album ved å bruke synkroniseringsinnstillinger", "organize_your_library": "Organiser biblioteket ditt", "original": "original", "other": "Annet", "other_devices": "Andre enheter", - "other_entities": "Andre objekter", + "other_entities": "Andre elementer", "other_variables": "Andre variabler", "owned": "Dine", "owner": "Eier", @@ -1479,17 +1514,17 @@ "pause_memories": "Sett minner på pause", "paused": "Satt på pause", "pending": "Avventer", - "people": "Folk", + "people": "Personer", "people_edits_count": "Endret {count, plural, one {# person} other {# people}}", "people_feature_description": "Utforsk bilder og videoer gruppert etter mennesker", "people_sidebar_description": "Vis en lenke til Personer i sidepanelet", "permanent_deletion_warning": "Advarsel om permanent sletting", "permanent_deletion_warning_setting_description": "Vis en advarsel ved permanent sletting av filer", "permanently_delete": "Slett permanent", - "permanently_delete_assets_count": "Slett {count, plural, one {objekt} other {objekter}} permanent", - "permanently_delete_assets_prompt": "Er du sikker på at du vil permanent slette {count, plural, one {dette objektet?} other {disse # objektene?}} Dette vil også slette {count, plural, one {det fra dets} other {de fra deres}} album(er).", + "permanently_delete_assets_count": "Slett {count, plural, one {element} other {elementer}} permanent", + "permanently_delete_assets_prompt": "Vil du virkelig permanent slette {count, plural, one {dette elementet?} other {disse # elementene?}} Dette vil også slette {count, plural, one {det fra dets} other {de fra deres}} album(er).", "permanently_deleted_asset": "Filen har blitt permanent slettet", - "permanently_deleted_assets_count": "Permanent slett {count, plural, one {# objekt} other {# objekter}}", + "permanently_deleted_assets_count": "Permanent slett {count, plural, one {# element} other {# elementer}}", "permission": "Tillatelse", "permission_empty": "Dine tillatelser burde ikke være tomme", "permission_onboarding_back": "Tilbake", @@ -1523,10 +1558,14 @@ "play_memories": "Spill av minner", "play_motion_photo": "Spill av bevegelsesbilde", "play_or_pause_video": "Spill av eller pause video", + "play_original_video": "Spill av originalvideo", + "play_original_video_setting_description": "Foretrekk avspilling av originalvideoer istedenfor omkodede videoer. Hvis originalvideo ikke er kompatibel kan avspillingsproblemer oppstå.", + "play_transcoded_video": "Spill av omkodet video", "please_auth_to_access": "Vennligst autentiser for å fortsette", "port": "Port", "preferences_settings_subtitle": "Administrer appens preferanser", "preferences_settings_title": "Innstillinger", + "preparing": "Forbereder", "preset": "Forhåndsinstilling", "preview": "Forhåndsvis", "previous": "Forrige", @@ -1539,13 +1578,9 @@ "privacy": "Privat", "profile": "Profil", "profile_drawer_app_logs": "Logg", - "profile_drawer_client_out_of_date_major": "Mobilapp er utdatert. Vennligst oppdater til nyeste versjon.", - "profile_drawer_client_out_of_date_minor": "Mobilapp er utdatert. Vennligst oppdater til nyeste versjon.", "profile_drawer_client_server_up_to_date": "Klient og server er oppdatert", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Skrivebeskyttet modus er aktivert. Langttrykk på brukerens avatarikon for å avslutte.", - "profile_drawer_server_out_of_date_major": "Server er utdatert. Vennligst oppdater til nyeste versjon.", - "profile_drawer_server_out_of_date_minor": "Server er utdatert. Vennligst oppdater til nyeste versjon.", "profile_image_of_user": "Profil bilde av {user}", "profile_picture_set": "Profilbildet er satt.", "public_album": "Offentlige album", @@ -1561,7 +1596,7 @@ "purchase_button_reminder": "Påminn meg om 30 dager", "purchase_button_remove_key": "Ta bort produktnøkkel", "purchase_button_select": "Velg", - "purchase_failed_activation": "Feilet med å aktivere! Vennligst sjekk eposten for riktig produktnøkkel!", + "purchase_failed_activation": "Mislyktes med å aktivere! Vennligst sjekk eposten for riktig produktnøkkel!", "purchase_individual_description_1": "For en person", "purchase_individual_description_2": "Støttespiller status", "purchase_individual_title": "Individuell", @@ -1575,14 +1610,14 @@ "purchase_per_server": "For hver server", "purchase_per_user": "For hver bruker", "purchase_remove_product_key": "Ta bor Produktnøkkel", - "purchase_remove_product_key_prompt": "Er du sikker på at du vil ta bort produktnøkkelen?", + "purchase_remove_product_key_prompt": "Vil du virkelig ta bort produktnøkkelen?", "purchase_remove_server_product_key": "Ta bort Server Produktnøkkel", - "purchase_remove_server_product_key_prompt": "Er du sikker på at du vil ta bort Server Produktnøkkelen?", + "purchase_remove_server_product_key_prompt": "Vil du virkelig ta bort Server Produktnøkkelen?", "purchase_server_description_1": "For hele serveren", "purchase_server_description_2": "Støttespiller status", "purchase_server_title": "Server", "purchase_settings_server_activated": "Produktnøkkel for server er administrert av administratoren", - "query_asset_id": "Forespør objektID", + "query_asset_id": "Forespør elementID", "queue_status": "Kø {count}/{total}", "rating": "Stjernevurdering", "rating_clear": "Slett vurdering", @@ -1592,9 +1627,10 @@ "read_changelog": "Les endringslogg", "readonly_mode_disabled": "Skrivebeskyttet modus deaktivert", "readonly_mode_enabled": "Skrivebeskyttet modus aktivert", + "ready_for_upload": "Klar for opplasting", "reassign": "Tilordne på nytt", - "reassigned_assets_to_existing_person": "Flyttet {count, plural, one {# objekt} other {# objekter}} to {name, select, null {en eksisterende person} other {{name}}}", - "reassigned_assets_to_new_person": "Flyttet {count, plural, one {# objekt} other {# objekter}} til en ny person", + "reassigned_assets_to_existing_person": "Flyttet {count, plural, one {# element} other {# elementer}} to {name, select, null {en eksisterende person} other {{name}}}", + "reassigned_assets_to_new_person": "Flyttet {count, plural, one {# element} other {# elementer}} til en ny person", "reassing_hint": "Tilordne valgte eiendeler til en eksisterende person", "recent": "Nylig", "recent-albums": "Nylige album", @@ -1615,11 +1651,11 @@ "refreshing_metadata": "Oppdaterer matadata", "regenerating_thumbnails": "Regenererer miniatyrbilder", "remote": "Eksternt", - "remote_assets": "Eksterne objekter", + "remote_assets": "Eksterne elementer", "remote_media_summary": "Oppsummering av eksterne media", "remove": "Fjern", - "remove_assets_album_confirmation": "Er du sikker på at du fil slette {count, plural, one {# objekt} other {# objekter}} fra albumet?", - "remove_assets_shared_link_confirmation": "Er du sikker på at du vil slette {count, plural, one {# objekt} other {# objekter}} fra den delte lenken?", + "remove_assets_album_confirmation": "Er du sikker på at du fil slette {count, plural, one {# element} other {# elementer}} fra albumet?", + "remove_assets_shared_link_confirmation": "Vil du virkelig slette {count, plural, one {# element} other {# elementer}} fra den delte lenken?", "remove_assets_title": "Vil du fjerne eiendeler?", "remove_custom_date_range": "Fjern egendefinert datoperiode", "remove_deleted_assets": "Fjern fra frakoblede filer", @@ -1628,7 +1664,7 @@ "remove_from_favorites": "Fjern fra favoritter", "remove_from_lock_folder_action_prompt": "{count} fjernet fra låst mappe", "remove_from_locked_folder": "Fjern fra låst mappe", - "remove_from_locked_folder_confirmation": "Er du sikker på at du vil flytte disse bildene og videoene ut av den låste mappen? De vil bli synlige i biblioteket.", + "remove_from_locked_folder_confirmation": "Vil du virkelig flytte disse bildene og videoene ut av den låste mappen? De vil bli synlige i biblioteket.", "remove_from_shared_link": "Fjern fra delt lenke", "remove_memory": "Slett minne", "remove_photo_from_memory": "Slett bilde fra dette minne", @@ -1641,7 +1677,7 @@ "removed_from_favorites_count": "{count, plural, other {Removed #}} fra favoritter", "removed_memory": "Slettet minne", "removed_photo_from_memory": "Slettet bilde fra minne", - "removed_tagged_assets": "Fjern tag fra {count, plural, one {# objekt} other {# objekter}}", + "removed_tagged_assets": "Fjern tag fra {count, plural, one {# element} other {# elementer}}", "rename": "Gi nytt navn", "repair": "Reparer", "repair_no_results_message": "Usporrede og savnede filer vil vises her", @@ -1658,14 +1694,15 @@ "reset_pin_code_success": "PIN-koden er tilbakestilt", "reset_pin_code_with_password": "Du kan alltid tilbakestiller PIN-koden med passordet ditt", "reset_sqlite": "Reset SQLite Databasen", - "reset_sqlite_confirmation": "Er du sikker på at du vil resette SQLite databasen? Du blir nødt til å logge ut og inn igjen for å resynkronisere data", + "reset_sqlite_confirmation": "Vil du virkelig resette SQLite databasen? Du blir nødt til å logge ut og inn igjen for å resynkronisere data", "reset_sqlite_success": "Vellykket resetting av SQLite databasen", "reset_to_default": "Tilbakestill til standard", + "resolution": "Oppløsning", "resolve_duplicates": "Løs duplikater", "resolved_all_duplicates": "Løste alle duplikater", "restore": "Gjenopprett", "restore_all": "Gjenopprett alle", - "restore_trash_action_prompt": "{count} gjenopprettet fra søppelbøtten", + "restore_trash_action_prompt": "{count} gjenopprettet fra papirkurven", "restore_user": "Gjenopprett bruker", "restored_asset": "Gjenopprettet ressurs", "resume": "Fortsett", @@ -1679,6 +1716,7 @@ "running": "Kjører", "save": "Lagre", "save_to_gallery": "Lagre til galleriet", + "saved": "Lagret", "saved_api_key": "Lagret API-nøkkel", "saved_profile": "Lagret profil", "saved_settings": "Lagret instillinger", @@ -1695,6 +1733,9 @@ "search_by_description_example": "Turdag i Sapa", "search_by_filename": "Søk etter filnavn og filtype", "search_by_filename_example": "f.eks. IMG_1234.JPG eller PNG", + "search_by_ocr": "Søk etter tekst i bilde", + "search_by_ocr_example": "Latte", + "search_camera_lens_model": "Søk etter objektivmodell...", "search_camera_make": "Søk etter kameramerke...", "search_camera_model": "Søk etter kamera modell...", "search_city": "Søk etter by...", @@ -1711,6 +1752,7 @@ "search_filter_location_title": "Velg lokasjon", "search_filter_media_type": "Medietype", "search_filter_media_type_title": "Velg medietype", + "search_filter_ocr": "Søk med OCR", "search_filter_people_title": "Velg mennesker", "search_for": "Søk etter", "search_for_existing_person": "Søk etter eksisterende person", @@ -1721,7 +1763,7 @@ "search_options": "Søke alternativer", "search_page_categories": "Kategorier", "search_page_motion_photos": "Bevegelige bilder", - "search_page_no_objects": "Ingen objektinfo tilgjengelig", + "search_page_no_objects": "Ingen elementinfo tilgjengelig", "search_page_no_places": "Ingen stedsinformasjon er tilgjengelig", "search_page_screenshots": "Skjermbilder", "search_page_search_photos_videos": "Søk etter dine bilder og videoer", @@ -1760,7 +1802,7 @@ "select_person_to_tag": "Velg en person å tagge", "select_photos": "Velg bilder", "select_trash_all": "Velg å flytte alt til papirkurven", - "select_user_for_sharing_page_err_album": "Feilet ved oppretting av album", + "select_user_for_sharing_page_err_album": "Mislyktes ved oppretting av album", "selected": "Valgt", "selected_count": "{count, plural, other {# valgt}}", "selected_gps_coordinates": "Valgte GPS-koordinater", @@ -1773,6 +1815,7 @@ "server_online": "Server tilkoblet", "server_privacy": "Server personvern", "server_stats": "Serverstatistikk", + "server_update_available": "Serveroppdatering er tilgjengelig", "server_version": "Server Versjon", "set": "Sett", "set_as_album_cover": "Sett som albumomslag", @@ -1796,11 +1839,13 @@ "setting_notifications_notify_minutes": "{count} minutter", "setting_notifications_notify_never": "aldri", "setting_notifications_notify_seconds": "{count} sekunder", - "setting_notifications_single_progress_subtitle": "Detaljert opplastingsinformasjon per objekt", + "setting_notifications_single_progress_subtitle": "Detaljert opplastingsinformasjon per element", "setting_notifications_single_progress_title": "Vis detaljert status på sikkerhetskopiering i bakgrunnen", "setting_notifications_subtitle": "Juster notifikasjonsinnstillinger", - "setting_notifications_total_progress_subtitle": "Total opplastingsstatus (fullført/totalt objekter)", + "setting_notifications_total_progress_subtitle": "Total opplastingsstatus (fullført/totalt elementer)", "setting_notifications_total_progress_title": "Vis status på sikkerhetskopiering i bakgrunnen", + "setting_video_viewer_auto_play_subtitle": "Automatisk avspilling av videoer når de åpnes", + "setting_video_viewer_auto_play_title": "Automatisk avspilling av videoer", "setting_video_viewer_looping_title": "Looping", "setting_video_viewer_original_video_subtitle": "Når det streames en video fra serveren, spill originalkvaliteten selv om en omkodet versjon finnes. Dette kan medføre buffring. Videoer som er lagret lokalt på enheten spilles i originalkvalitet uavhengig av denne innstillingen.", "setting_video_viewer_original_video_title": "Tving original video", @@ -1809,7 +1854,7 @@ "settings_saved": "Innstillinger lagret", "setup_pin_code": "Sett opp en PINkode", "share": "Del", - "share_action_prompt": "Delte {count} objekter", + "share_action_prompt": "Delte {count} elementer", "share_add_photos": "Legg til bilder", "share_assets_selected": "{count} valgt", "share_dialog_preparing": "Forbereder ...", @@ -1843,7 +1888,7 @@ "shared_link_edit_expire_after_option_year": "{count} år", "shared_link_edit_password_hint": "Skriv inn dele-passord", "shared_link_edit_submit_button": "Oppdater link", - "shared_link_error_server_url_fetch": "Kan ikke hente server-url", + "shared_link_error_server_url_fetch": "Kunne ikke hente server-url", "shared_link_expires_day": "Utgår om {count} dag", "shared_link_expires_days": "Utgår om {count} dager", "shared_link_expires_hour": "Utgår om {count} time", @@ -1866,7 +1911,7 @@ "sharing": "Deling", "sharing_enter_password": "Vennligst skriv inn passordet for å se denne siden.", "sharing_page_album": "Delte album", - "sharing_page_description": "Lag delte albumer for å dele bilder og videoer med folk i nettverket ditt.", + "sharing_page_description": "Lag delte album for å dele bilder og videoer med personer i nettverket ditt.", "sharing_page_empty_list": "TOM LISTE", "sharing_sidebar_description": "Vis en lenke til Deling i sidepanelet", "sharing_silver_appbar_create_shared_album": "Lag delt album", @@ -1910,7 +1955,7 @@ "sort_modified": "Dato modifisert", "sort_newest": "Nyeste bilde", "sort_oldest": "Eldste bilde", - "sort_people_by_similarity": "Sorter folk etter likhet", + "sort_people_by_similarity": "Sorter personer etter likhet", "sort_recent": "Nyeste bilde", "sort_title": "Tittel", "source": "Kilde", @@ -1919,7 +1964,7 @@ "stack_duplicates": "Stable duplikater", "stack_select_one_photo": "Velg hovedbilde for bildestabbel", "stack_selected_photos": "Stable valgte bilder", - "stacked_assets_count": "Stable {count, plural, one {# objekt} other {# objekter}}", + "stacked_assets_count": "Stable {count, plural, one {# element} other {# elementer}}", "stacktrace": "Stakkspor", "start": "Start", "start_date": "Startdato", @@ -1944,7 +1989,7 @@ "support_third_party_description": "Immich-installasjonen din ble pakket av en tredjepart. Problemer du opplever kan være forårsaket av den pakken, så vennligst ta opp problemer med dem i første omgang ved å bruke koblingene nedenfor.", "swap_merge_direction": "Bytt retning på sammenslåingen", "sync": "Synkroniser", - "sync_albums": "Synkroniser albumer", + "sync_albums": "Synkroniser album", "sync_albums_manual_subtitle": "Synkroniser alle opplastede videoer og bilder til det valgte backupalbumet", "sync_local": "Synkroniser lokalt", "sync_remote": "Synkroniser eksternt", @@ -1956,17 +2001,17 @@ "tag_created": "Lag merke: {tag}", "tag_feature_description": "Bla gjennom bilder og videoer gruppert etter logiske merke-emner", "tag_not_found_question": "Finner du ikke en merke? Opprett en nytt merke.", - "tag_people": "Tag Folk", + "tag_people": "Tag personer", "tag_updated": "Oppdater merke: {tag}", - "tagged_assets": "Merket {count, plural, one {# objekt} other {# objekter}}", + "tagged_assets": "Merket {count, plural, one {# element} other {# elementer}}", "tags": "Merker", "tap_to_run_job": "Trykk for å kjøre jobben", "template": "Mal", "theme": "Tema", "theme_selection": "Temavalg", "theme_selection_description": "Automatisk sett tema til lys eller mørk basert på nettleserens systeminnstilling", - "theme_setting_asset_list_storage_indicator_title": "Vis lagringsindiaktor på objekter i fotorutenettet", - "theme_setting_asset_list_tiles_per_row_title": "Antall objekter per rad ({count})", + "theme_setting_asset_list_storage_indicator_title": "Vis lagringsindiaktor på elementer i fotorutenettet", + "theme_setting_asset_list_tiles_per_row_title": "Antall elementer per rad ({count})", "theme_setting_colorful_interface_subtitle": "Angi primærfarge til bakgrunner.", "theme_setting_colorful_interface_title": "Fargefullt grensesnitt", "theme_setting_image_viewer_quality_subtitle": "Juster kvaliteten på bilder i detaljvisning", @@ -1980,6 +2025,7 @@ "theme_setting_three_stage_loading_title": "Aktiver tre-trinns innlasting", "they_will_be_merged_together": "De vil bli slått sammen", "third_party_resources": "Tredjeparts Ressurser", + "time": "Tid", "time_based_memories": "Tidsbaserte minner", "timeline": "Tidslinje", "timezone": "Tidssone", @@ -1998,20 +2044,21 @@ "trash_action_prompt": "{count} flyttet til søppel", "trash_all": "Slett alt", "trash_count": "Slett {count, number}", - "trash_delete_asset": "Slett objekt", - "trash_emptied": "Søppelbøtte tømt", + "trash_delete_asset": "Slett element", + "trash_emptied": "Søppelbøtte Tømt", "trash_no_results_message": "Her vises bilder og videoer som er flyttet til papirkurven.", "trash_page_delete_all": "Slett alt", - "trash_page_empty_trash_dialog_content": "Vil du tømme søppelbøtten? Objektene vil bli permanent fjernet fra Immich", - "trash_page_info": "Objekter i søppelbøtten blir permanent fjernet etter {days} dager", - "trash_page_no_assets": "Ingen forkastede objekter", + "trash_page_empty_trash_dialog_content": "Vil du Tømme papirkurven? Objektene vil bli permanent fjernet fra Immich", + "trash_page_info": "Objekter i papirkurven blir permanent fjernet etter {days} dager", + "trash_page_no_assets": "Ingen forkastede elementer", "trash_page_restore_all": "Gjenopprett alt", - "trash_page_select_assets_btn": "Velg objekter", + "trash_page_select_assets_btn": "Velg elementer", "trash_page_title": "Søppelbøtte ({count})", "trashed_items_will_be_permanently_deleted_after": "Elementer i papirkurven vil bli permanent slettet etter {days, plural, one {# dag} other {# dager}}.", "troubleshoot": "Feilsøk", "type": "Type", "unable_to_change_pin_code": "Klarte ikke å endre PIN-kode", + "unable_to_check_version": "Kunne ikke sjekke app eller serverversjon", "unable_to_setup_pin_code": "Klarte ikke å sette opp PINkode", "unarchive": "Fjern fra arkiv", "unarchive_action_prompt": "{count} slettet fra Arkiv", @@ -2029,7 +2076,7 @@ "unlinked_oauth_account": "Koblet fra OAuth-konto", "unmute_memories": "Opphev demping av minner", "unnamed_album": "Navnløst album", - "unnamed_album_delete_confirmation": "Er du sikker på at du vil slette dette albumet?", + "unnamed_album_delete_confirmation": "Vil du virkelig slette dette albumet?", "unnamed_share": "Deling uten navn", "unsaved_change": "Ulagrede endringer", "unselect_all": "Fjern alle valg", @@ -2037,22 +2084,22 @@ "unselect_all_in": "Fjern velging av alle i {group}", "unstack": "avstable", "unstack_action_prompt": "{count} ustakket", - "unstacked_assets_count": "Ikke stablet {count, plural, one {# objekt} other {# objekter}}", + "unstacked_assets_count": "Ikke stablet {count, plural, one {# element} other {# elementer}}", "untagged": "Umerket", "up_next": "Neste", - "update_location_action_prompt": "Oppdater plasseringen til {count} valgte objekter med:", + "update_location_action_prompt": "Oppdater plasseringen til {count} valgte elementer med:", "updated_at": "Oppdatert", "updated_password": "Passord oppdatert", "upload": "Last opp", "upload_action_prompt": "{count} i kø for opplasting", "upload_concurrency": "Samtidig opplastning", "upload_details": "Opplastingsdetaljer", - "upload_dialog_info": "Vil du utføre backup av valgte objekt(er) til serveren?", - "upload_dialog_title": "Last opp objekt", + "upload_dialog_info": "Vil du utføre backup av valgte element(er) til serveren?", + "upload_dialog_title": "Last opp element", "upload_errors": "Opplasting fullført med {count, plural, one {# error} other {# errors}}, oppdater siden for å se nye opplastingsressurser.", "upload_finished": "Opplasting fullført", "upload_progress": "Gjenstående {remaining, number} – behandlet {processed, number}/{total, number}", - "upload_skipped_duplicates": "Hoppet over {count, plural, one {# duplisert objekt} other {# dupliserte objekter}}", + "upload_skipped_duplicates": "Hoppet over {count, plural, one {# duplisert element} other {# dupliserte elementer}}", "upload_status_duplicates": "Duplikater", "upload_status_errors": "Feil", "upload_status_uploaded": "Opplastet", @@ -2068,7 +2115,7 @@ "user": "Bruker", "user_has_been_deleted": "Denne brukeren har blitt slettet.", "user_id": "Bruker ID", - "user_liked": "{user} likte {type, select, photo {dette bildet} video {denne videoen} asset {dette objektet} other {dette}}", + "user_liked": "{user} likte {type, select, photo {dette bildet} video {denne videoen} asset {dette elementet} other {dette}}", "user_pin_code_settings": "PINkode", "user_pin_code_settings_description": "Håndter din PINkode", "user_privacy": "Personverninnstillinger", @@ -2111,7 +2158,7 @@ "view_stack": "Vis stabel", "view_user": "Vis bruker", "viewer_remove_from_stack": "Fjern fra stabling", - "viewer_stack_use_as_main_asset": "Bruk som hovedobjekt", + "viewer_stack_use_as_main_asset": "Bruk som hovedelement", "viewer_unstack": "avstable", "visibility_changed": "Synlighet endret for {count, plural, one {# person} other {# people}}", "waiting": "Venter", diff --git a/i18n/nl.json b/i18n/nl.json index c236401a73..62be7e4a15 100644 --- a/i18n/nl.json +++ b/i18n/nl.json @@ -28,10 +28,12 @@ "add_to_album": "Aan album toevoegen", "add_to_album_bottom_sheet_added": "Toegevoegd aan {album}", "add_to_album_bottom_sheet_already_exists": "Staat al in {album}", + "add_to_album_bottom_sheet_some_local_assets": "Sommige lokale items konden niet aan album toegevoegd worden", "add_to_album_toggle": "Selectie inschakelen voor {album}", "add_to_albums": "Toevoegen aan albums", "add_to_albums_count": "Toevoegen aan albums ({count})", "add_to_shared_album": "Aan gedeeld album toevoegen", + "add_upload_to_stack": "Voeg upload toe aan stack", "add_url": "URL toevoegen", "added_to_archive": "Toegevoegd aan archief", "added_to_favorites": "Toegevoegd aan favorieten", @@ -41,7 +43,7 @@ "admin_user": "Beheerder gebruiker", "asset_offline_description": "Dit item uit een externe bibliotheek is niet meer beschikbaar op de schijf en is naar de prullenbak verplaatst. Als het bestand binnen de bibliotheek is verplaatst, controleer dan je tijdlijn voor het nieuwe bijbehorende item. Om dit bestand te herstellen, zorg ervoor dat het onderstaande bestandspad toegankelijk is voor Immich en scan de bibliotheek opnieuw.", "authentication_settings": "Authenticatie-instellingen", - "authentication_settings_description": "Wachtwoord, OAuth, en andere authenticatie-instellingen beheren", + "authentication_settings_description": "Wachtwoord-, OAuth-, en andere authenticatie-instellingen beheren", "authentication_settings_disable_all": "Weet je zeker dat je alle inlogmethoden wilt uitschakelen? Inloggen zal volledig worden uitgeschakeld.", "authentication_settings_reenable": "Gebruik een servercommando om opnieuw in te schakelen.", "background_task_job": "Achtergrondtaken", @@ -64,7 +66,7 @@ "confirm_email_below": "Typ hieronder \"{email}\" ter bevestiging", "confirm_reprocess_all_faces": "Weet je zeker dat je alle gezichten opnieuw wilt verwerken? Hiermee worden ook alle mensen gewist.", "confirm_user_password_reset": "Weet je zeker dat je het wachtwoord van {user} wilt resetten?", - "confirm_user_pin_code_reset": "Weet je zeker dat je de PIN code van {user} wilt resetten?", + "confirm_user_pin_code_reset": "Weet je zeker dat je de pincode van {user} wilt resetten?", "create_job": "Taak maken", "cron_expression": "Cron expressie", "cron_expression_description": "Stel het scaninterval in met het cron-formaat. Voor meer informatie kun je bijvoorbeeld kijken naar Crontab Guru", @@ -85,7 +87,7 @@ "image_fullsize_enabled_description": "Genereer afbeelding op volledig formaat voor niet-webvriendelijke formaten. Als “Verkies ingesloten voorbeeldafbeelding” is ingeschakeld, worden ingesloten voorvertoningen direct gebruikt zonder conversie. Heeft geen invloed op webvriendelijke formaten zoals JPEG.", "image_fullsize_quality_description": "Beeldkwaliteit op ware grootte van 1-100. Hoger is beter, maar genereert grotere bestanden.", "image_fullsize_title": "Instellingen afbeelding op ware grootte", - "image_prefer_embedded_preview": "Verkies ingesloten voorbeeldafbeelding", + "image_prefer_embedded_preview": "Voorkeur geven aan ingesloten voorbeeldafbeelding", "image_prefer_embedded_preview_setting_description": "Gebruik ingesloten voorbeeldafbeelding van RAW-bestanden als invoer voor beeldverwerking wanneer beschikbaar. Dit kan preciezere kleuren produceren voor sommige afbeeldingen, maar de kwaliteit van het voorbeeld is afhankelijk van de camera en de afbeelding kan mogelijk meer compressie-artefacten bevatten.", "image_prefer_wide_gamut": "Voorkeur geven aan wide gamut", "image_prefer_wide_gamut_setting_description": "Display P3 gebruiken voor voorbeeldafbeeldingen. Dit behoudt de levendigheid van afbeeldingen met brede kleurruimtes beter, maar afbeeldingen kunnen er anders uitzien op oude apparaten met een oude browserversie. sRGB-afbeeldingen blijven sRGB gebruiken om kleurverschuivingen te vermijden.", @@ -102,7 +104,7 @@ "image_thumbnail_title": "Thumbnailinstellingen", "job_concurrency": "{job} gelijktijdigheid", "job_created": "Taak aangemaakt", - "job_not_concurrency_safe": "Deze taak kan niet gelijktijdig worden uitgevoerd.", + "job_not_concurrency_safe": "Deze taak kan niet parallel worden uitgevoerd.", "job_settings": "Achtergrondtaak-instellingen", "job_settings_description": "Beheer aantal gelijktijdige taken", "job_status": "Taakstatus", @@ -118,23 +120,26 @@ "library_settings_description": "Externe bibliotheekinstellingen beheren", "library_tasks_description": "Scan externe bibliotheken op nieuwe en/of gewijzigde media", "library_watching_enable_description": "Externe bibliotheken monitoren op bestandswijzigingen", - "library_watching_settings": "Bibliotheek monitoren (EXPERIMENTEEL)", + "library_watching_settings": "Bibliotheek monitoren [EXPERIMENTEEL]", "library_watching_settings_description": "Automatisch gewijzigde bestanden bijhouden", "logging_enable_description": "Logboek inschakelen", "logging_level_description": "Indien ingeschakeld, welk logniveau er wordt gebruikt.", - "logging_settings": "Logging", + "logging_settings": "Logboek", + "machine_learning_availability_checks": "Beschikbaarheidscontroles", "machine_learning_availability_checks_description": "Automatisch detecteren en selecteren van beschikbare machine learning servers", - "machine_learning_availability_checks_enabled": "Activeer beschikbaarheid controles", + "machine_learning_availability_checks_enabled": "Activeer beschikbaarheidscontroles", "machine_learning_availability_checks_interval": "Controleinterval", - "machine_learning_availability_checks_interval_description": "Interval in milliseconden tussen beschikbaarheid checks", - "machine_learning_clip_model": "CLIP model", - "machine_learning_clip_model_description": "De naam van een CLIP-model dat hier is vermeld. Let op: je moet de 'Slim Zoeken -taak opnieuw uitvoeren voor alle afbeeldingen wanneer je een model wijzigt.", - "machine_learning_duplicate_detection": "Duplicaat detectie", + "machine_learning_availability_checks_interval_description": "Interval in milliseconden tussen beschikbaarheidscontroles", + "machine_learning_availability_checks_timeout": "Verzoek time-out", + "machine_learning_availability_checks_timeout_description": "Time-out in milliseconden voor beschikbaarheidscontroles", + "machine_learning_clip_model": "CLIP-model", + "machine_learning_clip_model_description": "De naam van een CLIP-model dat hier is vermeld. Let op: je moet de 'Slim Zoeken'-taak voor alle afbeeldingen opnieuw uitvoeren wanneer je een model wijzigt.", + "machine_learning_duplicate_detection": "Duplicaatdetectie", "machine_learning_duplicate_detection_enabled": "Duplicaatdetectie inschakelen", "machine_learning_duplicate_detection_enabled_description": "Indien uitgeschakeld, worden identieke items nog steeds gededupliceerd.", - "machine_learning_duplicate_detection_setting_description": "Gebruik CLIP om exactie kopieën te vinden", + "machine_learning_duplicate_detection_setting_description": "Gebruik CLIP-embeddings om mogelijke kopieën te vinden", "machine_learning_enabled": "Machine learning inschakelen", - "machine_learning_enabled_description": "Wanneer uitgeschakeld zullen alle ML instellingen uitgezet worden, ongeacht onderstaande instellingen.", + "machine_learning_enabled_description": "Indien uitgeschakeld, worden alle ML-instellingen uitgezet, ongeacht onderstaande instellingen.", "machine_learning_facial_recognition": "Gezichtsherkenning", "machine_learning_facial_recognition_description": "Detecteer, herken en groepeer gezichten in afbeeldingen", "machine_learning_facial_recognition_model": "Model voor gezichtsherkenning", @@ -149,6 +154,18 @@ "machine_learning_min_detection_score_description": "Minimale betrouwbaarheidsscore voor het detecteren van een gezicht tussen 0-1. Lagere waarden detecteren meer gezichten, maar kunnen ze ook vaker ten onrechte detecteren.", "machine_learning_min_recognized_faces": "Minimaal herkende gezichten", "machine_learning_min_recognized_faces_description": "Het minimale aantal herkende gezichten voordat een persoon wordt aangemaakt. Door dit te verhogen wordt gezichtsherkenning nauwkeuriger, maar dit vergroot de kans dat een gezicht niet aan een persoon is toegewezen.", + "machine_learning_ocr": "OCR (tekstherkenning)", + "machine_learning_ocr_description": "Gebruik machine learning om tekst in afbeeldingen te herkennen", + "machine_learning_ocr_enabled": "OCR inschakelen", + "machine_learning_ocr_enabled_description": "Indien uitgeschakeld, worden afbeeldingen niet verwerkt voor tekstherkenning.", + "machine_learning_ocr_max_resolution": "Maximale resolutie", + "machine_learning_ocr_max_resolution_description": "Voorbeeldafbeeldingen met een hogere resolutie dan deze, worden verkleind met behoud van de beeldverhouding. Hogere resoluties resulteren in een hogere nauwkeurigheid, maar kosten meer tijd werkgeheugen om te verwerken.", + "machine_learning_ocr_min_detection_score": "Minimale detectiescore", + "machine_learning_ocr_min_detection_score_description": "Minimale betrouwbaarheidsscore om tekst te detecteren, tussen 0-1. Met een lagere grenswaarde wordt meer tekst gedetecteerd, maar dit kan ook leiden tot vals-positieven.", + "machine_learning_ocr_min_recognition_score": "Minimale herkenningsafstand", + "machine_learning_ocr_min_score_recognition_description": "Minimale betrouwbaarheidsscore om tekst te herkennen, tussen 0-1. Met een lagere grenswaarde wordt meer tekst herkend, maar dit kan ook leiden tot vals-positieven.", + "machine_learning_ocr_model": "OCR-model", + "machine_learning_ocr_model_description": "De 'server' modellen zijn nauwkeuriger dan de 'mobile' modellen, maar daarmee kost het meer tijd en werkgeheugen om afbeeldingen te verwerken.", "machine_learning_settings": "Machine learning instellingen", "machine_learning_settings_description": "Beheer machine learning functies en instellingen", "machine_learning_smart_search": "Slim Zoeken", @@ -160,8 +177,8 @@ "manage_log_settings": "Beheer logboekinstellingen", "map_dark_style": "Donkere stijl", "map_enable_description": "Kaartfuncties inschakelen", - "map_gps_settings": "Kaart & GPS Instellingen", - "map_gps_settings_description": "Beheer kaart & GPS (omgekeerde geocodering) instellingen", + "map_gps_settings": "Kaart- & gps-instellingen", + "map_gps_settings_description": "Beheer kaart- & gps-instellingen (omgekeerde geocodering)", "map_implications": "De kaartfunctie is afhankelijk van een externe service (tiles.immich.cloud)", "map_light_style": "Lichte stijl", "map_manage_reverse_geocoding_settings": "Beheer omgekeerde geocodering instellingen", @@ -174,27 +191,27 @@ "memory_cleanup_job": "Herinneringen opschonen", "memory_generate_job": "Herinneringen genereren", "metadata_extraction_job": "Metadata ophalen", - "metadata_extraction_job_description": "Metadata ophalen van ieder item, zoals GPS, gezichten en resolutie", + "metadata_extraction_job_description": "Metadata ophalen van ieder item, zoals gps, gezichten en resolutie", "metadata_faces_import_setting": "Gezichten importeren inschakelen", "metadata_faces_import_setting_description": "Gezichten importeren uit EXIF-gegevens van afbeeldingen en sidecar bestanden", - "metadata_settings": "Metadata instellingen", - "metadata_settings_description": "Beheer metadata instellingen", + "metadata_settings": "Metadata-instellingen", + "metadata_settings_description": "Beheer metadata-instellingen", "migration_job": "Migratie", "migration_job_description": "Migreer thumbnails voor items en gezichten naar de nieuwste mapstructuur", "nightly_tasks_cluster_faces_setting_description": "Gezichtsherkenning uitvoeren op nieuw gedetecteerde gezichten", "nightly_tasks_cluster_new_faces_setting": "Cluster nieuwe gezichten", - "nightly_tasks_database_cleanup_setting": "Database opschoon taken", - "nightly_tasks_database_cleanup_setting_description": "Ruim oude data op van de database", + "nightly_tasks_database_cleanup_setting": "Database-opruimtaken", + "nightly_tasks_database_cleanup_setting_description": "Ruim oude, niet meer geldige data op uit de database", "nightly_tasks_generate_memories_setting": "Genereer herinneringen", "nightly_tasks_generate_memories_setting_description": "Maak nieuwe herinneringen van items", "nightly_tasks_missing_thumbnails_setting": "Genereer ontbrekende thumbnails", "nightly_tasks_missing_thumbnails_setting_description": "Items zonder thumbnail in een wachtrij plaatsen voor het genereren van thumbnails", - "nightly_tasks_settings": "Instellingen voor nacht taken", - "nightly_tasks_settings_description": "Beheer nacht taken", - "nightly_tasks_start_time_setting": "Start tijd", - "nightly_tasks_start_time_setting_description": "De tijd waarop de server begint met het uitvoeren van de nacht taken", - "nightly_tasks_sync_quota_usage_setting": "Synchroniseer quota gebruik", - "nightly_tasks_sync_quota_usage_setting_description": "update gebruiker opslag quota, gebaseerd op huidig gebruik", + "nightly_tasks_settings": "Instellingen voor nachtelijke taken", + "nightly_tasks_settings_description": "Beheer nachtelijke taken", + "nightly_tasks_start_time_setting": "Starttijd", + "nightly_tasks_start_time_setting_description": "De tijd waarop de server begint met het uitvoeren van de nachtelijke taken", + "nightly_tasks_sync_quota_usage_setting": "Synchroniseer opslaglimieten", + "nightly_tasks_sync_quota_usage_setting_description": "Update opslaglimieten van gebruikers, gebaseerd op huidig gebruik", "no_paths_added": "Geen paden toegevoegd", "no_pattern_added": "Geen patroon toegevoegd", "note_apply_storage_label_previous_assets": "Opmerking: om het opslaglabel toe te passen op eerder geüploade items, voer de volgende taak uit", @@ -206,6 +223,8 @@ "notification_email_ignore_certificate_errors_description": "Negeer validatiefouten van TLS-certificaat (niet aanbevolen)", "notification_email_password_description": "Wachtwoord om te gebruiken bij authenticatie met de e-mailserver", "notification_email_port_description": "Poort van de e-mailserver (bijv. 25, 465 of 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "Gebruik SMTPS (SMTP via TLS)", "notification_email_sent_test_email_button": "Verstuur testmail en opslaan", "notification_email_setting_description": "Instellingen voor het verzenden van e-mailmeldingen", "notification_email_test_email": "Verstuur testmail", @@ -238,6 +257,7 @@ "oauth_storage_quota_default_description": "Limiet in GiB die moet worden gebruikt als er geen claim is opgegeven.", "oauth_timeout": "Aanvraag timeout", "oauth_timeout_description": "Time-out voor aanvragen in milliseconden", + "ocr_job_description": "Gebruik machine learning om tekst in afbeeldingen te herkennen", "password_enable_description": "Inloggen met e-mailadres en wachtwoord", "password_settings": "Inloggen met wachtwoord", "password_settings_description": "Beheer instellingen voor inloggen met wachtwoord", @@ -269,12 +289,12 @@ "storage_template_date_time_sample": "Voorbeeldtijd {date}", "storage_template_enable_description": "Engine voor opslagtemplate inschakelen", "storage_template_hash_verification_enabled": "Hashverificatie ingeschakeld", - "storage_template_hash_verification_enabled_description": "Zet hashverificatie aan, schakel dit niet uit tenzij je zeker bent van de implicaties", + "storage_template_hash_verification_enabled_description": "Zet hashverificatie aan. Schakel dit niet uit tenzij je zeker bent van de gevolgen", "storage_template_migration": "Opslagtemplate migratie", "storage_template_migration_description": "Pas de huidige {template} toe op eerder geüploade items", "storage_template_migration_info": "Wijzigingen in de opslagtemplate worden alleen toegepast op nieuwe items. Om de template met terugwerkende kracht toe te passen op eerder geüploade items, voer je de {job} uit.", "storage_template_migration_job": "Opslagtemplate migratietaak", - "storage_template_more_details": "Voor meer details over deze functie, bekijk de Opslagstemplate en de implicaties daarvan", + "storage_template_more_details": "Meer details over deze functie vind je onder Opslagtemplate, net als de gevolgen daarvan", "storage_template_onboarding_description_v2": "Wanneer ingeschakeld, zal deze functie bestanden automatisch organiseren gebaseerd op een template gedefinieerd door de gebruiker. Voor meer informatie, bekijk de documentatie.", "storage_template_path_length": "Geschatte padlengte: {length, number}/{limit, number}", "storage_template_settings": "Opslagtemplate", @@ -322,13 +342,13 @@ "transcoding_encoding_options": "Coderings Opties", "transcoding_encoding_options_description": "Stel codecs, resolutie, kwaliteit en andere opties in voor de gecodeerde video's", "transcoding_hardware_acceleration": "Hardware acceleratie", - "transcoding_hardware_acceleration_description": "Experimenteel; snellere transcodering maar kan kwaliteit verminderen bij dezelfde bitrate", + "transcoding_hardware_acceleration_description": "Experimenteel; snellere transcodering, maar kan kwaliteit verminderen bij dezelfde bitrate", "transcoding_hardware_decoding": "Hardware decodering", "transcoding_hardware_decoding_setting_description": "Maakt end-to-end versnelling mogelijk in plaats van alleen de codering te versnellen. Werkt mogelijk niet op alle video's.", - "transcoding_max_b_frames": "Maximum B-Frames", + "transcoding_max_b_frames": "Maximaal aantal B-frames", "transcoding_max_b_frames_description": "Hogere waarden verbeteren de compressie efficiëntie, maar vertragen de codering. Is mogelijk niet compatibel met hardwareversnelling op oudere apparaten. 0 schakelt B-frames uit, terwijl -1 deze waarde automatisch instelt.", - "transcoding_max_bitrate": "Maximum bitrate", - "transcoding_max_bitrate_description": "Het instellen van een maximale bitrate kan de bestandsgrootte voorspelbaarder maken, tegen geringe kosten voor de kwaliteit. Bij 720p zijn de typische waarden 2600 kbit/s voor VP9 of HEVC, of 4500 kbit/s voor H.264. Uitgeschakeld indien ingesteld op 0.", + "transcoding_max_bitrate": "Maximale bitrate", + "transcoding_max_bitrate_description": "Het instellen van een maximale bitrate kan de bestandsgrootte voorspelbaarder maken, tegen geringe kosten voor de kwaliteit. Bij 720p zijn de typische waarden 2600 kbit/s voor VP9 of HEVC, of 4500 kbit/s voor H.264. Uitgeschakeld indien ingesteld op 0. Zonder eenheid wordt k (kbit/s) aangenomen; bitrates 5000, 5000k, en 5M (Mbit/s) komen dus op hetzelfde neer.", "transcoding_max_keyframe_interval": "Maximale keyframe interval", "transcoding_max_keyframe_interval_description": "Stelt de maximale frameafstand tussen keyframes in. Lagere waarden verslechteren de compressie efficiëntie, maar verbeteren de zoektijden en kunnen de kwaliteit verbeteren in scènes met snelle bewegingen. 0 stelt deze waarde automatisch in.", "transcoding_optimal_description": "Video's met een hogere resolutie dan de doelresolutie of niet in een geaccepteerd formaat", @@ -346,10 +366,10 @@ "transcoding_target_resolution": "Target resolutie", "transcoding_target_resolution_description": "Hogere resoluties kunnen meer details behouden, maar het coderen ervan duurt langer, de bestandsgrootte is groter en de app reageert mogelijk minder snel.", "transcoding_temporal_aq": "Tijdelijke AQ", - "transcoding_temporal_aq_description": "Alleen van toepassing op NVENC. Verhoogt de kwaliteit van scènes met veel details en weinig beweging. Is mogelijk niet compatibel met oudere apparaten.", + "transcoding_temporal_aq_description": "Alleen van toepassing op NVENC. Temporale Adaptieve Kwantisatie verhoogt de kwaliteit van scènes met veel details en weinig beweging. Is mogelijk niet compatibel met oudere apparaten.", "transcoding_threads": "Threads", "transcoding_threads_description": "Hogere waarden leiden tot snellere codering, maar laten minder ruimte over voor de server om andere taken te verwerken terwijl deze actief is. Deze waarde mag niet groter zijn dan het aantal CPU cores. Maximaliseert het gebruik als deze is ingesteld op 0.", - "transcoding_tone_mapping": "Tone-mapping", + "transcoding_tone_mapping": "Tone mapping", "transcoding_tone_mapping_description": "Probeert het uiterlijk van HDR-video's te behouden wanneer ze worden geconverteerd naar SDR. Elk algoritme maakt verschillende afwegingen voor kleur, detail en helderheid. Hable behoudt detail, Mobius behoudt kleur en Reinhard behoudt helderheid.", "transcoding_transcode_policy": "Transcodeerbeleid", "transcoding_transcode_policy_description": "Beleid voor wanneer een video getranscodeerd moet worden. HDR-video's worden altijd getranscodeerd (behalve als transcodering is uitgeschakeld).", @@ -368,7 +388,7 @@ "user_cleanup_job": "Gebruiker opschoning", "user_delete_delay": "Het account en de items van {user} worden over {delay, plural, one {# dag} other {# dagen}} permanent verwijderd.", "user_delete_delay_settings": "Verwijder vertraging", - "user_delete_delay_settings_description": "Aantal dagen na verwijdering om het account en de items van een gebruiker permanent te verwijderen. De taak voor het verwijderen van gebruikers wordt om middernacht uitgevoerd om te controleren of gebruikers verwijderd kunnen worden. Wijzigingen in deze instelling worden bij de volgende uitvoering meegenomen.", + "user_delete_delay_settings_description": "Aantal dagen na verwijdering om het account en de items van een gebruiker permanent te verwijderen. De taak voor het verwijderen van gebruikers wordt om middernacht uitgevoerd om te controleren of gebruiker te verwijderen zijn. Wijzigingen in deze instelling worden bij de volgende uitvoering meegenomen.", "user_delete_immediately": "Het account en de items van {user} worden onmiddellijk in de wachtrij geplaatst voor permanente verwijdering.", "user_delete_immediately_checkbox": "Gebruikers en items in de wachtrij plaatsen voor onmiddellijke verwijdering", "user_details": "Gebruiker details", @@ -397,11 +417,11 @@ "advanced_settings_prefer_remote_subtitle": "Sommige apparaten zijn traag met het laden van lokale afbeeldingen. Activeer deze instelling om in plaats daarvan externe afbeeldingen te laden.", "advanced_settings_prefer_remote_title": "Externe afbeeldingen laden", "advanced_settings_proxy_headers_subtitle": "Definieer proxy headers die Immich bij elk netwerkverzoek moet verzenden", - "advanced_settings_proxy_headers_title": "Proxy headers", + "advanced_settings_proxy_headers_title": "Proxy Headers [EXPERIMENTEEL]", "advanced_settings_readonly_mode_subtitle": "Schakelt de alleen-lezenmodus in, waarbij de foto's alleen bekeken kunnen worden. Dingen zoals het selecteren van meerdere afbeeldingen, delen, casten en verwijderen zijn allemaal uitgeschakeld. Schakel alleen-lezen in of uit via de gebruikers avatar vanaf het hoofdscherm", - "advanced_settings_readonly_mode_title": "Alleen-lezen Mode", + "advanced_settings_readonly_mode_title": "Alleen-lezen mode", "advanced_settings_self_signed_ssl_subtitle": "Slaat SSL-certificaatverificatie voor de connectie met de server over. Deze optie is vereist voor zelfondertekende certificaten.", - "advanced_settings_self_signed_ssl_title": "Zelfondertekende SSL-certificaten toestaan", + "advanced_settings_self_signed_ssl_title": "Zelfondertekende SSL-certificaten toestaan [EXPERIMENTEEL]", "advanced_settings_sync_remote_deletions_subtitle": "Automatisch bestanden verwijderen of herstellen op dit apparaat als die actie op het web is ondernomen", "advanced_settings_sync_remote_deletions_title": "Synchroniseer verwijderingen op afstand [EXPERIMENTEEL]", "advanced_settings_tile_subtitle": "Geavanceerde gebruikersinstellingen", @@ -412,9 +432,9 @@ "age_years": "{years, plural, other {Leeftijd #}}", "album_added": "Album toegevoegd", "album_added_notification_setting_description": "Ontvang een e-mailmelding wanneer je aan een gedeeld album wordt toegevoegd", - "album_cover_updated": "Album cover is bijgewerkt", + "album_cover_updated": "Albumomslag is bijgewerkt", "album_delete_confirmation": "Weet je zeker dat je het album {album} wilt verwijderen?", - "album_delete_confirmation_description": "Als dit album gedeeld is, hebben andere gebruikers er geen toegang meer toe.", + "album_delete_confirmation_description": "Als dit album gedeeld is, zullen andere gebruikers geen toegang meer hebben.", "album_deleted": "Album verwijderd", "album_info_card_backup_album_excluded": "UITGESLOTEN", "album_info_card_backup_album_included": "INBEGREPEN", @@ -461,10 +481,14 @@ "api_key_description": "Deze waarde wordt slechts één keer getoond. Zorg ervoor dat je deze kopieert voordat je het venster sluit.", "api_key_empty": "De naam van uw API-sleutel mag niet leeg zijn", "api_keys": "API-sleutels", + "app_architecture_variant": "Variant (architectuur)", "app_bar_signout_dialog_content": "Weet je zeker dat je wilt uitloggen?", "app_bar_signout_dialog_ok": "Ja", "app_bar_signout_dialog_title": "Log uit", + "app_download_links": "Download-verwijzingen van de app", "app_settings": "App instellingen", + "app_stores": "Appstore", + "app_update_available": "Een update van de applicatie is beschikbaar", "appears_in": "Komt voor in", "apply_count": "Toepassen ({count, number})", "archive": "Archief", @@ -548,6 +572,7 @@ "backup_albums_sync": "Backup albums synchronisatie", "backup_all": "Alle", "backup_background_service_backup_failed_message": "Fout bij het back-uppen van de items. Opnieuw proberen…", + "backup_background_service_complete_notification": "Backup voltooid", "backup_background_service_connection_failed_message": "Fout bij het verbinden met de server. Opnieuw proberen…", "backup_background_service_current_upload_notification": "{filename} wordt geüpload", "backup_background_service_default_notification": "Controleren op nieuwe items…", @@ -595,6 +620,7 @@ "backup_controller_page_turn_on": "Back-up op de voorgrond aanzetten", "backup_controller_page_uploading_file_info": "Bestandsgegevens uploaden", "backup_err_only_album": "Kan het enige album niet verwijderen", + "backup_error_sync_failed": "Synchronisatie mislukt. Kan back-up niet verwerken.", "backup_info_card_assets": "bestanden", "backup_manual_cancelled": "Geannuleerd", "backup_manual_in_progress": "Het uploaden is al bezig. Probeer het na een tijdje", @@ -656,10 +682,12 @@ "change_password_description": "Dit is de eerste keer dat je inlogt op het systeem of er is een verzoek gedaan om je wachtwoord te wijzigen. Voer hieronder het nieuwe wachtwoord in.", "change_password_form_confirm_password": "Bevestig wachtwoord", "change_password_form_description": "Hallo {name},\n\nDit is ofwel de eerste keer dat je inlogt, of er is een verzoek gedaan om je wachtwoord te wijzigen. Vul hieronder een nieuw wachtwoord in.", + "change_password_form_log_out": "Uitloggen op alle andere apparaten", + "change_password_form_log_out_description": "Het is verstandig om op alle andere apparaten uit te loggen", "change_password_form_new_password": "Nieuw wachtwoord", "change_password_form_password_mismatch": "Wachtwoorden komen niet overeen", "change_password_form_reenter_new_password": "Vul het wachtwoord opnieuw in", - "change_pin_code": "Wijzig PIN code", + "change_pin_code": "Wijzig pincode", "change_your_password": "Wijzig je wachtwoord", "changed_visibility_successfully": "Zichtbaarheid succesvol gewijzigd", "charging": "Opladen", @@ -682,8 +710,8 @@ "client_cert_import_success_msg": "Clientcertificaat is geïmporteerd", "client_cert_invalid_msg": "Ongeldig certificaatbestand of verkeerd wachtwoord", "client_cert_remove_msg": "Clientcertificaat is verwijderd", - "client_cert_subtitle": "Ondersteunt alleen PKCS12 (.p12, .pfx) formaat. Certificaat importeren/verwijderen is alleen beschikbaar vóór het inloggen", - "client_cert_title": "SSL clientcertificaat", + "client_cert_subtitle": "Ondersteunt alleen PKCS12-formaat (.p12, .pfx). Het importeren/verwijderen van certificaten is alleen beschikbaar vóór het inloggen", + "client_cert_title": "SSL clientcertificaat [EXPERIMENTEEL]", "clockwise": "Rechtsom", "close": "Sluiten", "collapse": "Inklappen", @@ -695,14 +723,13 @@ "comments_and_likes": "Opmerkingen & likes", "comments_are_disabled": "Opmerkingen zijn uitgeschakeld", "common_create_new_album": "Nieuw album maken", - "common_server_error": "Controleer je netwerkverbinding, zorg ervoor dat de server bereikbaar is en de app/server versies compatibel zijn.", "completed": "Voltooid", "confirm": "Bevestigen", "confirm_admin_password": "Bevestig beheerder wachtwoord", "confirm_delete_face": "Weet je zeker dat je het gezicht van {name} wilt verwijderen uit het item?", "confirm_delete_shared_link": "Weet je zeker dat je deze gedeelde link wilt verwijderen?", "confirm_keep_this_delete_others": "Alle andere items in de stack worden verwijderd, behalve deze. Weet je zeker dat je wilt doorgaan?", - "confirm_new_pin_code": "Bevestig nieuwe PIN code", + "confirm_new_pin_code": "Bevestig nieuwe pincode", "confirm_password": "Bevestig wachtwoord", "confirm_tag_face": "Wil je dit gezicht taggen als {name}?", "confirm_tag_face_unnamed": "Wil je dit gezicht taggen?", @@ -730,10 +757,11 @@ "copy_to_clipboard": "Kopiëren naar klembord", "country": "Land", "cover": "Bedekken", - "covers": "Covers", + "covers": "Omslagen", "create": "Aanmaken", "create_album": "Album aanmaken", "create_album_page_untitled": "Naamloos", + "create_api_key": "API-sleutel maken", "create_library": "Bibliotheek maken", "create_link": "Link maken", "create_link_to_share": "Gedeelde link maken", @@ -750,11 +778,11 @@ "create_user": "Gebruiker aanmaken", "created": "Aangemaakt", "created_at": "Aangemaakt", - "creating_linked_albums": "Gekoppelde albums worden aangemaakt", + "creating_linked_albums": "Gekoppelde albums worden aangemaakt...", "crop": "Bijsnijden", "curated_object_page_title": "Dingen", "current_device": "Huidig apparaat", - "current_pin_code": "Huidige PIN code", + "current_pin_code": "Huidige pincode", "current_server_address": "Huidig serveradres", "custom_locale": "Aangepaste landinstelling", "custom_locale_description": "Formatteer datums en getallen op basis van de taal en de regio", @@ -816,7 +844,7 @@ "disabled": "Uitgeschakeld", "disallow_edits": "Geen bewerkingen toestaan", "discord": "Discord", - "discover": "Zoeken", + "discover": "Zoek", "discovered_devices": "Gevonden apparaten", "dismiss_all_errors": "Negeer alle fouten", "dismiss_error": "Negeer fout", @@ -836,7 +864,7 @@ "download_failed": "Download mislukt", "download_finished": "Download voltooid", "download_include_embedded_motion_videos": "Ingesloten video's", - "download_include_embedded_motion_videos_description": "Voeg video's toe die ingesloten zijn in bewegende foto's als een apart bestand", + "download_include_embedded_motion_videos_description": "Voeg video's die in bewegingsfoto's zijn ingebed toe als een apart bestand", "download_notfound": "Download niet gevonden", "download_paused": "Download gepauseerd", "download_settings": "Downloaden", @@ -846,7 +874,7 @@ "download_sucess_android": "Het bestand is gedownload naar DCIM/Immich", "download_waiting_to_retry": "Wachten om opnieuw te proberen", "downloading": "Downloaden", - "downloading_asset_filename": "Downloading asset {filename}", + "downloading_asset_filename": "Downloaden asset {filename}", "downloading_media": "Media aan het downloaden", "drop_files_to_upload": "Zet bestanden ergens neer om ze te uploaden", "duplicates": "Duplicaten", @@ -877,7 +905,6 @@ "edit_tag": "Tag bewerken", "edit_title": "Titel bewerken", "edit_user": "Gebruiker bewerken", - "edited": "Bijgewerkt", "editor": "Bewerker", "editor_close_without_save_prompt": "De wijzigingen worden niet opgeslagen", "editor_close_without_save_title": "Editor sluiten?", @@ -900,7 +927,9 @@ "error": "Fout", "error_change_sort_album": "Sorteervolgorde van album wijzigen mislukt", "error_delete_face": "Fout bij verwijderen van gezicht uit het item", + "error_getting_places": "Fout bij ophalen plaatsen", "error_loading_image": "Fout bij laden afbeelding", + "error_loading_partners": "Fout bij ophalen partners: {error}", "error_saving_image": "Fout: {error}", "error_tag_face_bounding_box": "Fout bij taggen van gezicht - kan coördinaten van omvattend kader niet ophalen", "error_title": "Fout - Er is iets misgegaan", @@ -933,7 +962,7 @@ "failed_to_load_notifications": "Kon meldingen niet laden", "failed_to_load_people": "Kan mensen niet laden", "failed_to_remove_product_key": "Fout bij het verwijderen van de licentiesleutel", - "failed_to_reset_pin_code": "Resetten van PIN code mislukt", + "failed_to_reset_pin_code": "Resetten van pincode mislukt", "failed_to_stack_assets": "Fout bij stapelen van items", "failed_to_unstack_assets": "Fout bij ontstapelen van items", "failed_to_update_notification_status": "Kon notificatiestatus niet updaten", @@ -998,7 +1027,7 @@ "unable_to_remove_partner": "Kan partner niet verwijderen", "unable_to_remove_reaction": "Kan reactie niet verwijderen", "unable_to_reset_password": "Kan wachtwoord niet resetten", - "unable_to_reset_pin_code": "Kan PIN code niet resetten", + "unable_to_reset_pin_code": "Kan pincode niet resetten", "unable_to_resolve_duplicate": "Kan duplicaat niet oplossen", "unable_to_restore_assets": "Kan items niet herstellen", "unable_to_restore_trash": "Kan niet herstellen uit prullenbak", @@ -1017,7 +1046,7 @@ "unable_to_trash_asset": "Kan item niet naar prullenbak verplaatsen", "unable_to_unlink_account": "Kan account niet ontkoppelen", "unable_to_unlink_motion_video": "Kan bewegende video niet ontkoppelen", - "unable_to_update_album_cover": "Kan album cover niet bijwerken", + "unable_to_update_album_cover": "Kan albumomslag niet bijwerken", "unable_to_update_album_info": "Kan albumgegevens niet bijwerken", "unable_to_update_library": "Kan bibliotheek niet bijwerken", "unable_to_update_location": "Kan locatie niet bijwerken", @@ -1031,6 +1060,7 @@ "exif_bottom_sheet_description_error": "Fout bij het bijwerken van de beschrijving", "exif_bottom_sheet_details": "DETAILS", "exif_bottom_sheet_location": "LOCATIE", + "exif_bottom_sheet_no_description": "Geen beschrijving", "exif_bottom_sheet_people": "MENSEN", "exif_bottom_sheet_person_add_person": "Naam toevoegen", "exit_slideshow": "Diavoorstelling sluiten", @@ -1081,12 +1111,12 @@ "folder_not_found": "Map niet gevonden", "folders": "Mappen", "folders_feature_description": "Bladeren door de mapweergave van de foto's en video's op het bestandssysteem", - "forgot_pin_code_question": "PIN vergeten?", + "forgot_pin_code_question": "Pincode vergeten?", "forward": "Vooruit", "gcast_enabled": "Google Cast", "gcast_enabled_description": "Deze functie gebruikt externe bronnen van Google om te kunnen werken.", "general": "Algemeen", - "geolocation_instruction_location": "Klik op een item met GPS coördinaten om de locatie te gebruiken, of selecteer een locatie direct vanaf de kaart", + "geolocation_instruction_location": "Klik op een item met gps-coördinaten om de locatie te gebruiken, of kies een locatie direct op de kaart", "get_help": "Krijg hulp", "get_wifiname_error": "Kon de WiFi-naam niet ophalen. Zorg ervoor dat je de benodigde machtigingen hebt verleend en verbonden bent met een WiFi-netwerk", "getting_started": "Aan de slag", @@ -1112,7 +1142,6 @@ "header_settings_field_validator_msg": "Waarde kan niet leeg zijn", "header_settings_header_name_input": "Header naam", "header_settings_header_value_input": "Header waarde", - "headers_settings_tile_subtitle": "Definieer proxy headers die de app met elk netwerkverzoek moet verzenden", "headers_settings_tile_title": "Aangepaste proxy headers", "hi_user": "Hallo {name} ({email})", "hide_all_people": "Verberg alle mensen", @@ -1131,7 +1160,7 @@ "home_page_delete_err_partner": "Partner items kunnen niet verwijderd worden, overslaan", "home_page_delete_remote_err_local": "Lokale items staan in verwijder selectie externe items, overslaan", "home_page_favorite_err_local": "Lokale items kunnen nog niet als favoriet worden aangemerkt, overslaan", - "home_page_favorite_err_partner": "Partner items kunnen nog niet ge-favoriet worden, overslaan", + "home_page_favorite_err_partner": "Partner items kunnen nog niet als favoriet gemarkeerd worden, overslaan", "home_page_first_time_notice": "Als dit de eerste keer is dat je de app gebruikt, zorg er dan voor dat je een back-up album kiest, zodat de tijdlijn gevuld kan worden met foto's en video's uit het album", "home_page_locked_error_local": "Kan lokale bestanden niet naar de vergrendelde map verplaatsen, sla over", "home_page_locked_error_partner": "Kan partnerbestanden niet naar de vergrendelde map verplaatsen, sla over", @@ -1175,7 +1204,7 @@ "day_at_onepm": "Iedere dag om 13 uur", "hours": "{hours, plural, one {Ieder uur} other {Iedere {hours, number} uren}}", "night_at_midnight": "Iedere avond om middernacht", - "night_at_twoam": "Iedere nacht om 2 uur" + "night_at_twoam": "Elke nacht om 2 uur" }, "invalid_date": "Ongeldige datum", "invalid_date_format": "Ongeldig datumformaat", @@ -1337,6 +1366,8 @@ "minute": "Minuut", "minutes": "Minuten", "missing": "Missend", + "mobile_app": "Mobiele app", + "mobile_app_download_onboarding_note": "Download de mobiele app via de onderstaande opties", "model": "Model", "month": "Maand", "monthly_title_text_date_format": "MMMM y", @@ -1355,6 +1386,8 @@ "my_albums": "Mijn albums", "name": "Naam", "name_or_nickname": "Naam of gebruikersnaam", + "navigate": "Navigeer", + "navigate_to_time": "Navigeer naar tijdstip", "network_requirement_photos_upload": "Gebruik mobiele data voor de backup van foto's", "network_requirement_videos_upload": "Gebruik mobiele data voor de backups van video's", "network_requirements": "Netwerk vereisten", @@ -1364,9 +1397,10 @@ "never": "Nooit", "new_album": "Nieuw album", "new_api_key": "Nieuwe API-sleutel", + "new_date_range": "Nieuw datumbereik", "new_password": "Nieuw wachtwoord", "new_person": "Nieuw persoon", - "new_pin_code": "Nieuwe PIN code", + "new_pin_code": "Nieuwe pincode", "new_pin_code_subtitle": "Dit is de eerste keer dat u de vergrendelde map opent. Stel een pincode in om deze pagina veilig te openen", "new_timeline": "Nieuwe tijdlijn", "new_user_created": "Nieuwe gebruiker aangemaakt", @@ -1382,20 +1416,25 @@ "no_assets_message": "KLIK HIER OM JE EERSTE FOTO TE UPLOADEN", "no_assets_to_show": "Geen foto's om te laten zien", "no_cast_devices_found": "Geen cast-apparaten gevonden", + "no_checksum_local": "Geen checksum beschikbaar - kan lokale assets niet ophalen", + "no_checksum_remote": "Geen checksum beschikbaar - kan online assets niet ophalen", "no_duplicates_found": "Er zijn geen duplicaten gevonden.", "no_exif_info_available": "Geen exif info beschikbaar", "no_explore_results_message": "Upload meer foto's om je verzameling te verkennen.", "no_favorites_message": "Voeg favorieten toe om snel je beste foto's en video's te vinden", "no_libraries_message": "Maak een externe bibliotheek om je foto's en video's te bekijken", + "no_local_assets_found": "Geen lokale assets gevonden met deze checksum", "no_locked_photos_message": "Foto’s en video’s in de vergrendelde map zijn verborgen en worden niet weergegeven wanneer je door je bibliotheek bladert of zoekt.", "no_name": "Geen naam", "no_notifications": "Geen meldingen", "no_people_found": "Geen mensen gevonden", "no_places": "Geen plaatsen", + "no_remote_assets_found": "Geen online assets gevonden met deze checksum", "no_results": "Geen resultaten", "no_results_description": "Probeer een synoniem of een algemener zoekwoord", "no_shared_albums_message": "Maak een album om foto's en video's te delen met mensen in je netwerk", "no_uploads_in_progress": "Geen uploads bezig", + "not_available": "n.v.t.", "not_in_any_album": "Niet in een album", "not_selected": "Niet geselecteerd", "note_apply_storage_label_to_previously_uploaded assets": "Opmerking: om het opslaglabel toe te passen op eerder geüploade items, voer de volgende taak uit", @@ -1409,13 +1448,16 @@ "notifications": "Meldingen", "notifications_setting_description": "Beheer meldingen", "oauth": "OAuth", + "obtainium_configurator": "Obtainium instellen", + "obtainium_configurator_instructions": "Met Obtainium kan je de Androidapp direct van de GitHub-releases installeren. Maak een API-sleutel aan en kies een variant om de Obtainium-configuratielink te maken", + "ocr": "OCR", "official_immich_resources": "Officiële Immich bronnen", "offline": "Offline", "offset": "Verrekening", "ok": "Ok", "oldest_first": "Oudste eerst", "on_this_device": "Op dit apparaat", - "onboarding": "Onboarding", + "onboarding": "Introductie", "onboarding_locale_description": "Selecteer je voorkeurstaal. Je dan dit later wijzigen in je instellingen.", "onboarding_privacy_description": "De volgende (optionele) functies zijn afhankelijk van externe services en kunnen op elk moment worden uitgeschakeld in de instellingen.", "onboarding_server_welcome_description": "Laten we je instantie instellen met een aantal veelgebruikte instellingen.", @@ -1502,9 +1544,9 @@ "photos_count": "{count, plural, one {{count, number} foto} other {{count, number} foto's}}", "photos_from_previous_years": "Foto's van voorgaande jaren", "pick_a_location": "Kies een locatie", - "pin_code_changed_successfully": "PIN code succesvol gewijzigd", - "pin_code_reset_successfully": "PIN code succesvol gereset", - "pin_code_setup_successfully": "PIN code succesvol ingesteld", + "pin_code_changed_successfully": "Pincode succesvol gewijzigd", + "pin_code_reset_successfully": "Pincode succesvol gereset", + "pin_code_setup_successfully": "Pincode succesvol ingesteld", "pin_verification": "Pincodeverificatie", "place": "Plaats", "places": "Plaatsen", @@ -1513,10 +1555,14 @@ "play_memories": "Herinneringen afspelen", "play_motion_photo": "Bewegingsfoto afspelen", "play_or_pause_video": "Video afspelen of pauzeren", + "play_original_video": "Originele video afspelen", + "play_original_video_setting_description": "Originele video afspelen in plaats van de getranscodeerde video's. Als het origineel niet compatibel is, zou deze verkeerd weergegeven kunnen worden.", + "play_transcoded_video": "Getranscodeerde video afspelen", "please_auth_to_access": "Verifieer om toegang te krijgen", "port": "Poort", "preferences_settings_subtitle": "Beheer de voorkeuren van de app", "preferences_settings_title": "Voorkeuren", + "preparing": "Voorbereiden", "preset": "Voorinstelling", "preview": "Voorbeeld", "previous": "Vorige", @@ -1529,13 +1575,9 @@ "privacy": "Privacy", "profile": "Profiel", "profile_drawer_app_logs": "Logs", - "profile_drawer_client_out_of_date_major": "Mobiele app is verouderd. Werk bij naar de nieuwste hoofdversie.", - "profile_drawer_client_out_of_date_minor": "Mobiele app is verouderd. Werk bij naar de nieuwste subversie.", "profile_drawer_client_server_up_to_date": "App en server zijn up-to-date", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Alleen-lezen-modus ingeschakeld. Druk lang op je profielfoto om te verlaten.", - "profile_drawer_server_out_of_date_major": "Server is verouderd. Werk bij naar de nieuwste hoofdversie.", - "profile_drawer_server_out_of_date_minor": "Server is verouderd. Werk bij naar de nieuwste subversie.", "profile_image_of_user": "Profielfoto van {user}", "profile_picture_set": "Profielfoto ingesteld.", "public_album": "Openbaar album", @@ -1558,7 +1600,7 @@ "purchase_input_suggestion": "Heb je een licentiesleutel? Voer deze hieronder in", "purchase_license_subtitle": "Koop Immich om de verdere ontwikkeling van de service te ondersteunen", "purchase_lifetime_description": "Levenslange aankoop", - "purchase_option_title": "AANKOOP MOGELIJKHEDEN", + "purchase_option_title": "AANKOOPMOGELIJKHEDEN", "purchase_panel_info_1": "Het bouwen van Immich kost veel tijd en moeite, en we hebben fulltime engineers die eraan werken om het zo goed mogelijk te maken. Onze missie is om open-source software en ethische bedrijfspraktijken een duurzame inkomstenbron te laten worden voor ontwikkelaars en een ecosysteem te creëren dat de privacy respecteert met echte alternatieven voor uitbuitende cloudservices.", "purchase_panel_info_2": "Omdat we ons inzetten om geen paywalls toe te voegen, krijg je met deze aankoop geen extra functies in Immich. We vertrouwen op gebruikers zoals jij om de verdere ontwikkeling van Immich te ondersteunen.", "purchase_panel_title": "Steun het project", @@ -1572,7 +1614,7 @@ "purchase_server_description_2": "Supporterstatus", "purchase_server_title": "Server", "purchase_settings_server_activated": "De licentiesleutel van de server wordt beheerd door de beheerder", - "query_asset_id": "Query Asset ID", + "query_asset_id": "Item-ID opvragen", "queue_status": "Wachtrij {count}/{total}", "rating": "Sterwaardering", "rating_clear": "Waardering verwijderen", @@ -1582,6 +1624,7 @@ "read_changelog": "Lees wijzigingen", "readonly_mode_disabled": "Alleen-lezen modus uitgeschakeld", "readonly_mode_enabled": "Alleen-lezen modus ingeschakeld", + "ready_for_upload": "Klaar voor upload", "reassign": "Opnieuw toewijzen", "reassigned_assets_to_existing_person": "{count, plural, one {# item} other {# items}} opnieuw toegewezen aan {name, select, null {een bestaand persoon} other {{name}}}", "reassigned_assets_to_new_person": "{count, plural, one {# item} other {# items}} opnieuw toegewezen aan een nieuw persoon", @@ -1606,6 +1649,7 @@ "regenerating_thumbnails": "Thumbnails opnieuw aan het genereren", "remote": "Externe", "remote_assets": "Externe Items", + "remote_media_summary": "Online media samenvatting", "remove": "Verwijderen", "remove_assets_album_confirmation": "Weet je zeker dat je {count, plural, one {# item} other {# items}} uit het album wilt verwijderen?", "remove_assets_shared_link_confirmation": "Weet je zeker dat je {count, plural, one {# item} other {# items}} uit deze gedeelde link wilt verwijderen?", @@ -1642,10 +1686,10 @@ "reset": "Resetten", "reset_password": "Wachtwoord resetten", "reset_people_visibility": "Zichtbaarheid mensen resetten", - "reset_pin_code": "Reset PIN code", - "reset_pin_code_description": "Als je jouw PIN code bent vergeten, neem dan contact op met de administrator van de server om deze te resetten", - "reset_pin_code_success": "Resetten van PIN code gelukt", - "reset_pin_code_with_password": "Je kan altijd je PIN code resetten met je wachtwoord", + "reset_pin_code": "Reset pincode", + "reset_pin_code_description": "Als je jouw pincode bent vergeten, neem dan contact op met de administrator van de server om deze te resetten", + "reset_pin_code_success": "Resetten van pincode gelukt", + "reset_pin_code_with_password": "Je kan je pincode altijd resetten met je wachtwoord", "reset_sqlite": "SQLite database resetten", "reset_sqlite_confirmation": "Ben je zeker dat je de SQLite database wilt resetten? Je zal moeten uitloggen om de data opnieuw te synchroniseren", "reset_sqlite_success": "De SQLite database is succesvol gereset", @@ -1684,6 +1728,9 @@ "search_by_description_example": "Wandelen in Sapa", "search_by_filename": "Zoeken op bestandsnaam of -extensie", "search_by_filename_example": "b.v. IMG_1234.JPG of PNG", + "search_by_ocr": "Zoeken op tekst herkend door OCR", + "search_by_ocr_example": "Kaneel", + "search_camera_lens_model": "Zoek cameralens…", "search_camera_make": "Zoek cameramerk...", "search_camera_model": "Zoek cameramodel...", "search_city": "Zoek stad...", @@ -1700,6 +1747,7 @@ "search_filter_location_title": "Selecteer locatie", "search_filter_media_type": "Mediatype", "search_filter_media_type_title": "Selecteer mediatype", + "search_filter_ocr": "Zoeken op tekst herkend door OCR", "search_filter_people_title": "Selecteer mensen", "search_for": "Zoeken naar", "search_for_existing_person": "Zoek naar bestaande persoon", @@ -1735,7 +1783,7 @@ "second": "Seconde", "see_all_people": "Bekijk alle mensen", "select": "Selecteer", - "select_album_cover": "Selecteer album cover", + "select_album_cover": "Selecteer albumomslag", "select_all": "Alles selecteren", "select_all_duplicates": "Selecteer alle duplicaten", "select_all_in": "Selecteer alles in {group}", @@ -1752,7 +1800,7 @@ "select_user_for_sharing_page_err_album": "Album aanmaken mislukt", "selected": "Geselecteerd", "selected_count": "{count, plural, other {# geselecteerd}}", - "selected_gps_coordinates": "Geselecteerde GPS Coördinaten", + "selected_gps_coordinates": "Geselecteerde gps-coördinaten", "send_message": "Bericht versturen", "send_welcome_email": "Stuur welkomstmail", "server_endpoint": "Server-URL", @@ -1762,9 +1810,10 @@ "server_online": "Server online", "server_privacy": "Serverprivacy", "server_stats": "Serverstatistieken", + "server_update_available": "Server update is beschikbaar", "server_version": "Serverversie", "set": "Instellen", - "set_as_album_cover": "Stel in als album cover", + "set_as_album_cover": "Stel in als albumomslag", "set_as_featured_photo": "Instellen als uitgelichte foto", "set_as_profile_picture": "Instellen als profielfoto", "set_date_of_birth": "Geboortedatum instellen", @@ -1790,13 +1839,15 @@ "setting_notifications_subtitle": "Voorkeuren voor meldingen beheren", "setting_notifications_total_progress_subtitle": "Algehele uploadvoortgang (voltooid/totaal aantal items)", "setting_notifications_total_progress_title": "Totale voortgang van achtergrond back-up tonen", + "setting_video_viewer_auto_play_subtitle": "Speel video's automatisch af zodra ze geopend worden", + "setting_video_viewer_auto_play_title": "Video's automatisch afspelen", "setting_video_viewer_looping_title": "Herhalen", "setting_video_viewer_original_video_subtitle": "Speel video's altijd in originele kwaliteit af, zelfs als er een getranscodeerd bestand beschikbaar is op de server. Dit kan leiden tot buffering. Video's die lokaal beschikbaar zijn, worden altijd in originele kwaliteit afgespeeld, ongeacht deze instelling.", "setting_video_viewer_original_video_title": "Forceer originele videokwaliteit", "settings": "Instellingen", "settings_require_restart": "Start Immich opnieuw op om deze instelling toe te passen", "settings_saved": "Instellingen opgeslagen", - "setup_pin_code": "Stel een PIN code in", + "setup_pin_code": "Stel een pincode in", "share": "Delen", "share_action_prompt": "{count} item(s) gedeeld", "share_add_photos": "Foto's toevoegen", @@ -1881,6 +1932,7 @@ "show_slideshow_transition": "Diavoorstellingsovergang tonen", "show_supporter_badge": "Supportersbadge", "show_supporter_badge_description": "Toon een supportersbadge", + "show_text_search_menu": "Laat tekst zoek menu zien", "shuffle": "Willekeurig", "sidebar": "Zijbalk", "sidebar_display_description": "Toon een link naar deze pagina in de zijbalk", @@ -1911,6 +1963,7 @@ "stacktrace": "Stacktrace", "start": "Start", "start_date": "Startdatum", + "start_date_before_end_date": "Startdatum moet voor einddatum liggen", "state": "Staat", "status": "Status", "stop_casting": "Stop met casten", @@ -1930,7 +1983,7 @@ "support_and_feedback": "Ondersteuning & feedback", "support_third_party_description": "Je Immich installatie is door een derde partij samengesteld. Problemen die je ervaart, kunnen door dat pakket veroorzaakt zijn. Meld problemen in eerste instantie bij hen via de onderstaande links.", "swap_merge_direction": "Wissel richting voor samenvoegen om", - "sync": "Sync", + "sync": "Synchroniseren", "sync_albums": "Albums synchroniseren", "sync_albums_manual_subtitle": "Synchroniseer alle geüploade video’s en foto’s naar de geselecteerde back-up albums", "sync_local": "Lokaal synchroniseren", @@ -1998,8 +2051,9 @@ "trashed_items_will_be_permanently_deleted_after": "Items in de prullenbak worden na {days, plural, one {# dag} other {# dagen}} permanent verwijderd.", "troubleshoot": "Problemen oplossen", "type": "Type", - "unable_to_change_pin_code": "PIN code kan niet gewijzigd worden", - "unable_to_setup_pin_code": "PIN code kan niet ingesteld worden", + "unable_to_change_pin_code": "Pincode kan niet gewijzigd worden", + "unable_to_check_version": "Kan app-/serverversie niet checken", + "unable_to_setup_pin_code": "Pincode kan niet ingesteld worden", "unarchive": "Herstellen uit archief", "unarchive_action_prompt": "{count} verwijderd uit het archief", "unarchived_count": "{count, plural, other {# verwijderd uit archief}}", @@ -2056,8 +2110,8 @@ "user_has_been_deleted": "Deze gebruiker is verwijderd.", "user_id": "Gebruikers ID", "user_liked": "{user} heeft {type, select, photo {deze foto} video {deze video} asset {} other {dit item}} geliket", - "user_pin_code_settings": "PIN Code", - "user_pin_code_settings_description": "Beheer je PIN code", + "user_pin_code_settings": "Pincode", + "user_pin_code_settings_description": "Beheer je pincode", "user_privacy": "Gebruikersprivacy", "user_purchase_settings": "Kopen", "user_purchase_settings_description": "Beheer je aankoop", @@ -2078,8 +2132,8 @@ "version_history": "Versiegeschiedenis", "version_history_item": "{version} geïnstalleerd op {date}", "video": "Video", - "video_hover_setting": "Speel videothumbnail af bij hoveren", - "video_hover_setting_description": "Speel videothumbnail af wanneer de muis over het item beweegt. Zelfs wanneer uitgeschakeld, kan het afspelen worden gestart door de muis over het afspeelpictogram te bewegen.", + "video_hover_setting": "Speel videominiatuur af bij hoveren", + "video_hover_setting_description": "Speel videominiatuur af wanneer de muis over het item beweegt. Zelfs wanneer uitgeschakeld, kan het afspelen worden gestart door de muis over het afspeelpictogram te bewegen.", "videos": "Video's", "videos_count": "{count, plural, one {# video} other {# video's}}", "view": "Bekijken", @@ -2091,14 +2145,14 @@ "view_link": "Bekijk link", "view_links": "Links bekijken", "view_name": "Bekijken", - "view_next_asset": "Bekijk volgende item", - "view_previous_asset": "Bekijk vorige item", + "view_next_asset": "Bekijk volgend item", + "view_previous_asset": "Bekijk vorig item", "view_qr_code": "QR-code bekijken", "view_similar_photos": "Bekijk vergelijkbare foto's", "view_stack": "Bekijk stapel", "view_user": "Bekijk gebruiker", - "viewer_remove_from_stack": "Verwijder van Stapel", - "viewer_stack_use_as_main_asset": "Gebruik als Hoofd Item", + "viewer_remove_from_stack": "Verwijder van stapel", + "viewer_stack_use_as_main_asset": "Zet bovenaan de stapel", "viewer_unstack": "Ontstapel", "visibility_changed": "Zichtbaarheid gewijzigd voor {count, plural, one {# persoon} other {# mensen}}", "waiting": "Wachtend", @@ -2113,5 +2167,6 @@ "yes": "Ja", "you_dont_have_any_shared_links": "Je hebt geen gedeelde links", "your_wifi_name": "Je WiFi-naam", - "zoom_image": "Inzoomen" + "zoom_image": "Inzoomen", + "zoom_to_bounds": "Zoom naar randen" } diff --git a/i18n/nn.json b/i18n/nn.json index 7b2f256c94..2cfb31bede 100644 --- a/i18n/nn.json +++ b/i18n/nn.json @@ -293,7 +293,6 @@ "comments_and_likes": "Kommentarar og likerklikk", "comments_are_disabled": "Kommentering er slått av", "common_create_new_album": "Lag nytt album", - "common_server_error": "Kontroller nettverkstilkoplinga di, sørg for at tenaren er tilgjengeleg, og at app- og tenarversjonane er kompatible.", "completed": "Fullført", "confirm": "Stadfest", "confirm_admin_password": "Stadfest administratorpassord", @@ -328,7 +327,6 @@ "duplicates": "Duplikat", "duration": "Lengde", "edit": "Rediger", - "edited": "Redigert", "editor": "Redigeringsverktøy", "explore": "Utforsk", "explorer": "Utforsker", diff --git a/i18n/pa.json b/i18n/pa.json index 0967ef424b..0801a54351 100644 --- a/i18n/pa.json +++ b/i18n/pa.json @@ -1 +1,21 @@ -{} +{ + "about": "ਐਪ ਬਾਰੇ", + "account": "ਖ਼ਾਤਾ", + "account_settings": "ਖ਼ਾਤਾ ਸੈਟਿੰਗਾਂ", + "action": "ਕਾਰਵਾਈ", + "action_common_update": "ਅੱਪਡੇਟ", + "actions": "ਕਾਰਵਾਈਆਂ", + "active": "ਕਿਰਿਆਸ਼ੀਲ", + "activity": "ਗਤੀਵਿਧੀ", + "add": "ਸ਼ਾਮਲ ਕਰੋ", + "add_a_description": "ਵੇਰਵਾ ਸ਼ਾਮਲ ਕਰੋ", + "add_a_location": "ਇੱਕ ਸਥਾਨ ਸ਼ਾਮਲ ਕਰੋ", + "add_a_name": "ਨਾਮ ਸ਼ਾਮਲ ਕਰੋ", + "add_a_title": "ਸਿਰਲੇਖ ਸ਼ਾਮਲ ਕਰੋ", + "add_birthday": "ਜਨਮਦਿਨ ਸ਼ਾਮਲ ਕਰੋ", + "add_endpoint": "ਐਂਡਪੁਆਇੰਟ ਸ਼ਾਮਲ ਕਰੋ", + "add_exclusion_pattern": "ਅਲਹਿਦਗੀ ਪੈਟਰਨ ਸ਼ਾਮਲ ਕਰੋ", + "add_import_path": "ਆਯਾਤ ਮਾਰਗ ਸ਼ਾਮਲ ਕਰੋ", + "add_location": "ਸਥਾਨ ਸ਼ਾਮਲ ਕਰੋ", + "add_more_users": "ਹੋਰ ਉਪਭੋਗਤਾ ਸ਼ਾਮਲ ਕਰੋ" +} diff --git a/i18n/pl.json b/i18n/pl.json index f0bd407bab..8bbefef148 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -28,10 +28,12 @@ "add_to_album": "Dodaj do albumu", "add_to_album_bottom_sheet_added": "Dodano do {album}", "add_to_album_bottom_sheet_already_exists": "Już jest w {album}", + "add_to_album_bottom_sheet_some_local_assets": "Niektóre lokalne zasoby nie mogły zostać dodane do albumu", "add_to_album_toggle": "Przełącz wybieranie dla {album}", "add_to_albums": "Dodaj do albumów", "add_to_albums_count": "Dodaj do albumów ({count})", "add_to_shared_album": "Dodaj do udostępnionego albumu", + "add_upload_to_stack": "Dodaj przesłane do stosu", "add_url": "Dodaj URL", "added_to_archive": "Dodano do archiwum", "added_to_favorites": "Dodano do ulubionych", @@ -118,7 +120,7 @@ "library_settings_description": "Zarządzaj ustawieniami zewnętrznych bibliotek", "library_tasks_description": "Wyszukiwanie nowych lub zmienionych pozycji w zewnętrznych bibliotekach", "library_watching_enable_description": "Przejrzyj zewnętrzne biblioteki w poszukiwaniu zmienionych plików", - "library_watching_settings": "Obserwowanie bibliotek (Funkcja eksperymentalna)", + "library_watching_settings": "Obserwowanie bibliotek [EKSPERYMENTALNE]", "library_watching_settings_description": "Automatycznie obserwuj zmienione pliki", "logging_enable_description": "Uruchom zapisywanie logów", "logging_level_description": "Kiedy włączone, jakiego poziomu użyć.", @@ -148,10 +150,22 @@ "machine_learning_max_detection_distance_description": "Maksymalna odległość między dwoma obrazami, aby uznać je za duplikaty, w zakresie od 0,001-0,1. Wyższe wartości wykryją więcej duplikatów, ale mogą skutkować błędnymi grupowaniami.", "machine_learning_max_recognition_distance": "Maksymalny dystans rozpoznania", "machine_learning_max_recognition_distance_description": "Maksymalna odległość między dwiema twarzami, którą należy uznać za tę samą osobę, waha się od 0-2. Obniżenie tej wartości może zapobiec grupowaniu dwóch osób jako tej samej osoby. Pamiętaj, że łatwiej jest połączyć dwie osoby niż podzielić jedną osobę na dwie, więc jeśli to możliwe, staraj się ustawić jak najmniejszą wartość, która będzie spełniała twoje wymagania.", - "machine_learning_min_detection_score": "Minimalny wynik rozpoznania", + "machine_learning_min_detection_score": "Minimalny wskaźnik wykrywalności", "machine_learning_min_detection_score_description": "Minimalny poziom uznania twarzy za twarz. Wartość mieści się w zakresie 0-1. Niższe wartości pozwolą wykryć więcej twarzy, ale mogą skutkować znajdywaniem twarz tam, gdzie ich nie ma.", "machine_learning_min_recognized_faces": "Minimum rozpoznanych twarzy", "machine_learning_min_recognized_faces_description": "Minimalna liczba rozpoznanych twarzy, zanim zostaną one powiązane jako osoba. Zwiększenie tej wartości spowoduje, że rozpoznawanie twarzy jest bardziej precyzyjne, lecz kosztem zwiększenia ryzyka, że twarz nie zostanie przypisana do jakiejkolwiek osoby.", + "machine_learning_ocr": "OCR", + "machine_learning_ocr_description": "Wykorzystaj uczenie maszynowe do rozpoznawania tekstu na zdjęciach", + "machine_learning_ocr_enabled": "Włącz OCR", + "machine_learning_ocr_enabled_description": "Jeśli opcja jest wyłączona, obrazy nie będą poddawane rozpoznawaniu tekstu.", + "machine_learning_ocr_max_resolution": "Maksymalna rozdzielczość", + "machine_learning_ocr_max_resolution_description": "Podglądy powyżej tej rozdzielczości zostaną przeskalowane z zachowaniem proporcji. Wyższe wartości są dokładniejsze, ale ich przetwarzanie trwa dłużej i zajmuje więcej pamięci.", + "machine_learning_ocr_min_detection_score": "Minimalny wskaźnik wykrywalności", + "machine_learning_ocr_min_detection_score_description": "Minimalny wskaźnik pewności, aby tekst został wykryty, w zakresie 0-1. Niższe wartości pozwolą wykryć więcej tekstu, ale mogą skutkować wynikami fałszywie dodatnimi.", + "machine_learning_ocr_min_recognition_score": "Minimalny wskaźnik rozpoznawalności", + "machine_learning_ocr_min_score_recognition_description": "Minimalny wskaźnik pewności dla wykrytego tekstu, aby został rozpoznany, w zakresie 0-1. Niższe wartości rozpoznają więcej tekstu, ale mogą skutkować wynikami fałszywie dodatnimi.", + "machine_learning_ocr_model": "Model OCR", + "machine_learning_ocr_model_description": "Modele serwerowe są dokładniejsze niż modele mobilne, ale dłużej przetwarzają dane i zużywają więcej pamięci.", "machine_learning_settings": "Ustawienia Uczenia Maszynowego", "machine_learning_settings_description": "Zarządzaj ustawieniami i funkcjami uczenia maszynowego", "machine_learning_smart_search": "Inteligentne Wyszukiwanie", @@ -209,6 +223,8 @@ "notification_email_ignore_certificate_errors_description": "Ignoruj błąd walidacji certyfikatu TLS (nie zalecane)", "notification_email_password_description": "Hasło do serwera poczty", "notification_email_port_description": "Port serwera poczty (np. 25, 465 lub 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "Użyj SMTPS (SMTP over TLS)", "notification_email_sent_test_email_button": "Wyślij testowego maila i zapisz", "notification_email_setting_description": "Ustawienia powiadomień e-mail", "notification_email_test_email": "Wyślij e-mail testowy", @@ -241,6 +257,7 @@ "oauth_storage_quota_default_description": "Limit w GiB do wykorzystania, gdy nie podano żadnej wartości.", "oauth_timeout": "Upłynął czas żądania", "oauth_timeout_description": "Limit czasu żądania w milisekundach", + "ocr_job_description": "Wykorzystaj uczenie maszynowe do rozpoznawania tekstu na zdjęciach", "password_enable_description": "Zaloguj używając e-mail i hasła", "password_settings": "Logowanie Hasłem", "password_settings_description": "Zarządzaj ustawieniami logowania hasłem", @@ -331,7 +348,7 @@ "transcoding_max_b_frames": "Maksymalne klatki B (B-Frames)", "transcoding_max_b_frames_description": "Wyższe wartości poprawiają wydajność kompresji, ale spowalniają kodowanie. Może nie być kompatybilny z akceleracją sprzętową na starszych urządzeniach. 0 wyłącza klatki B (B-frames), natomiast -1 ustawia tę wartość automatycznie.", "transcoding_max_bitrate": "Maksymalna szybkość transmisji", - "transcoding_max_bitrate_description": "Ustawienie maksymalnej szybkości transmisji może sprawić, że rozmiary plików będą bardziej przewidywalne przy niewielkim koszcie na jakość. Przy rozdzielczości 720p typowe wartości to 2600 kbit/s dla VP9 lub HEVC, lub 4500 kbit/s dla H.264. Wyłączone, jeśli ustawione na 0.", + "transcoding_max_bitrate_description": "Ustawienie maksymalnej szybkości transmisji może sprawić, że rozmiary plików będą bardziej przewidywalne przy niewielkim koszcie na jakość. Przy rozdzielczości 720p typowe wartości to 2600 kbit/s dla VP9 lub HEVC, lub 4500 kbit/s dla H.264. Wyłączone, jeśli ustawione na 0. Jeśli nie podano jednostki, przyjmuje się k (dla kbit/s), dlatego 5000, 5000k i 5M (dla Mbit/s) są równoznaczne.", "transcoding_max_keyframe_interval": "Maksymalny interwał klatek kluczowych", "transcoding_max_keyframe_interval_description": "Ustawia maksymalny dystans między klatkami kluczowymi. Niższe wartości przyspieszają przeszukiwanie filmów i mogą poprawić jakość w scenach z dużą ilością ruchu, kosztem gorszej efektywności kompresji. 0 ustawia tą wartość automatycznie.", "transcoding_optimal_description": "Filmy w rozdzielczości wyższej niż docelowa lub w nieakceptowanym formacie", @@ -349,7 +366,7 @@ "transcoding_target_resolution": "Docelowa rozdzielczość", "transcoding_target_resolution_description": "Wyższe rozdzielczości pozwalają zachować więcej szczegółów, ale kodowanie zajmuje więcej czasu, powoduje większe rozmiary plików i może zmniejszyć płynność aplikacji.", "transcoding_temporal_aq": "Tymczasowe (Temporal) AQ", - "transcoding_temporal_aq_description": "Dotyczy tylko kodeka NVENC. Zwiększa jakość scen o dużej szczegółowości i małym ruchu. Może nie być kompatybilny ze starszymi urządzeniami.", + "transcoding_temporal_aq_description": "Dotyczy tylko kodeka NVENC. Temporal Adaptive Quantization zwiększa jakość scen o dużej szczegółowości i małym ruchu. Może nie być kompatybilny ze starszymi urządzeniami.", "transcoding_threads": "Wątki", "transcoding_threads_description": "Wyższe wartości prowadzą do szybszego kodowania, ale pozostawiają mniej zasobów serwerowi na przetwarzanie innych zadań, gdy jest ono aktywne. Wartość ta nie powinna być większa niż liczba rdzeni procesora. Maksymalizuje wykorzystanie, jeśli jest ustawione na 0.", "transcoding_tone_mapping": "Mapowanie tonów", @@ -400,11 +417,11 @@ "advanced_settings_prefer_remote_subtitle": "Niektóre urządzenia bardzo wolno ładują miniatury z lokalnych zasobów. Aktywuj to ustawienie, aby ładować zdalne obrazy.", "advanced_settings_prefer_remote_title": "Preferuj obrazy zdalne", "advanced_settings_proxy_headers_subtitle": "Zdefiniuj nagłówki proxy, które Immich powinien wysyłać z każdym żądaniem sieciowym", - "advanced_settings_proxy_headers_title": "Nagłówki proxy", + "advanced_settings_proxy_headers_title": "Niestandardowe nagłówki proxy [EKSPERYMENTALNE]", "advanced_settings_readonly_mode_subtitle": "Włącza tryb tylko do odczytu, w którym zdjęcia można tylko przeglądać, a takie czynności jak wybieranie wielu obrazów, udostępnianie, przesyłanie i usuwanie są wyłączone. Włącz/wyłącz tryb tylko do odczytu za pomocą awatara użytkownika na ekranie głównym", "advanced_settings_readonly_mode_title": "Tryb tylko do odczytu", "advanced_settings_self_signed_ssl_subtitle": "Pomija weryfikację certyfikatu SSL dla punktu końcowego serwera. Wymagane w przypadku certyfikatów z podpisem własnym.", - "advanced_settings_self_signed_ssl_title": "Zezwalaj na certyfikaty SSL z podpisem własnym", + "advanced_settings_self_signed_ssl_title": "Zezwól na certyfikaty SSL z podpisem własnym [EKSPERYMENTALNE]", "advanced_settings_sync_remote_deletions_subtitle": "Automatycznie usuń lub przywróć zasób na tym urządzeniu po wykonaniu tej czynności w interfejsie webowym", "advanced_settings_sync_remote_deletions_title": "Synchronizuj zdalne usunięcia [EKSPERYMENTALNE]", "advanced_settings_tile_subtitle": "Zaawansowane ustawienia użytkownika", @@ -464,10 +481,14 @@ "api_key_description": "Widzisz tę wartość po raz pierwszy i ostatni, więc lepiej ją skopiuj przed zamknięciem okna.", "api_key_empty": "Twój Klucz API nie powinien być pusty", "api_keys": "Klucze API", + "app_architecture_variant": "Wariant (Architektura)", "app_bar_signout_dialog_content": "Czy na pewno chcesz się wylogować?", "app_bar_signout_dialog_ok": "Tak", "app_bar_signout_dialog_title": "Wyloguj się", + "app_download_links": "Linki do pobrania aplikacji", "app_settings": "Ustawienia aplikacji", + "app_stores": "Sklepy z aplikacjami", + "app_update_available": "Dostępna jest aktualizacja aplikacji", "appears_in": "W albumach", "apply_count": "Zastosuj ({count, number})", "archive": "Archiwum", @@ -551,6 +572,7 @@ "backup_albums_sync": "Synchronizacja kopii zapasowych albumów", "backup_all": "Wszystkie", "backup_background_service_backup_failed_message": "Nie udało się wykonać kopii zapasowej zasobów. Ponowna próba…", + "backup_background_service_complete_notification": "Kopia zapasowa zasobu zakończona", "backup_background_service_connection_failed_message": "Nie udało się połączyć z serwerem. Ponowna próba…", "backup_background_service_current_upload_notification": "Przesyłanie {filename}", "backup_background_service_default_notification": "Sprawdzanie nowych zasobów…", @@ -598,6 +620,7 @@ "backup_controller_page_turn_on": "Włącz kopię zapasową na pierwszym planie", "backup_controller_page_uploading_file_info": "Informacje o przesyłanym pliku", "backup_err_only_album": "Nie można usunąć jedynego albumu", + "backup_error_sync_failed": "Synchronizacja nie powiodła się. Nie można wykonać kopii zapasowej.", "backup_info_card_assets": "zasoby", "backup_manual_cancelled": "Anulowano", "backup_manual_in_progress": "Przesyłanie już trwa. Spróbuj po pewnym czasie", @@ -659,6 +682,8 @@ "change_password_description": "Logujesz się po raz pierwszy lub wysłano prośbę o zmianę hasła. Wprowadź nowe hasło poniżej.", "change_password_form_confirm_password": "Potwierdź Hasło", "change_password_form_description": "Cześć {name},\n\nPierwszy raz logujesz się do systemu, albo złożono prośbę o zmianę hasła. Wpisz poniżej nowe hasło.", + "change_password_form_log_out": "Wyloguj wszystkie inne urządzenia", + "change_password_form_log_out_description": "Zaleca się wylogowanie się ze wszystkich innych urządzeń", "change_password_form_new_password": "Nowe Hasło", "change_password_form_password_mismatch": "Hasła nie są zgodne", "change_password_form_reenter_new_password": "Wprowadź ponownie Nowe Hasło", @@ -674,10 +699,10 @@ "choose_matching_people_to_merge": "Wybierz osoby, aby złączyć je w jedną", "city": "Miasto", "clear": "Wyczyść", - "clear_all": "Wyczyść", + "clear_all": "Wyczyść wszystko", "clear_all_recent_searches": "Usuń ostatnio wyszukiwane", "clear_file_cache": "Wyczyść pamięć podręczną plików", - "clear_message": "Zamknij wiadomość", + "clear_message": "Wyczyść wiadomość", "clear_value": "Wyczyść wartość", "client_cert_dialog_msg_confirm": "OK", "client_cert_enter_password": "Wprowadź hasło", @@ -685,8 +710,8 @@ "client_cert_import_success_msg": "Certyfikat klienta został zaimportowany", "client_cert_invalid_msg": "Nieprawidłowy plik certyfikatu lub nieprawidłowe hasło", "client_cert_remove_msg": "Certyfikat klienta został usunięty", - "client_cert_subtitle": "Obsługuje tylko format PKCS12 (.p12, .pfx). Import/Usunięcie certyfikatu jest dostępne tylko przed zalogowaniem", - "client_cert_title": "Certyfikat klienta SSL", + "client_cert_subtitle": "Obsługuje wyłącznie format PKCS12 (.p12, .pfx). Importowanie/usuwanie certyfikatów jest dostępne wyłącznie przed zalogowaniem", + "client_cert_title": "Certyfikat klienta SSL [EKSPERYMENTALNE]", "clockwise": "Zgodnie z ruchem wskazówek zegara", "close": "Zamknij", "collapse": "Zwiń", @@ -698,7 +723,6 @@ "comments_and_likes": "Komentarze i polubienia", "comments_are_disabled": "Komentarze są wyłączone", "common_create_new_album": "Utwórz nowy album", - "common_server_error": "Sprawdź połączenie sieciowe, upewnij się, że serwer jest osiągalny i wersje aplikacji/serwera są kompatybilne.", "completed": "Ukończono", "confirm": "Potwierdź", "confirm_admin_password": "Potwierdź Hasło Administratora", @@ -737,6 +761,7 @@ "create": "Utwórz", "create_album": "Utwórz album", "create_album_page_untitled": "Bez tytułu", + "create_api_key": "Utwórz klucz API", "create_library": "Stwórz Bibliotekę", "create_link": "Utwórz link", "create_link_to_share": "Utwórz link do udostępnienia", @@ -766,6 +791,7 @@ "daily_title_text_date_year": "E, dd MMM, yyyy", "dark": "Ciemny", "dark_theme": "Przełącz ciemny motyw", + "date": "Data", "date_after": "Data po", "date_and_time": "Data i godzina", "date_before": "Data przed", @@ -880,7 +906,6 @@ "edit_tag": "Edytuj etykietę", "edit_title": "Edytuj Tytuł", "edit_user": "Edytuj użytkownika", - "edited": "Edytowane", "editor": "Edytor", "editor_close_without_save_prompt": "Zmiany nie zostaną zapisane", "editor_close_without_save_title": "Zamknąć edytor?", @@ -1036,6 +1061,7 @@ "exif_bottom_sheet_description_error": "Wystąpił błąd podczas aktualizacji opisu", "exif_bottom_sheet_details": "SZCZEGÓŁY", "exif_bottom_sheet_location": "LOKALIZACJA", + "exif_bottom_sheet_no_description": "Brak opisu", "exif_bottom_sheet_people": "LUDZIE", "exif_bottom_sheet_person_add_person": "Dodaj nazwę", "exit_slideshow": "Zamknij Pokaz Slajdów", @@ -1074,6 +1100,7 @@ "features_setting_description": "Zarządzaj funkcjami aplikacji", "file_name": "Nazwa pliku", "file_name_or_extension": "Nazwie lub rozszerzeniu pliku", + "file_size": "Rozmiar pliku", "filename": "Nazwa pliku", "filetype": "Typ pliku", "filter": "Filtr", @@ -1117,7 +1144,6 @@ "header_settings_field_validator_msg": "Wartość nie może być pusta", "header_settings_header_name_input": "Nazwa nagłówka", "header_settings_header_value_input": "Wartość nagłówka", - "headers_settings_tile_subtitle": "Zdefiniuj nagłówki proxy, które aplikacja powinna wysyłać z każdym żądaniem sieciowym", "headers_settings_tile_title": "Niestandardowe nagłówki proxy", "hi_user": "Cześć {name} ({email})", "hide_all_people": "Ukryj wszystkie osoby", @@ -1238,6 +1264,7 @@ "local_media_summary": "Podsumowanie lokalnych mediów", "local_network": "Sieć lokalna", "local_network_sheet_info": "Aplikacja połączy się z serwerem za pośrednictwem tego adresu URL podczas korzystania z określonej sieci Wi-Fi", + "location": "Lokalizacja", "location_permission": "Zezwolenie na lokalizację", "location_permission_content": "Aby móc korzystać z funkcji automatycznego przełączania, Immich potrzebuje uprawnienia do dokładnej lokalizacji, aby móc odczytać nazwę bieżącej sieci Wi-Fi", "location_picker_choose_on_map": "Wybierz na mapie", @@ -1282,7 +1309,7 @@ "longitude": "Długość geograficzna", "look": "Wygląd", "loop_videos": "Powtarzaj filmy", - "loop_videos_description": "Włącz automatyczne zapętlanie wideo w przeglądarce szczegółów.", + "loop_videos_description": "Włącz automatyczne odtwarzanie w pętli filmu w widoku szczegółowym.", "main_branch_warning": "Używasz wersji deweloperskiej. Zdecydowanie zalecamy korzystanie z wydanej wersji aplikacji!", "main_menu": "Menu główne", "make": "Marka", @@ -1342,6 +1369,8 @@ "minute": "Minuta", "minutes": "Minuty", "missing": "Brakujące", + "mobile_app": "Aplikacja mobilna", + "mobile_app_download_onboarding_note": "Pobierz towarzyszącą aplikację mobilną, korzystając z następujących opcji", "model": "Model", "month": "Miesiąc", "monthly_title_text_date_format": "MMMM y", @@ -1360,6 +1389,8 @@ "my_albums": "Moje albumy", "name": "Nazwa", "name_or_nickname": "Nazwa lub pseudonim", + "navigate": "Nawiguj", + "navigate_to_time": "Nawiguj do czasu", "network_requirement_photos_upload": "Używaj danych komórkowych do tworzenia kopii zapasowych zdjęć", "network_requirement_videos_upload": "Używaj danych komórkowych do tworzenia kopii zapasowych filmów", "network_requirements": "Wymagania sieciowe", @@ -1369,6 +1400,7 @@ "never": "nigdy", "new_album": "Nowy album", "new_api_key": "Nowy Klucz API", + "new_date_range": "Nowy zakres dat", "new_password": "Nowe hasło", "new_person": "Nowa osoba", "new_pin_code": "Nowy kod PIN", @@ -1419,6 +1451,9 @@ "notifications": "Powiadomienia", "notifications_setting_description": "Zarządzanie powiadomieniami", "oauth": "OAuth", + "obtainium_configurator": "Konfigurator Obtainium", + "obtainium_configurator_instructions": "Użyj Obtainium, aby zainstalować i zaktualizować aplikację na Androida bezpośrednio z wydania GitHuba Immich. Utwórz klucz API i wybierz wariant, aby utworzyć link konfiguracyjny Obtainium", + "ocr": "OCR", "official_immich_resources": "Oficjalne zasoby Immicha", "offline": "Offline", "offset": "Przesunięcie", @@ -1523,10 +1558,14 @@ "play_memories": "Odtwórz wspomnienia", "play_motion_photo": "Odtwórz Ruchome Zdjęcie", "play_or_pause_video": "Odtwórz lub wstrzymaj wideo", + "play_original_video": "Odtwórz oryginalne wideo", + "play_original_video_setting_description": "Preferuj odtwarzanie oryginalnych nagrań wideo zamiast nagrań transkodowanych. Jeśli oryginalny zasób nie jest kompatybilny, może nie być odtwarzany poprawnie.", + "play_transcoded_video": "Odtwórz transkodowane wideo", "please_auth_to_access": "Uwierzytelnij się, aby uzyskać dostęp", "port": "Port", "preferences_settings_subtitle": "Zarządzaj preferencjami aplikacji", "preferences_settings_title": "Ustawienia", + "preparing": "Przygotowywanie", "preset": "Ustawienie", "preview": "Podgląd", "previous": "Poprzedni", @@ -1539,13 +1578,9 @@ "privacy": "Prywatność", "profile": "Profil", "profile_drawer_app_logs": "Logi", - "profile_drawer_client_out_of_date_major": "Aplikacja mobilna jest nieaktualna. Zaktualizuj do najnowszej głównej wersji.", - "profile_drawer_client_out_of_date_minor": "Aplikacja mobilna jest nieaktualna. Zaktualizuj do najnowszej pomniejszej wersji.", "profile_drawer_client_server_up_to_date": "Klient i serwer są aktualne", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Włączono tryb tylko do odczytu. Aby wyjść, naciśnij i przytrzymaj ikonę awatara użytkownika.", - "profile_drawer_server_out_of_date_major": "Serwer jest nieaktualny. Zaktualizuj do najnowszej głównej wersji.", - "profile_drawer_server_out_of_date_minor": "Serwer jest nieaktualny. Zaktualizuj do najnowszej pomniejszej wersji.", "profile_image_of_user": "Zdjęcie profilowe {user}", "profile_picture_set": "Zdjęcie profilowe ustawione.", "public_album": "Publiczny album", @@ -1592,6 +1627,7 @@ "read_changelog": "Zobacz Zmiany", "readonly_mode_disabled": "Tryb tylko do odczytu wyłączony", "readonly_mode_enabled": "Tryb tylko do odczytu włączony", + "ready_for_upload": "Gotowe do przesłania", "reassign": "Przypisz ponownie", "reassigned_assets_to_existing_person": "Przypisano ponownie {count, plural, one {# zasób} other {# zasobów}} do {name, select, null {istniejącej osoby} other {{name}}}", "reassigned_assets_to_new_person": "Przypisano ponownie {count, plural, one {# zasób} other {# zasobów}} do nowej osoby", @@ -1630,8 +1666,8 @@ "remove_from_locked_folder": "Usuń z folderu zablokowanego", "remove_from_locked_folder_confirmation": "Czy na pewno chcesz przenieść te zdjęcia i filmy z folderu zablokowanego? Będą one widoczne w bibliotece.", "remove_from_shared_link": "Usuń z udostępnionego linku", - "remove_memory": "Usuń pamięć", - "remove_photo_from_memory": "Usuń zdjęcia z tej pamięci", + "remove_memory": "Usuń wspomnienie", + "remove_photo_from_memory": "Usuń zdjęcia z tych wspomnień", "remove_tag": "Usuń tag", "remove_url": "Usuń URL", "remove_user": "Usuń użytkownika", @@ -1639,15 +1675,15 @@ "removed_from_archive": "Usunięto z archiwum", "removed_from_favorites": "Usunięto z ulubionych", "removed_from_favorites_count": "{count, plural, other {Usunięto #}} z ulubionych", - "removed_memory": "Pamięć została usunięta", - "removed_photo_from_memory": "Usunięto zdjęcie z pamięci", + "removed_memory": "Wspomnienie usunięte", + "removed_photo_from_memory": "Usunięto zdjęcie ze wspomnień", "removed_tagged_assets": "Usunięto etykietę z {count, plural, one {# zasobu} other {# zasobów}}", "rename": "Zmień nazwę", "repair": "Napraw", "repair_no_results_message": "Tutaj pojawią się nieśledzone i brakujące pliki", "replace_with_upload": "Prześlij nową wersję", "repository": "Repozytorium", - "require_password": "Wymagaj hasło", + "require_password": "Wymagaj hasła", "require_user_to_change_password_on_first_login": "Zmuś użytkownika do zmiany hasła podczas następnego logowania", "rescan": "Ponowne skanowanie", "reset": "Reset", @@ -1661,6 +1697,7 @@ "reset_sqlite_confirmation": "Czy na pewno chcesz zresetować bazę danych SQLite? Wymagane będzie wylogowanie oraz ponowne zalogowanie, aby zsynchronizować dane", "reset_sqlite_success": "Pomyślnie zresetowano bazę danych SQLite", "reset_to_default": "Przywróć ustawienia domyślne", + "resolution": "Rozdzielczość", "resolve_duplicates": "Rozwiąż problemy z duplikatami", "resolved_all_duplicates": "Rozwiązano wszystkie duplikaty", "restore": "Przywrócić", @@ -1679,6 +1716,7 @@ "running": "W trakcie", "save": "Zapisz", "save_to_gallery": "Zapisz w galerii", + "saved": "Zapisano", "saved_api_key": "Zapisany klucz API", "saved_profile": "Zapisany profil", "saved_settings": "Zapisane ustawienia", @@ -1692,9 +1730,12 @@ "search_albums": "Przeszukaj albumy", "search_by_context": "Wyszukaj według treści", "search_by_description": "Wyszukaj według opisu", - "search_by_description_example": "Jednodniowa wycieczka górska w Bieszczady", + "search_by_description_example": "Całodniowa wycieczka w Bieszczady", "search_by_filename": "Szukaj według nazwy pliku lub rozszerzenia", "search_by_filename_example": "np. IMG_1234.JPG lub PNG", + "search_by_ocr": "Wyszukaj przy użyciu OCR", + "search_by_ocr_example": "Latte", + "search_camera_lens_model": "Wyszukaj model obiektywu...", "search_camera_make": "Wyszukaj markę aparatu...", "search_camera_model": "Wyszukaj model aparatu...", "search_city": "Wyszukaj miasto...", @@ -1711,6 +1752,7 @@ "search_filter_location_title": "Wybierz lokalizację", "search_filter_media_type": "Typ multimediów", "search_filter_media_type_title": "Wybierz typ multimediów", + "search_filter_ocr": "Wyszukaj przy użyciu OCR", "search_filter_people_title": "Wybierz osoby", "search_for": "Szukaj wśród", "search_for_existing_person": "Wyszukaj istniejącą osobę", @@ -1773,6 +1815,7 @@ "server_online": "Serwer Online", "server_privacy": "Ochrona prywatności serwera", "server_stats": "Statystyki serwera", + "server_update_available": "Dostępna jest aktualizacja serwera", "server_version": "Wersja serwera", "set": "Ustaw", "set_as_album_cover": "Ustaw jako okładkę albumu", @@ -1782,11 +1825,11 @@ "set_profile_picture": "Ustaw zdjęcie profilowe", "set_slideshow_to_fullscreen": "Ustaw Pokaz slajdów na pełny ekran", "set_stack_primary_asset": "Ustaw jako główny zasób", - "setting_image_viewer_help": "Przeglądarka szczegółów najpierw ładuje małą miniaturę, następnie ładuje podgląd średniej wielkości (jeśli jest włączony), a na koniec ładuje oryginał (jeśli jest włączony).", - "setting_image_viewer_original_subtitle": "Włącz ładowanie oryginalnego obrazu w pełnej rozdzielczości (dużego!). Wyłącz, aby zmniejszyć zużycie danych (zarówno w sieci, jak i w pamięci podręcznej urządzenia).", + "setting_image_viewer_help": "Przeglądarka szczegółów najpierw ładuje małą miniaturę, następnie ładuje podgląd obrazu średniej wielkości (jeśli jest włączony), a na koniec ładuje oryginał (jeśli jest włączony).", + "setting_image_viewer_original_subtitle": "Włącz, aby załadować oryginalny obraz w pełnej rozdzielczości (duży!). Wyłącz, aby zmniejszyć zużycie danych (zarówno w sieci, jak i w pamięci podręcznej urządzenia).", "setting_image_viewer_original_title": "Załaduj oryginalny obraz", - "setting_image_viewer_preview_subtitle": "Włącz ładowanie obrazu o średniej rozdzielczości. Wyłącz opcję bezpośredniego ładowania oryginału lub używania tylko miniatury.", - "setting_image_viewer_preview_title": "Załaduj obraz podglądu", + "setting_image_viewer_preview_subtitle": "Włącz, aby załadować obraz w średniej rozdzielczości. Wyłącz, aby załadować bezpośrednio oryginał lub używać tylko miniatury.", + "setting_image_viewer_preview_title": "Załaduj podgląd obrazu", "setting_image_viewer_title": "Zdjęcia", "setting_languages_apply": "Zastosuj", "setting_languages_subtitle": "Zmień język aplikacji", @@ -1801,7 +1844,9 @@ "setting_notifications_subtitle": "Dostosuj preferencje powiadomień", "setting_notifications_total_progress_subtitle": "Ogólny postęp przesyłania (gotowe/całkowite zasoby)", "setting_notifications_total_progress_title": "Pokaż całkowity postęp tworzenia kopii zapasowej w tle", - "setting_video_viewer_looping_title": "Zapętlenie", + "setting_video_viewer_auto_play_subtitle": "Automatycznie rozpoczynaj odtwarzanie filmów po ich otwarciu", + "setting_video_viewer_auto_play_title": "Automatycznie odtwarzaj filmy", + "setting_video_viewer_looping_title": "Zapętlanie", "setting_video_viewer_original_video_subtitle": "Podczas strumieniowego przesyłania wideo z serwera odtwarzaj oryginał, nawet jeśli transkodowanie jest dostępne. Może to prowadzić do buforowania. Filmy dostępne lokalnie są odtwarzane w oryginalnej jakości niezależnie od tego ustawienia.", "setting_video_viewer_original_video_title": "Wymuś oryginalne wideo", "settings": "Ustawienia", @@ -1980,6 +2025,7 @@ "theme_setting_three_stage_loading_title": "Włączenie trójstopniowego ładowania", "they_will_be_merged_together": "Zostaną one ze sobą połączone", "third_party_resources": "Zasoby stron trzecich", + "time": "Czas", "time_based_memories": "Wspomnienia oparte na czasie", "timeline": "Oś czasu", "timezone": "Strefa czasowa", @@ -2012,6 +2058,7 @@ "troubleshoot": "Rozwiąż problemy", "type": "Typ", "unable_to_change_pin_code": "Nie można zmienić kodu PIN", + "unable_to_check_version": "Nie można sprawdzić wersji aplikacji lub serwera", "unable_to_setup_pin_code": "Nie można ustawić kodu PIN", "unarchive": "Przywróć z archiwum", "unarchive_action_prompt": "{count} usunięto z archiwum", diff --git a/i18n/pt.json b/i18n/pt.json index f039505152..4e04331ad3 100644 --- a/i18n/pt.json +++ b/i18n/pt.json @@ -28,10 +28,12 @@ "add_to_album": "Adicionar ao álbum", "add_to_album_bottom_sheet_added": "Adicionado a {album}", "add_to_album_bottom_sheet_already_exists": "Já existe em {album}", + "add_to_album_bottom_sheet_some_local_assets": "Alguns conteúdos locais não puderam ser adicionados no álbum", "add_to_album_toggle": "Alternar seleção para {album}", "add_to_albums": "Adicionar aos álbuns", "add_to_albums_count": "Adicionar aos álbuns ({count})", "add_to_shared_album": "Adicionar ao álbum partilhado", + "add_upload_to_stack": "Adicionar carregamento à fila", "add_url": "Adicionar URL", "added_to_archive": "Adicionado ao arquivo", "added_to_favorites": "Adicionado aos favoritos", @@ -118,11 +120,18 @@ "library_settings_description": "Gerir definições de biblioteca externa", "library_tasks_description": "Pesquisa bibliotecas externas em busca de itens novos e/ou alterados", "library_watching_enable_description": "Analisar bibliotecas externas por alterações de ficheiros", - "library_watching_settings": "Análise de biblioteca (EXPERIMENTAL)", + "library_watching_settings": "Análise de biblioteca [EXPERIMENTAL]", "library_watching_settings_description": "Analise automaticamente por ficheiros alterados", "logging_enable_description": "Ativar registo", "logging_level_description": "Quando ativado, qual o nível de log a usar.", "logging_settings": "Registo", + "machine_learning_availability_checks": "Verificação de disponibilidade", + "machine_learning_availability_checks_description": "Detectar automaticamente e dar preferência aos servidores de aprendizagem automática disponíveis", + "machine_learning_availability_checks_enabled": "Ativar confirmações de disponibilidade", + "machine_learning_availability_checks_interval": "Confirmação de intervalo", + "machine_learning_availability_checks_interval_description": "Intervalo, em milisegundos, entre confirmações de disponibilidade", + "machine_learning_availability_checks_timeout": "Tempo limite para requisição", + "machine_learning_availability_checks_timeout_description": "Tempo limite em milissegundos para verificações de disponibilidade", "machine_learning_clip_model": "Modelo CLIP", "machine_learning_clip_model_description": "O nome do modelo CLIP definido aqui. Tome nota de que é necessário voltar a executar a tarefa de \"Pesquisa Inteligente\" para todas as imagens depois de alterar o modelo.", "machine_learning_duplicate_detection": "Deteção de Itens Duplicados", @@ -145,6 +154,18 @@ "machine_learning_min_detection_score_description": "Pontuação mínima de confiança para um rosto ser detetado, de 0 a 1. Valores mais baixos detetam mais rostos, mas poderão resultar em falsos positivos.", "machine_learning_min_recognized_faces": "Mínimo de rostos reconhecidos", "machine_learning_min_recognized_faces_description": "O número mínimo de faces reconhecidas para uma pessoa ser criada na lista. Aumentar isto torna o Reconhecimento Facial mais preciso, no entanto aumenta a probabilidade de um rosto não ser atribuído a uma pessoa.", + "machine_learning_ocr": "Reconhecimento Ótico de Caracteres (OCR)", + "machine_learning_ocr_description": "Utilizar aprendizagem de máquina para reconhecer texto dentro de imagens", + "machine_learning_ocr_enabled": "Ativar OCR", + "machine_learning_ocr_enabled_description": "Se estiver desativado, as imagens não serão utilizadas para reconhecimento de texto.", + "machine_learning_ocr_max_resolution": "Resolução máxima", + "machine_learning_ocr_max_resolution_description": "Pré-visualizações acima desta resolução serão redimensionadas mantendo a proporção. Valores mais elevados têm mais precisão, mas irão demorar mais tempo a processar e utilizam mais memória.", + "machine_learning_ocr_min_detection_score": "Pontuação mínima de deteção", + "machine_learning_ocr_min_detection_score_description": "Pontuação mínima de confiança para o texto ser detetado de 0 a 1. Valores mais baixos vão detetar mais texto mas podem resultar em falsos positivos.", + "machine_learning_ocr_min_recognition_score": "Pontuação mínima de reconhecimento", + "machine_learning_ocr_min_score_recognition_description": "Pontuação mínima de confiança para o texto ser reconhecido de 0 a 1. Valores mais baixos vão reconhecer mais texto mas podem resultar em falsos positivos.", + "machine_learning_ocr_model": "Modelo de OCR", + "machine_learning_ocr_model_description": "Modelos do servidor têm mais precisão do que modelos móveis, mas demoram mais tempo a processar e utilizam mais memória.", "machine_learning_settings": "Definições de aprendizagem de máquina (Machine Learning)", "machine_learning_settings_description": "Gerir funcionalidades e definições de aprendizagem de máquina", "machine_learning_smart_search": "Pesquisa Inteligente", @@ -202,6 +223,8 @@ "notification_email_ignore_certificate_errors_description": "Ignorar erros de validação de certificado TLS (não recomendado)", "notification_email_password_description": "Palavra-passe a ser usada ao autenticar no servidor de e-mail", "notification_email_port_description": "Porta do servidor de e-mail (por exemplo, 25, 465 ou 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "Usar SMTPS (SMTP por TLS)", "notification_email_sent_test_email_button": "Enviar e-mail de teste e gravar", "notification_email_setting_description": "Definições para envio de notificações por e-mail", "notification_email_test_email": "Enviar e-mail de teste", @@ -212,7 +235,7 @@ "notification_settings": "Definições de notificações", "notification_settings_description": "Gerir definições de notificações, incluindo e-mail", "oauth_auto_launch": "Arranque automático", - "oauth_auto_launch_description": "Iniciar o fluxo de login do OAuth automaticamente ao navegar até a página de inicio de sessão", + "oauth_auto_launch_description": "Iniciar o fluxo de sessão por OAuth automaticamente ao navegar até a página de inicio de sessão", "oauth_auto_register": "Registo automático", "oauth_auto_register_description": "Registar automaticamente novos utilizadores após iniciarem sessão com o OAuth", "oauth_button_text": "Texto do botão", @@ -234,6 +257,7 @@ "oauth_storage_quota_default_description": "Quota em GiB a ser usada quando nenhuma reivindicação for fornecida.", "oauth_timeout": "Tempo Limite de Requisição", "oauth_timeout_description": "Tempo limite para requisições, em milissegundos", + "ocr_job_description": "Utilizar aprendizagem de máquina para reconhecer texto em imagens", "password_enable_description": "Iniciar sessão com e-mail e palavra-passe", "password_settings": "Palavra-passe de acesso", "password_settings_description": "Gerir definições de inicio de sessão e palavra-passe", @@ -320,11 +344,11 @@ "transcoding_hardware_acceleration": "Aceleração de hardware", "transcoding_hardware_acceleration_description": "Experimental; transcodificação mais rápida, mas poderá ter qualidade inferior com a mesma taxa de bits", "transcoding_hardware_decoding": "Decodificação de hardware", - "transcoding_hardware_decoding_setting_description": "Permite a aceleração ponta a ponta em vez de apenas acelerar a codificação. Pode não funcionar em todos os formatos de arquivo.", + "transcoding_hardware_decoding_setting_description": "Permite a aceleração ponta a ponta em vez de apenas acelerar a codificação. Pode não funcionar em todos os videos.", "transcoding_max_b_frames": "Máximo de quadros B", "transcoding_max_b_frames_description": "Valores mais altos melhoram a eficiência da compressão, mas tornam a codificação mais lenta. Pode não ser compatível com aceleração de hardware em dispositivos mais antigos. 0 desativa os quadros B, enquanto -1 define esse valor automaticamente.", "transcoding_max_bitrate": "Taxa de bits máxima", - "transcoding_max_bitrate_description": "Definir uma taxa de bits máxima pode tornar os tamanhos dos ficheiros mais previsíveis com um custo menor de qualidade. Em 720p, os valores típicos são 2600 kbit/s para VP9 ou HEVC, ou 4500 kbit/s para H.264. Desativado se definido como 0.", + "transcoding_max_bitrate_description": "Definir uma taxa de bits máxima pode fazer os tamanhos dos ficheiros mais previsíveis com um custo menor de qualidade. Em 720p, os valores típicos são 2600 kbit/s para VP9 ou HEVC, ou 4500 kbit/s para H.264. Quando uma unidade não for especificada, k (de kbit/s) será utilizado, ou seja, 5000, 5000k e 5M (de Mbit/s) são equivalentes.", "transcoding_max_keyframe_interval": "Intervalo máximo de quadro-chave", "transcoding_max_keyframe_interval_description": "Define a distância máxima do quadro entre os quadros-chave. Valores mais baixos pioram a eficiência da compressão, mas melhoram os tempos de procura e podem melhorar a qualidade em cenas com movimento rápido. 0 define esse valor automaticamente.", "transcoding_optimal_description": "Vídeos com resolução superior à desejada ou num formato não aceite", @@ -342,7 +366,7 @@ "transcoding_target_resolution": "Resolução desejada", "transcoding_target_resolution_description": "Resoluções mais altas podem preservar mais detalhes, mas demoram mais para codificar, têm tamanhos de ficheiro maiores e podem reduzir a capacidade de resposta da aplicação.", "transcoding_temporal_aq": "QA temporal", - "transcoding_temporal_aq_description": "Aplica-se apenas ao NVENC. Aumenta a qualidade de cenas com alto detalhe e pouco movimento. Pode não ser compatível com dispositivos mais antigos.", + "transcoding_temporal_aq_description": "Aplica-se apenas ao NVENC. A Quantização com adaptação temporal aumenta a qualidade de cenas com alto detalhe e pouco movimento. Pode não ser compatível com dispositivos mais antigos.", "transcoding_threads": "Threads", "transcoding_threads_description": "Valores mais altos levam a uma codificação mais rápida, mas deixam menos espaço para o servidor processar outras tarefas enquanto estiver ativo. Este valor não deve ser superior ao número de núcleos do CPU. Maximiza a utilização se definido como 0.", "transcoding_tone_mapping": "Mapeamento de tons", @@ -360,7 +384,7 @@ "trash_settings_description": "Gerir definições da reciclagem", "unlink_all_oauth_accounts": "Desvincular todas as contas OAuth", "unlink_all_oauth_accounts_description": "Lembre-se de desvincular todas as contas OAuth antes de migrar para um novo provedor.", - "unlink_all_oauth_accounts_prompt": "Tem a certeza de que deseja desvincular todas as contas OAuth? Isso redefinirá o ID OAuth de cada utilizador e não poderá ser desfeito.", + "unlink_all_oauth_accounts_prompt": "Tem a certeza de que deseja desvincular todas as contas OAuth? Isto irá redefinir o ID OAuth de cada utilizador e não poderá ser anulado.", "user_cleanup_job": "Limpeza de utilizadores", "user_delete_delay": "A conta e os ficheiros de {user} serão agendados para eliminação permanente dentro de {delay, plural, one {# dia} other {# dias}}.", "user_delete_delay_settings": "Atraso de eliminação", @@ -393,14 +417,14 @@ "advanced_settings_prefer_remote_subtitle": "Alguns dispositivos são extremamente lentos a carregar miniaturas da memória interna. Ative esta opção para preferir imagens do servidor.", "advanced_settings_prefer_remote_title": "Preferir imagens do servidor", "advanced_settings_proxy_headers_subtitle": "Defina os cabeçalhos do proxy que o Immich deve enviar em todas comunicações com a rede", - "advanced_settings_proxy_headers_title": "Cabeçalhos do Proxy", - "advanced_settings_readonly_mode_subtitle": "Activa o modo somente leitura, onde as fotos podem ser visualizadas. Recursos como selecionar várias imagens, partilhar, transmitir e excluir ficam deactivados. Activar/Desactivar o modo somente leitura via avatar do utilizador na janela principal", - "advanced_settings_readonly_mode_title": "Modo somente leitura", + "advanced_settings_proxy_headers_title": "Cabeçalhos do proxy personalizados [EXPERIMENTAL]", + "advanced_settings_readonly_mode_subtitle": "Ativa o modo só de leitura, onde as fotos apenas podem ser visualizadas. Funções como selecionar várias imagens, partilhar, transmitir e eliminar ficam deactivadas. Pode ativar ou desativar o modo só de leitura através da imagem de perfil do utilizador na janela principal", + "advanced_settings_readonly_mode_title": "Modo só de leitura", "advanced_settings_self_signed_ssl_subtitle": "Não validar o certificado SSL com o endereço do servidor. Isto é necessário para certificados auto-assinados.", - "advanced_settings_self_signed_ssl_title": "Permitir certificados SSL auto-assinados", + "advanced_settings_self_signed_ssl_title": "Permitir certificados SSL autoassinados", "advanced_settings_sync_remote_deletions_subtitle": "Automaticamente eliminar ou restaurar um ficheiro neste dispositivo quando essa mesma ação for efetuada na web", "advanced_settings_sync_remote_deletions_title": "Sincronizar ficheiros eliminados remotamente [EXPERIMENTAL]", - "advanced_settings_tile_subtitle": "Configurações avançadas do usuário", + "advanced_settings_tile_subtitle": "Definições avançadas do utilizador", "advanced_settings_troubleshooting_subtitle": "Ativar funcionalidades adicionais para a resolução de problemas", "advanced_settings_troubleshooting_title": "Resolução de problemas", "age_months": "Idade {months, plural, one {# mês} other {# meses}}", @@ -423,6 +447,7 @@ "album_remove_user_confirmation": "Tem a certeza de que quer remover {user}?", "album_search_not_found": "Nenhum álbum encontrado segundo a pesquisa", "album_share_no_users": "Parece que tem este álbum partilhado com todos os utilizadores ou que não existem utilizadores com quem o partilhar.", + "album_summary": "Resumo do álbum", "album_updated": "Álbum atualizado", "album_updated_setting_description": "Receber uma notificação por e-mail quando um álbum partilhado tiver novos ficheiros", "album_user_left": "Saíu do {album}", @@ -430,11 +455,11 @@ "album_viewer_appbar_delete_confirm": "Tem certeza que deseja excluir este álbum da sua conta?", "album_viewer_appbar_share_err_delete": "Ocorreu um erro ao eliminar álbum", "album_viewer_appbar_share_err_leave": "Ocorreu um erro ao sair do álbum", - "album_viewer_appbar_share_err_remove": "Houveram problemas ao remover arquivos do álbum", + "album_viewer_appbar_share_err_remove": "Ocorreu um erro ao remover ficheiros do álbum", "album_viewer_appbar_share_err_title": "Ocorreu um erro ao alterar o título do álbum", "album_viewer_appbar_share_leave": "Deixar álbum", "album_viewer_appbar_share_to": "Compartilhar com", - "album_viewer_page_share_add_users": "Adicionar usuários", + "album_viewer_page_share_add_users": "Adicionar utilzadores", "album_with_link_access": "Permite o acesso a fotos e pessoas deste álbum por qualquer pessoa com o link.", "albums": "Álbuns", "albums_count": "{count, plural, one {{count, number} Álbum} other {{count, number} Álbuns}}", @@ -456,16 +481,20 @@ "api_key_description": "Este valor será apresentado apenas uma única vez. Por favor, certifique-se que o copiou antes de fechar a janela.", "api_key_empty": "O nome da chave a API não pode estar vazio", "api_keys": "Chaves de API", + "app_architecture_variant": "Variante (Arquitetura)", "app_bar_signout_dialog_content": "Tem certeza que deseja sair?", "app_bar_signout_dialog_ok": "Sim", "app_bar_signout_dialog_title": "Sair", + "app_download_links": "Ligações de Descarga da App", "app_settings": "Definições da Aplicação", + "app_stores": "Lojas de aplicações", + "app_update_available": "Atualização disponível", "appears_in": "Aparece em", "apply_count": "Aplicar ({count, number})", "archive": "Arquivo", "archive_action_prompt": "{count} adicionados ao Arquivo", "archive_or_unarchive_photo": "Arquivar ou desarquivar foto", - "archive_page_no_archived_assets": "Nenhum arquivo encontrado", + "archive_page_no_archived_assets": "Nenhum ficheiro arquivado encontrado", "archive_page_title": "Arquivo ({count})", "archive_size": "Tamanho do arquivo", "archive_size_description": "Configure o tamanho do arquivo para transferências (em GiB)", @@ -473,8 +502,8 @@ "archived_count": "{count, plural, one {#Arquivado # item} other {Arquivados # itens}}", "are_these_the_same_person": "Estas pessoas são a mesma pessoa?", "are_you_sure_to_do_this": "Tem a certeza de que quer fazer isto?", - "asset_action_delete_err_read_only": "Não é possível excluir arquivo só leitura, ignorando", - "asset_action_share_err_offline": "Não foi possível obter os arquivos offline, ignorando", + "asset_action_delete_err_read_only": "Não é possível eliminar ficheiro só de leitura, a ignorar", + "asset_action_share_err_offline": "Não foi possível obter os ficheiros offline, a ignorar", "asset_added_to_album": "Adicionado ao álbum", "asset_adding_to_album": "A adicionar ao álbum…", "asset_description_updated": "A descrição do ficheiro foi atualizada", @@ -484,16 +513,18 @@ "asset_list_group_by_sub_title": "Agrupar por", "asset_list_layout_settings_dynamic_layout_title": "Layout dinâmico", "asset_list_layout_settings_group_automatically": "Automático", - "asset_list_layout_settings_group_by": "Agrupar arquivos por", + "asset_list_layout_settings_group_by": "Agrupar ficheiros por", "asset_list_layout_settings_group_by_month_day": "Mês + dia", "asset_list_layout_sub_title": "Disposição", "asset_list_settings_subtitle": "Configurações de disposição da grade de fotos", "asset_list_settings_title": "Grade de fotos", "asset_offline": "Ficheiro Indisponível", "asset_offline_description": "Este ficheiro externo deixou de estar disponível no disco. Contacte o seu administrador do Immich para obter ajuda.", - "asset_restored_successfully": "Arquivo restaurado com sucesso", + "asset_restored_successfully": "FIcheiro restaurado com sucesso", "asset_skipped": "Ignorado", "asset_skipped_in_trash": "Na reciclagem", + "asset_trashed": "Ficheiro apagado", + "asset_troubleshoot": "Resolução de problemas com conteúdos", "asset_uploaded": "Enviado", "asset_uploading": "A enviar…", "asset_viewer_settings_subtitle": "Gerenciar as configurações do visualizador da galeria", @@ -501,9 +532,9 @@ "assets": "Ficheiros", "assets_added_count": "{count, plural, one {# ficheiro adicionado} other {# ficheiros adicionados}}", "assets_added_to_album_count": "{count, plural, one {# ficheiro adicionado} other {# ficheiros adicionados}} ao álbum", - "assets_added_to_albums_count": "Adicionado {assetTotal, plural, one {# asset} other {# assets}} a {albumTotal, plural, one {# album} other {# albums}}", + "assets_added_to_albums_count": "{assetTotal, plural, one {Foi adicionado # ficheiro} other {Foram adiciondos # ficheiros}} a {albumTotal, plural, one {# álbum} other {# albuns}}", "assets_cannot_be_added_to_album_count": "Não foi possível adicionar {count, plural, one {ficheiro} other {ficheiros}} ao álbum", - "assets_cannot_be_added_to_albums": "{count, plural, one {Asset} other {Assets}} não pode ser adicionado a nenhum dos álbuns", + "assets_cannot_be_added_to_albums": "{count, plural, one {Ficheiro não pode ser adicionado} other {Ficheiros não podem ser adiciondos}} a nenhum dos álbuns", "assets_count": "{count, plural, one {# ficheiro} other {# ficheiros}}", "assets_deleted_permanently": "{count} ficheiro(s) eliminado(s) permanentemente", "assets_deleted_permanently_from_server": "{count} ficheiro(s) eliminado(s) permanentemente do servidor Immich", @@ -520,29 +551,33 @@ "assets_trashed_count": "{count, plural, one {# ficheiro enviado} other {# ficheiros enviados}} para a reciclagem", "assets_trashed_from_server": "{count} ficheiro(s) do servidor Immich foi/foram enviados para a reciclagem", "assets_were_part_of_album_count": "{count, plural, one {O ficheiro já fazia} other {Os ficheiros já faziam}} parte do álbum", - "assets_were_part_of_albums_count": "{count, plural, one {Asset was} other {Assets were}} já faz parte dos álbuns", + "assets_were_part_of_albums_count": "{count, plural, one {Ficheiro já fazia} other {Ficheiros já faziam}} parte dos álbuns", "authorized_devices": "Dispositivos Autorizados", "automatic_endpoint_switching_subtitle": "Conecte-se localmente quando estiver em uma rede uma Wi-Fi específica e use conexões alternativas em outras redes", "automatic_endpoint_switching_title": "Troca automática de URL", "autoplay_slideshow": "Apresentação automática de diapositivos", "back": "Voltar", "back_close_deselect": "Voltar, fechar ou desmarcar", + "background_backup_running_error": "Com a cópia de segurança de fundo em execução, não é possível inicar uma manual", "background_location_permission": "Permissão de localização em segundo plano", - "background_location_permission_content": "Para que seja possível trocar a URL quando estiver executando em segundo plano, o Immich deve *sempre* ter a permissão de localização precisa para que o aplicativo consiga ler o nome da rede Wi-Fi", + "background_location_permission_content": "Para que seja possível trocar de redes enquanto executa em segundo plano, o Immich deve *sempre* ter a permissão de localização precisa para que a aplicação consiga ler o nome da rede Wi-Fi", + "background_options": "Opções de fundo", "backup": "Cópia de segurança", "backup_album_selection_page_albums_device": "Álbuns no dispositivo ({count})", "backup_album_selection_page_albums_tap": "Toque para incluir, duplo toque para excluir", - "backup_album_selection_page_assets_scatter": "Os arquivos podem estar espalhados em vários álbuns. Assim, os álbuns podem ser incluídos ou excluídos durante o processo de backup.", + "backup_album_selection_page_assets_scatter": "Os ficheros podem estar espalhados por vários álbuns. Desta forma, os álbuns podem ser incluídos ou excluídos durante o processo de cópia de segurança.", "backup_album_selection_page_select_albums": "Selecione Álbuns", "backup_album_selection_page_selection_info": "Informações da Seleção", - "backup_album_selection_page_total_assets": "Total de arquivos únicos", + "backup_album_selection_page_total_assets": "Total de ficheiros únicos", + "backup_albums_sync": "Cópia de segurança de sincronização de álbuns", "backup_all": "Tudo", "backup_background_service_backup_failed_message": "Ocorreu um erro ao efetuar cópia de segurança dos ficheiros. A tentar de novo…", + "backup_background_service_complete_notification": "Cópia de conteúdos concluída", "backup_background_service_connection_failed_message": "Ocorreu um erro na ligação ao servidor. A tentar de novo…", "backup_background_service_current_upload_notification": "A enviar {filename}", - "backup_background_service_default_notification": "Verificando novos arquivos…", + "backup_background_service_default_notification": "A verificar se há novos ficheiros…", "backup_background_service_error_title": "Erro de backup", - "backup_background_service_in_progress_notification": "Fazendo backup dos arquivos…", + "backup_background_service_in_progress_notification": "A fazer cópia de segurança dos seus ficheiros…", "backup_background_service_upload_failure_notification": "Ocorreu um erro ao enviar {filename}", "backup_controller_page_albums": "Backup Álbuns", "backup_controller_page_background_app_refresh_disabled_content": "Para utilizar a cópia de segurança em segundo plano, ative a atualização da aplicação em segundo plano em Definições > Geral > Atualização da aplicação em segundo plano.", @@ -555,7 +590,7 @@ "backup_controller_page_background_charging": "Apenas enquanto carrega a bateria", "backup_controller_page_background_configure_error": "Ocorreu um erro ao configurar o serviço em segundo plano", "backup_controller_page_background_delay": "Atraso da cópia de segurança de novos ficheiros: {duration}", - "backup_controller_page_background_description": "Ative o serviço em segundo plano para fazer backup automático de novos arquivos sem precisar abrir o aplicativo", + "backup_controller_page_background_description": "Ative o serviço em segundo plano para fazer cópia de segurança automática de novos ficheiros sem precisar de abrir a aplicação", "backup_controller_page_background_is_off": "O backup automático em segundo plano está desativado", "backup_controller_page_background_is_on": "O backup automático em segundo plano está ativado", "backup_controller_page_background_turn_off": "Desativar o serviço em segundo plano", @@ -565,7 +600,7 @@ "backup_controller_page_backup_selected": "Selecionado: ", "backup_controller_page_backup_sub": "Fotos e vídeos salvos em backup", "backup_controller_page_created": "Criado em: {date}", - "backup_controller_page_desc_backup": "Ative o backup para enviar automáticamente novos arquivos para o servidor.", + "backup_controller_page_desc_backup": "Ative a cópia de segurança em primeiro plano para enviar novos ficheiros automaticamente ao abrir a aplicação.", "backup_controller_page_excluded": "Eliminado: ", "backup_controller_page_failed": "Falhou ({count})", "backup_controller_page_filename": "Nome do ficheiro: {filename} [{size}]", @@ -575,7 +610,7 @@ "backup_controller_page_remainder": "Restante", "backup_controller_page_remainder_sub": "Fotos e vídeos selecionados restantes para fazer backup", "backup_controller_page_server_storage": "Armazenamento no servidor", - "backup_controller_page_start_backup": "Iniciar Backup", + "backup_controller_page_start_backup": "Iniciar Cópia de Segurança", "backup_controller_page_status_off": "Backup automático desativado", "backup_controller_page_status_on": "Backup automático ativado", "backup_controller_page_storage_format": "{used} de {total} utilizado", @@ -583,15 +618,16 @@ "backup_controller_page_total_sub": "Todas as fotos e vídeos dos álbuns selecionados", "backup_controller_page_turn_off": "Desativar backup", "backup_controller_page_turn_on": "Ativar backup", - "backup_controller_page_uploading_file_info": "Enviando arquivo", + "backup_controller_page_uploading_file_info": "A enviar informações do ficheiro", "backup_err_only_album": "Não é possível remover apenas o álbum", - "backup_info_card_assets": "arquivos", + "backup_error_sync_failed": "A sincronização falhou. Não é possível fazer a cópia de segurança.", + "backup_info_card_assets": "ficheiros", "backup_manual_cancelled": "Cancelado", "backup_manual_in_progress": "Envio já está em progresso. Tente novamente mais tarde", "backup_manual_success": "Sucesso", "backup_manual_title": "Estado do envio", "backup_options": "Definições de cópia de segurança", - "backup_options_page_title": "Opções de backup", + "backup_options_page_title": "Opções de cópia de segurança", "backup_setting_subtitle": "Gerenciar as configurações de envio em primeiro e segundo plano", "backup_settings_subtitle": "Gerir definições de carregamento", "backward": "Para trás", @@ -610,7 +646,7 @@ "bulk_trash_duplicates_confirmation": "Tem a certeza de que deseja mover para a reciclagem {count, plural, one {# ficheiro duplicado} other {# ficheiros duplicados}}? Isto manterá o maior ficheiro de cada grupo e irá mover para a reciclagem todos os outros duplicados.", "buy": "Comprar Immich", "cache_settings_clear_cache_button": "Limpar cache", - "cache_settings_clear_cache_button_title": "Limpa o cache do aplicativo. Isso afetará significativamente o desempenho do aplicativo até que o cache seja reconstruído.", + "cache_settings_clear_cache_button_title": "Limpa a cache da aplicação. Isto irá afetar significativamente o desempenho da mesma até que a cache seja reconstruída.", "cache_settings_duplicated_assets_clear_button": "LIMPAR", "cache_settings_duplicated_assets_subtitle": "Fotos e vídeos que estão na lista de bloqueio da aplicação", "cache_settings_duplicated_assets_title": "Ficheiros duplicados ({count})", @@ -619,7 +655,7 @@ "cache_settings_statistics_shared": "Miniaturas de álbuns compartilhados", "cache_settings_statistics_thumbnail": "Miniaturas", "cache_settings_statistics_title": "Uso de cache", - "cache_settings_subtitle": "Controle o comportamento de cache do aplicativo Immich", + "cache_settings_subtitle": "Controlar o comportamento da cache da aplicação Immich", "cache_settings_tile_subtitle": "Controlar o comportamento do armazenamento local", "cache_settings_tile_title": "Armazenamento local", "cache_settings_title": "Configurações de cache", @@ -633,7 +669,7 @@ "cannot_merge_people": "Não foi possível unir pessoas", "cannot_undo_this_action": "Não é possível anular esta ação!", "cannot_update_the_description": "Não foi possível atualizar a descrição", - "cast": "Reproduzir em", + "cast": "Transmitir", "cast_description": "Configurar destinos de reprodução remota disponíveis", "change_date": "Alterar data", "change_description": "Alterar descrição", @@ -646,15 +682,19 @@ "change_password_description": "Esta é a primeira vez que está a entrar no sistema ou um pedido foi feito para alterar a sua palavra-passe. Insira a nova palavra-passe abaixo.", "change_password_form_confirm_password": "Confirmar palavra-passe", "change_password_form_description": "Olá, {name}\n\nEsta é a primeira vez que está a aceder ao sistema, ou então foi feito um pedido para alterar a palavra-passe. Por favor insira uma nova palavra-passe abaixo.", + "change_password_form_log_out": "Terminar sessão em todos os outros dispositivos", + "change_password_form_log_out_description": "Recomenda-se que termine a sessão em todos os outros dispositivos", "change_password_form_new_password": "Nova palavra-passe", "change_password_form_password_mismatch": "As palavras-passe não condizem", "change_password_form_reenter_new_password": "Confirme a nova palavra-passe", "change_pin_code": "Alterar código PIN", "change_your_password": "Alterar a sua palavra-passe", "changed_visibility_successfully": "Visibilidade alterada com sucesso", + "charging": "A carregar", + "charging_requirement_mobile_backup": "Cópia de segurança de fundo necessita que o dispositivo esteja a carregar", "check_corrupt_asset_backup": "Verificar por backups corrompidos", "check_corrupt_asset_backup_button": "Verificar", - "check_corrupt_asset_backup_description": "Execute esta verificação somente em uma rede Wi-Fi e quando o backup de todos os arquivos já estiver concluído. O processo demora alguns minutos.", + "check_corrupt_asset_backup_description": "Execute esta verificação apenas numa rede Wi-Fi e quando a cópia de segurança de todos os ficheiros já estiver concluída. O processo pode demorar alguns minutos.", "check_logs": "Verificar registos", "choose_matching_people_to_merge": "Escolha pessoas correspondentes para unir", "city": "Cidade/Localidade", @@ -670,8 +710,8 @@ "client_cert_import_success_msg": "Certificado do cliente foi importado", "client_cert_invalid_msg": "Certificado inválido ou palavra-passe incorreta", "client_cert_remove_msg": "Certificado do cliente foi removido", - "client_cert_subtitle": "Somente há suporte ao formato PKCS12 (.p12, .pfx). Importar/Remover certificados está disponivel somente durante o login", - "client_cert_title": "Certificado de Cliente SSL", + "client_cert_subtitle": "Apenas há suporte ao formato PKCS12 (.p12, .pfx). Importar/Remover certificados está disponível apenas antes do início de sessão", + "client_cert_title": "Certificado de Cliente SSL [EXPERIMENTAL]", "clockwise": "Sentido horário", "close": "Fechar", "collapse": "Colapsar", @@ -683,7 +723,6 @@ "comments_and_likes": "Comentários e gostos", "comments_are_disabled": "Comentários estão desativados", "common_create_new_album": "Criar novo álbum", - "common_server_error": "Verifique a sua conexão de rede, certifique-se de que o servidor está acessível e de que as versões da aplicação/servidor são compatíveis.", "completed": "Sucesso", "confirm": "Confirmar", "confirm_admin_password": "Confirmar palavra-passe de administrador", @@ -722,6 +761,7 @@ "create": "Criar", "create_album": "Criar álbum", "create_album_page_untitled": "Sem título", + "create_api_key": "Criar chave de API", "create_library": "Criar biblioteca", "create_link": "Criar link", "create_link_to_share": "Criar link para partilhar", @@ -730,7 +770,7 @@ "create_new_person": "Criar nova pessoa", "create_new_person_hint": "Associe os ficheiros a uma nova pessoa", "create_new_user": "Criar novo utilizador", - "create_shared_album_page_share_add_assets": "ADICIONAR ARQUIVOS", + "create_shared_album_page_share_add_assets": "ADICIONAR FICHEIROS", "create_shared_album_page_share_select_photos": "Selecionar Fotos", "create_shared_link": "Criar link partilhado", "create_tag": "Criar etiqueta", @@ -738,6 +778,7 @@ "create_user": "Criar utilizador", "created": "Criado", "created_at": "Criado a", + "creating_linked_albums": "A criar albuns ligados...", "crop": "Cortar", "curated_object_page_title": "Objetos", "current_device": "Dispositivo atual", @@ -750,6 +791,7 @@ "daily_title_text_date_year": "E, dd MMM, yyyy", "dark": "Escuro", "dark_theme": "Alternar tema escuro", + "date": "Data", "date_after": "Data após", "date_and_time": "Data e Hora", "date_before": "Data antes", @@ -770,10 +812,10 @@ "delete_action_prompt": "{count} eliminados", "delete_album": "Apagar álbum", "delete_api_key_prompt": "Tem a certeza de que deseja remover esta chave de API?", - "delete_dialog_alert": "Esses arquivos serão permanentemente apagados do Immich e de seu dispositivo", - "delete_dialog_alert_local": "Estes arquivos serão permanentemente excluídos do seu dispositivo, mas continuarão disponíveis no servidor Immich", - "delete_dialog_alert_local_non_backed_up": "Não há backup de alguns dos arquivos no servidor e eles serão excluídos permanentemente do seu dispositivo", - "delete_dialog_alert_remote": "Estes arquivos serão permanentemente excluídos do servidor Immich", + "delete_dialog_alert": "Estes ficheiros serão eliminados permanentemente do Immich e do seu dispositivo", + "delete_dialog_alert_local": "Estes ficheiros serão eliminados permanentemente do seu dispositivo, mas continuarão disponíveis no servidor Immich", + "delete_dialog_alert_local_non_backed_up": "Alguns dos ficheiros não têm cópia de segurança no Immich e serão eliminados permanentemente do seu dispositivo", + "delete_dialog_alert_remote": "Estes ficheiros serão eliminados permanentemente do servidor Immich", "delete_dialog_ok_force": "Confirmo que quero excluir", "delete_dialog_title": "Excluir Permanentemente", "delete_duplicates_confirmation": "Tem a certeza de que deseja eliminar permanentemente estes itens duplicados?", @@ -782,7 +824,7 @@ "delete_library": "Eliminar Biblioteca", "delete_link": "Eliminar link", "delete_local_action_prompt": "{count} eliminados localmente", - "delete_local_dialog_ok_backed_up_only": "Excluir apenas arquivos com backup", + "delete_local_dialog_ok_backed_up_only": "Eliminar apenas ficheiros com cópia de segurança", "delete_local_dialog_ok_force": "Excluir mesmo assim", "delete_others": "Excluir outros", "delete_permanently": "Eliminar permanentemente", @@ -830,7 +872,7 @@ "download_settings_description": "Gerir definições relacionadas com a transferência de ficheiros", "download_started": "Iniciando", "download_sucess": "Baixado com sucesso", - "download_sucess_android": "O arquivo foi baixado na pasta DCIM/Immich", + "download_sucess_android": "O ficheiro foi descarregado para a pasta DCIM/Immich", "download_waiting_to_retry": "Tentando novamente", "downloading": "A transferir", "downloading_asset_filename": "A transferir o ficheiro {filename}", @@ -864,7 +906,6 @@ "edit_tag": "Editar etiqueta", "edit_title": "Editar Título", "edit_user": "Editar utilizador", - "edited": "Editado", "editor": "Editar", "editor_close_without_save_prompt": "As alterações não serão guardadas", "editor_close_without_save_title": "Fechar editor?", @@ -887,7 +928,9 @@ "error": "Erro", "error_change_sort_album": "Ocorreu um erro ao mudar a ordem de exibição", "error_delete_face": "Falha ao remover rosto do ficheiro", + "error_getting_places": "Erro ao obter locais", "error_loading_image": "Erro ao carregar a imagem", + "error_loading_partners": "Erro ao carregar parceiros: {error}", "error_saving_image": "Erro: {error}", "error_tag_face_bounding_box": "Erro ao marcar o rosto - não foi possível localizar o rosto", "error_title": "Erro - Algo correu mal", @@ -920,7 +963,7 @@ "failed_to_load_notifications": "Ocorreu um erro ao carregar notificações", "failed_to_load_people": "Ocorreu um erro ao carregar pessoas", "failed_to_remove_product_key": "Ocorreu um erro ao remover chave de produto", - "failed_to_reset_pin_code": "Falha ao repor o código PIN", + "failed_to_reset_pin_code": "Ocorreu um erro ao repor o código PIN", "failed_to_stack_assets": "Ocorreu um erro ao empilhar os ficheiros", "failed_to_unstack_assets": "Ocorreu um erro ao desempilhar ficheiros", "failed_to_update_notification_status": "Ocorreu um erro ao atualizar o estado das notificações", @@ -929,7 +972,7 @@ "paths_validation_failed": "Ocorreu um erro na validação de {paths, plural, one {# caminho} other {# caminhos}}", "profile_picture_transparent_pixels": "Imagem de perfil não pode ter pixeis transparentes. Por favor amplie e/ou mova a imagem.", "quota_higher_than_disk_size": "Definiu uma quota maior do que o tamanho do disco", - "something_went_wrong": "Algo deu errado", + "something_went_wrong": "Algo correu mal", "unable_to_add_album_users": "Não foi possível adicionar utilizadores ao álbum", "unable_to_add_assets_to_shared_link": "Não foi possível adicionar os ficheiros ao link partilhado", "unable_to_add_comment": "Não foi possível adicionar o comentário", @@ -1018,6 +1061,7 @@ "exif_bottom_sheet_description_error": "Ocorreu um erro ao alterar a descrição", "exif_bottom_sheet_details": "DETALHES", "exif_bottom_sheet_location": "LOCALIZAÇÃO", + "exif_bottom_sheet_no_description": "Sem descrição", "exif_bottom_sheet_people": "PESSOAS", "exif_bottom_sheet_person_add_person": "Adicionar nome", "exit_slideshow": "Sair da apresentação", @@ -1052,9 +1096,11 @@ "favorites_page_no_favorites": "Nenhum favorito encontrado", "feature_photo_updated": "Foto principal atualizada", "features": "Funcionalidades", + "features_in_development": "Funcionalidades em Desenvolvimento", "features_setting_description": "Configurar as funcionalidades da aplicação", "file_name": "Nome do ficheiro", "file_name_or_extension": "Nome do ficheiro ou extensão", + "file_size": "Tamanho do ficheiro", "filename": "Nome do ficheiro", "filetype": "Tipo de ficheiro", "filter": "Filtro", @@ -1067,12 +1113,12 @@ "folder_not_found": "Pasta não encontrada", "folders": "Pastas", "folders_feature_description": "Navegar na vista de pastas por fotos e vídeos no sistema de ficheiros", - "forgot_pin_code_question": "Esqueceu o seu PIN?", + "forgot_pin_code_question": "Esqueceu-se do seu PIN?", "forward": "Para a frente", "gcast_enabled": "Google Cast", "gcast_enabled_description": "Esta funcionalidade requer o carregamento de recursos externos da Google para poder funcionar.", "general": "Geral", - "geolocation_instruction_location": "Clique num ativo com coordenadas GPS para usar a sua localização ou seleccione um local diretamente do mapa", + "geolocation_instruction_location": "Clique num ficheiro com coordenadas GPS para usar a sua localização ou selecione um local diretamente do mapa", "get_help": "Obter Ajuda", "get_wifiname_error": "Não foi possível obter o nome do Wi-Fi. Verifique se concedeu as permissões necessárias e se está conectado a uma rede Wi-Fi", "getting_started": "Primeiros Passos", @@ -1088,17 +1134,16 @@ "group_owner": "Agrupar por dono", "group_places_by": "Agrupar lugares por...", "group_year": "Agrupar por ano", - "haptic_feedback_switch": "Habilitar vibração", + "haptic_feedback_switch": "Ativar vibração", "haptic_feedback_title": "Vibração", "has_quota": "Tem quota", "hash_asset": "Criptografar ficheiro", "hashed_assets": "Ficheiros criptografados", "hashing": "A criptografar", - "header_settings_add_header_tip": "Adicionar cabeçalho", + "header_settings_add_header_tip": "Adicionar Cabeçalho", "header_settings_field_validator_msg": "Campo deve ser preenchido", "header_settings_header_name_input": "Nome do cabeçalho", "header_settings_header_value_input": "Valor do cabeçalho", - "headers_settings_tile_subtitle": "Defina os cabeçalhos do proxy que o aplicativo deve enviar em todas comunicações com a rede", "headers_settings_tile_title": "Cabeçalhos do Proxy customizados", "hi_user": "Olá {name} ({email})", "hide_all_people": "Ocultar todas as pessoas", @@ -1109,20 +1154,20 @@ "hide_unnamed_people": "Ocultar pessoas sem nome", "home_page_add_to_album_conflicts": "Foram adicionados {added} ficheiros ao álbum {album}. {failed} ficheiros já estão no álbum.", "home_page_add_to_album_err_local": "Ainda não é possível adicionar recursos locais aos álbuns, ignorando", - "home_page_add_to_album_success": "Adicionado {added} arquivos ao álbum {album}.", - "home_page_album_err_partner": "Ainda não é possível adicionar arquivos do parceiro a um álbum, ignorando", + "home_page_add_to_album_success": "{added} ficheiros foram adicionados ao álbum {album}.", + "home_page_album_err_partner": "Ainda não é possível adicionar ficheiros do parceiro a um álbum, a ignorar", "home_page_archive_err_local": "Ainda não é possível arquivar recursos locais, ignorando", "home_page_archive_err_partner": "Não é possível arquivar Fotos e Videos do parceiro, ignorando", "home_page_building_timeline": "Construindo a linha do tempo", - "home_page_delete_err_partner": "Não é possível excluir arquivos do parceiro, ignorando", - "home_page_delete_remote_err_local": "Foram selecionados arquivos locais para excluir remotamente, ignorando", + "home_page_delete_err_partner": "Não é possível eliminar ficheiros do parceiro, a ignorar", + "home_page_delete_remote_err_local": "Foram selecionados ficheiros locais para excluir remotamente, a ignorar", "home_page_favorite_err_local": "Ainda não é possível adicionar recursos locais favoritos, ignorando", - "home_page_favorite_err_partner": "Ainda não é possível marcar arquivos do parceiro como favoritos, ignorando", + "home_page_favorite_err_partner": "Ainda não é possível marcar ficheiros do parceiro como favoritos, a ignorar", "home_page_first_time_notice": "Se é a primeira vez que utiliza a aplicação, certifique-se de que marca pelo menos um álbum do dispositivo para cópia de segurança, para a linha do tempo poder ser preenchida com fotos e vídeos", "home_page_locked_error_local": "Não foi possível mover ficheiros locais para a pasta trancada, a continuar", "home_page_locked_error_partner": "Não foi possível mover ficheiros do parceiro para a pasta trancada, a continuar", - "home_page_share_err_local": "Não é possível compartilhar arquivos locais com um link, ignorando", - "home_page_upload_err_limit": "Só é possível enviar 30 arquivos por vez, ignorando", + "home_page_share_err_local": "Não é possível partilhar ficheiros locais com um link, a ignorar", + "home_page_upload_err_limit": "Só é possível enviar um máximo de 30 ficheiros de cada vez, a ignorar", "host": "Servidor", "hour": "Hora", "hours": "Horas", @@ -1142,7 +1187,7 @@ "image_alt_text_date_place_3_people": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} em {city}, {country} com {person1}, {person2}, e {person3} em {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} em {city}, {country} com {person1}, {person2}, e outras {additionalCount, number} pessoas em {date}", "image_saved_successfully": "Imagem salva", - "image_viewer_page_state_provider_download_started": "Baixando arquivo", + "image_viewer_page_state_provider_download_started": "A descarregar ficheiro", "image_viewer_page_state_provider_download_success": "Baixado com sucesso", "image_viewer_page_state_provider_share_error": "Erro ao compartilhar", "immich_logo": "Logotipo do Immich", @@ -1199,13 +1244,13 @@ "library_options": "Opções da biblioteca", "library_page_device_albums": "Álbuns no dispositivo", "library_page_new_album": "Novo álbum", - "library_page_sort_asset_count": "Quantidade de arquivos", + "library_page_sort_asset_count": "Quantidade de ficheiros", "library_page_sort_created": "Data de criação", "library_page_sort_last_modified": "Última modificação", "library_page_sort_title": "Título do álbum", "licenses": "Licenças", "light": "Claro", - "like": "Gostar", + "like": "Gosto", "like_deleted": "Gosto removido", "link_motion_video": "Relacionar video animado", "link_to_oauth": "Link do OAuth", @@ -1216,8 +1261,10 @@ "local": "Local", "local_asset_cast_failed": "Não é possível transmitir um ficheiro que não tenha sido enviado antes para o servidor", "local_assets": "Ficheiros Locais", + "local_media_summary": "Sumário de conteúdo local", "local_network": "Rede local", - "local_network_sheet_info": "O aplicativo irá se conectar ao servidor através desta URL quando estiver na rede Wi-Fi especificada", + "local_network_sheet_info": "A aplicação irá ligar-se ao servidor através desta URL quando estiver na rede Wi-Fi especificada", + "location": "Localização", "location_permission": "Permissão de localização", "location_permission_content": "Para utilizar a função de troca automática de URL, o Immich necessita da permissão de localização exata, para que seja possível ler o nome da rede Wi-Fi atual", "location_picker_choose_on_map": "Escolha no mapa", @@ -1227,13 +1274,14 @@ "location_picker_longitude_hint": "Digite a longitude", "lock": "Trancar", "locked_folder": "Pasta Trancada", + "log_detail_title": "Detalhes de registo", "log_out": "Sair", "log_out_all_devices": "Terminar a sessão de todos os dispositivos", "logged_in_as": "Utilizador atual: {user}", "logged_out_all_devices": "Sessão terminada em todos os dispositivos", "logged_out_device": "Sessão terminada no dispositivo", - "login": "Iniciar sessão", - "login_disabled": "Login desativado", + "login": "Iniciar Sessão", + "login_disabled": "Início de sessão desativado", "login_form_api_exception": "Erro de API. Verifique a URL do servidor e tente novamente.", "login_form_back_button_text": "Voltar", "login_form_email_hint": "seuemail@email.com", @@ -1241,7 +1289,7 @@ "login_form_endpoint_url": "URL do servidor", "login_form_err_http": "Por favor especifique http:// ou https://", "login_form_err_invalid_email": "Email Inválido", - "login_form_err_invalid_url": "URL inválida", + "login_form_err_invalid_url": "URL inválido", "login_form_err_leading_whitespace": "Espaço em branco no início", "login_form_err_trailing_whitespace": "Espaço em branco no fim", "login_form_failed_get_oauth_server_config": "Ocorreu um erro ao iniciar sessão com o OAuth, verifique o URL do servidor", @@ -1249,19 +1297,20 @@ "login_form_failed_login": "Ocorreu um erro ao iniciar sessão, verifique o URL do servidor, o e-mail e a palavra-passe", "login_form_handshake_exception": "Erro ao conectar com o servidor. Ative o suporte para certificados auto-assinados nas configurações se estiver utilizando um certificado auto-assinado.", "login_form_password_hint": "Palavra-passe", - "login_form_save_login": "Lembrar login", - "login_form_server_empty": "Digite a URL de servidor.", - "login_form_server_error": "Não foi possível conectar ao servidor.", - "login_has_been_disabled": "Início de sessão foi desativado.", + "login_form_save_login": "Manter sessão iniciada", + "login_form_server_empty": "Insira a URL do servidor.", + "login_form_server_error": "Não foi possível ligar ao servidor.", + "login_has_been_disabled": "O início de sessão foi desativado.", "login_password_changed_error": "Ocorreu um erro ao atualizar a sua palavra-passe", "login_password_changed_success": "Palavra-passe atualizada com sucesso", "logout_all_device_confirmation": "Tem a certeza de que deseja terminar a sessão em todos os dispositivos?", "logout_this_device_confirmation": "Tem a certeza de que deseja terminar a sessão deste dispositivo?", + "logs": "Logs", "longitude": "Longitude", "look": "Estilo", "loop_videos": "Repetir vídeos", "loop_videos_description": "Ativar para repetir os vídeos automaticamente durante a exibição.", - "main_branch_warning": "Está a utilizar uma versão de desenvolvimento, recomendamos vivamente que utilize uma versão estável!", + "main_branch_warning": "Está a usar uma versão de desenvolvimento; recomendamos vivamente que use uma versão de lançamento!", "main_menu": "Menu Principal", "make": "Marca", "manage_geolocation": "Gerir localização", @@ -1273,7 +1322,7 @@ "manage_your_devices": "Gerir os seus dispositivos com sessão iniciada", "manage_your_oauth_connection": "Gerir a sua ligação ao OAuth", "map": "Mapa", - "map_assets_in_bounds": "{count, plural, =0 {No photos in this area} one {# photo} other {# photos}}", + "map_assets_in_bounds": "{count, plural, =0 {Sem fotos nesta área} one {# foto} other {# fotos}}", "map_cannot_get_user_location": "Impossível obter a sua localização", "map_location_dialog_yes": "Sim", "map_location_picker_page_use_location": "Utilizar esta localização", @@ -1299,6 +1348,7 @@ "mark_as_read": "Marcar como lido", "marked_all_as_read": "Tudo marcado como lido", "matches": "Correspondências", + "matching_assets": "Conteúdos coincidentes", "media_type": "Tipo de média", "memories": "Memórias", "memories_all_caught_up": "Finalizamos por hoje", @@ -1319,6 +1369,8 @@ "minute": "Minuto", "minutes": "Minutos", "missing": "Em falta", + "mobile_app": "App móvel", + "mobile_app_download_onboarding_note": "Descarregue a aplicação para dispositivos móveis com as seguintes opções", "model": "Modelo", "month": "Mês", "monthly_title_text_date_format": "MMMM y", @@ -1331,24 +1383,29 @@ "moved_to_archive": "{count, plural, one {Foi movido # ficheiro} other {Foram movidos # ficheiros}} para o arquivo", "moved_to_library": "{count, plural, one {Foi movido # ficheiro} other {Foram movidos # ficheiros}} para a biblioteca", "moved_to_trash": "Enviado para a reciclagem", - "multiselect_grid_edit_date_time_err_read_only": "Não é possível editar a data de arquivo só leitura, ignorando", - "multiselect_grid_edit_gps_err_read_only": "Não é possível editar a localização de arquivo só leitura, ignorando", + "multiselect_grid_edit_date_time_err_read_only": "Não é possível editar a data de um ficheiro só de leitura, a ignorar", + "multiselect_grid_edit_gps_err_read_only": "Não é possível editar a localização de um ficheiro só de leitura, a ignorar", "mute_memories": "Silenciar Memórias", "my_albums": "Os meus álbuns", "name": "Nome", "name_or_nickname": "Nome ou alcunha", - "network_requirement_photos_upload": "Usar dados móveis para fazer backup de fotos", - "network_requirement_videos_upload": "Usar dados móveis para fazer backup de vídeos", - "network_requirements_updated": "Requisitos de rede alterados, redefinindo fila de backup", - "networking_settings": "Conexões", - "networking_subtitle": "Gerencie a conexão do servidor", + "navigate": "Navegar", + "navigate_to_time": "Navegar para Horário", + "network_requirement_photos_upload": "Usar dados móveis para fazer cópia de segurança de fotos", + "network_requirement_videos_upload": "Usar dados móveis para fazer cópia de segurança de vídeos", + "network_requirements": "Requisitos de rede", + "network_requirements_updated": "Requisitos de rede alterados, a redefinir fila de cópia de segurança", + "networking_settings": "Ligações", + "networking_subtitle": "Gerir as ligações de rede do servidor", "never": "Nunca", "new_album": "Novo Álbum", "new_api_key": "Nova Chave de API", + "new_date_range": "Nova faixa de datas", "new_password": "Nova palavra-passe", "new_person": "Nova Pessoa", "new_pin_code": "Novo código PIN", "new_pin_code_subtitle": "Esta é a primeira vez que acede à pasta trancada. Crie um código PIN para aceder a esta página de forma segura", + "new_timeline": "Nova Linha do Tempo", "new_user_created": "Novo utilizador criado", "new_version_available": "NOVA VERSÃO DISPONÍVEL", "newest_first": "Mais recente primeiro", @@ -1360,22 +1417,27 @@ "no_albums_yet": "Parece que ainda não tem nenhum álbum.", "no_archived_assets_message": "Arquive fotos e vídeos para os ocultar da sua visualização de fotos", "no_assets_message": "FAÇA CLIQUE PARA CARREGAR A SUA PRIMEIRA FOTO", - "no_assets_to_show": "Não há arquivos para exibir", + "no_assets_to_show": "Não há ficheiros para exibir", "no_cast_devices_found": "Nenhum dispositivo de transmissão encontrado", + "no_checksum_local": "Sem cálculo de verificação disponível - não pode capturar conteúdos locais", + "no_checksum_remote": "Soma de verificação (checksum) não disponível - não é possível obter o recurso remoto", "no_duplicates_found": "Nenhum item duplicado foi encontrado.", "no_exif_info_available": "Sem informações exif disponíveis", "no_explore_results_message": "Carregue mais fotos para explorar a sua coleção.", "no_favorites_message": "Adicione aos favoritos para encontrar as suas melhores fotos e vídeos rapidamente", "no_libraries_message": "Crie uma biblioteca externa para ver as suas fotos e vídeos", + "no_local_assets_found": "Sem cálculo de verificação disponível", "no_locked_photos_message": "Fotos e vídeos na pasta trancada estão ocultos e não serão exibidos enquanto explora ou pesquisa na biblioteca.", "no_name": "Sem nome", "no_notifications": "Sem notificações", "no_people_found": "Nenhuma pessoa encontrada", "no_places": "Sem lugares", + "no_remote_assets_found": "Soma de verificação (checksum) não disponível - não é possível obter o recurso remoto", "no_results": "Sem resultados", "no_results_description": "Tente um sinónimo ou uma palavra-chave mais comum", "no_shared_albums_message": "Crie um álbum para partilhar fotos e vídeos com pessoas na sua rede", "no_uploads_in_progress": "Nenhum carregamento em curso", + "not_available": "N/A", "not_in_any_album": "Não está em nenhum álbum", "not_selected": "Não selecionado", "note_apply_storage_label_to_previously_uploaded assets": "Nota: Para aplicar o Rótulo de Armazenamento a ficheiros carregados anteriormente, execute o", @@ -1389,6 +1451,9 @@ "notifications": "Notificações", "notifications_setting_description": "Gerir notificações", "oauth": "OAuth", + "obtainium_configurator": "Configurador Obtainium", + "obtainium_configurator_instructions": "Utilize o Obtainium para instalar e atualizar a aplicação Android diretamente a partir do lançamento do Immich no Github. Crie uma chave API e selecione uma variante para criar a sua ligação de configuração do Obtainium", + "ocr": "Reconhecimento Ótico de Caracteres (OCR)", "official_immich_resources": "Recursos oficiais do Immich", "offline": "Offline", "offset": "Desvio", @@ -1427,7 +1492,7 @@ "partner_list_user_photos": "Fotos de {user}", "partner_list_view_all": "Ver tudo", "partner_page_empty_message": "As suas fotos ainda não foram compartilhadas com nenhum parceiro.", - "partner_page_no_more_users": "Não há mais usuários para adicionar", + "partner_page_no_more_users": "Não existem mais utilizadores para adicionar", "partner_page_partner_add_failed": "Ocorreu um erro ao adicionar parceiro", "partner_page_select_partner": "Selecionar parceiro", "partner_page_shared_to_title": "Compartilhar com", @@ -1471,12 +1536,12 @@ "permission_onboarding_permission_limited": "Permissão limitada. Para permitir que o Immich faça backups e gerencie sua galeria, conceda permissões para fotos e vídeos nas configurações.", "permission_onboarding_request": "O Immich requer autorização para ver as suas fotos e vídeos.", "person": "Pessoa", - "person_age_months": "{months, plural, one {# month} other {# months}} idade", - "person_age_year_months": "1 ano, {months, plural, one {# month} other {# months}} idade", - "person_age_years": "{years, plural, other {# years}} idade", + "person_age_months": "{months, plural, one {# month} other {# months}} de idade", + "person_age_year_months": "1 ano, {months, plural, one {# month} other {# months}} de idade", + "person_age_years": "{years, plural, other {# anos}} de idade", "person_birthdate": "Nasceu a {date}", "person_hidden": "{name}{hidden, select, true { (oculto)} other {}}", - "photo_shared_all_users": "Parece que partilhou as suas fotos com todos os utilizadores ou não tem nenhum utilizador para partilhar.", + "photo_shared_all_users": "Parece que já partilhou as suas fotos com todos os utilizadores ou não tem nenhum utilizador com quem partilhar.", "photos": "Fotos", "photos_and_videos": "Fotos & Vídeos", "photos_count": "{count, plural, one {{count, number} Foto} other {{count, number} Fotos}}", @@ -1493,10 +1558,14 @@ "play_memories": "Reproduzir memórias", "play_motion_photo": "Reproduzir foto em movimento", "play_or_pause_video": "Reproduzir ou Pausar vídeo", + "play_original_video": "Reproduzir o vídeo original", + "play_original_video_setting_description": "Preferir a reprodução de vídeos originais em vez de vídeos transcodificados. Se o ficheiro original não for compatível, este pode não ser reproduzido corretamente.", + "play_transcoded_video": "Reproduzir vídeo transcodificado", "please_auth_to_access": "Por favor autentique-se para aceder", "port": "Porta", - "preferences_settings_subtitle": "Gerenciar preferências do aplicativo", + "preferences_settings_subtitle": "Gerir as preferências da aplicação", "preferences_settings_title": "Preferências", + "preparing": "A Preparar", "preset": "Predefinição", "preview": "Pré-visualizar", "previous": "Anterior", @@ -1509,19 +1578,15 @@ "privacy": "Privacidade", "profile": "Perfil", "profile_drawer_app_logs": "Registo", - "profile_drawer_client_out_of_date_major": "O aplicativo está desatualizado. Por favor, atualize para a versão mais recente.", - "profile_drawer_client_out_of_date_minor": "O aplicativo está desatualizado. Por favor, atualize para a versão mais recente.", "profile_drawer_client_server_up_to_date": "Cliente e Servidor atualizados", "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Modo somente leitura ativado. Toque duas vezes no ícone do avatar do utilizador para sair.", - "profile_drawer_server_out_of_date_major": "O servidor está desatualizado. Atualize para a versão principal mais recente.", - "profile_drawer_server_out_of_date_minor": "O servidor está desatualizado. Atualize para a versão mais recente.", + "profile_drawer_readonly_mode": "Modo só de leitura ativado. Faça um toque longo no ícone do perfil do utilizador para sair.", "profile_image_of_user": "Imagem de perfil de {user}", "profile_picture_set": "Foto de perfil definida.", "public_album": "Álbum público", "public_share": "Partilhar Publicamente", "purchase_account_info": "Apoiante", - "purchase_activated_subtitle": "Agradecemos por apoiar o Immich e software de código aberto", + "purchase_activated_subtitle": "Agradecemos o seu apoio ao Immich e ao software de código aberto", "purchase_activated_time": "Ativado em {date}", "purchase_activated_title": "A sua chave foi ativada com sucesso", "purchase_button_activate": "Ativar", @@ -1540,7 +1605,7 @@ "purchase_lifetime_description": "Compra vitalícia", "purchase_option_title": "OPÇÕES DE COMPRA", "purchase_panel_info_1": "O desenvolvimento do Immich requer muito tempo e esforço, e temos engenheiros a tempo inteiro a trabalhar nele para melhorá-lo quanto possível. A nossa missão é para que o software de código aberto e práticas de negócio éticas se tornem numa fonte de rendimento sustentável para os desenvolvedores e criar um ecossistema que respeite a privacidade dos utilizadores e que ofereça alternativas reais a serviços cloud explorativos.", - "purchase_panel_info_2": "Como estamos comprometidos a não adicionar acesso pago, esta compra não lhe dará acesso a nenhuma funcionalidade adicional do Immich. Contamos com utilizadores como você para dar suporte ao desenvolvimento contínuo do Immich.", + "purchase_panel_info_2": "Uma vez que estamos empenhados em não adicionar barreiras de pagamento, esta compra não lhe dará quaisquer funcionalidades adicionais no Immich. Contamos com utilizadores como você para apoiar o desenvolvimento contínuo do Immich.", "purchase_panel_title": "Apoie o projeto", "purchase_per_server": "Por servidor", "purchase_per_user": "Por utilizador", @@ -1552,7 +1617,7 @@ "purchase_server_description_2": "Status de apoiante", "purchase_server_title": "Servidor", "purchase_settings_server_activated": "A chave de produto do servidor é gerida pelo administrador", - "query_asset_id": "Consultar ID do recurso", + "query_asset_id": "Consultar ID do ficheiro", "queue_status": "Em fila {count}/{total}", "rating": "Classificação por estrelas", "rating_clear": "Limpar classificação", @@ -1560,8 +1625,9 @@ "rating_description": "Mostrar a classificação EXIF no painel de informações", "reaction_options": "Opções de reação", "read_changelog": "Ler Novidades", - "readonly_mode_disabled": "Modo somente leitura desactivado", - "readonly_mode_enabled": "Modo somente leitura activado", + "readonly_mode_disabled": "Modo só de leitura desativado", + "readonly_mode_enabled": "Modo só de leitura ativado", + "ready_for_upload": "Pronto para upload", "reassign": "Reatribuir", "reassigned_assets_to_existing_person": "Reatribuir {count, plural, one {# ficheiro} other {# ficheiros}} para {name, select, null {uma pessoa existente} other {{name}}}", "reassigned_assets_to_new_person": "Reatribuído {count, plural, one {# ficheiro} other {# ficheiros}} a uma nova pessoa", @@ -1586,6 +1652,7 @@ "regenerating_thumbnails": "A atualizar miniaturas", "remote": "Remoto", "remote_assets": "Ficheiros Remotos", + "remote_media_summary": "Sumário de Ficheiros Remotos", "remove": "Remover", "remove_assets_album_confirmation": "Tem a certeza de que deseja remover {count, plural, one {# ficheiro} other {# ficheiros}} do álbum?", "remove_assets_shared_link_confirmation": "Tem certeza de que deseja remover {count, plural, one {# ficheiro} other {# ficheiros}} deste link partilhado?", @@ -1623,13 +1690,14 @@ "reset_password": "Redefinir palavra-passe", "reset_people_visibility": "Redefinir pessoas ocultas", "reset_pin_code": "Repor código PIN", - "reset_pin_code_description": "Se esqueceu o seu código PIN, pode entrar em contato com o administrador do servidor para o repor", + "reset_pin_code_description": "Se esqueceu o seu código PIN, pode entrar em contacto com o administrador do servidor para o repor", "reset_pin_code_success": "Código PIN redefinido com sucesso", - "reset_pin_code_with_password": "Pode sempre repor o seu código PIN com a sua senha", + "reset_pin_code_with_password": "Pode sempre repor o seu código PIN com a sua palavra-passe", "reset_sqlite": "Reiniciar Base de Dados SQLite", "reset_sqlite_confirmation": "Tem a certeza de que quer reiniciar a base de dados SQLite? Vai ter de terminar a sessão e entrar outra vez para sincronizar os dados de novo", "reset_sqlite_success": "Base de dados SQLite reiniciada com sucesso", "reset_to_default": "Repor predefinições", + "resolution": "Resolução", "resolve_duplicates": "Resolver itens duplicados", "resolved_all_duplicates": "Todos os itens duplicados resolvidos", "restore": "Restaurar", @@ -1638,16 +1706,17 @@ "restore_user": "Restaurar utilizador", "restored_asset": "Ficheiro restaurado", "resume": "Continuar", - "resume_paused_jobs": "Continuar {count, plural, one {# trabalho em pausa} other {# trabalhos pausados}}", + "resume_paused_jobs": "Continuar {count, plural, one {# trabalho em pausa} other {# trabalhos em pausa}}", "retry_upload": "Tentar carregar novamente", "review_duplicates": "Rever itens duplicados", - "review_large_files": "Rever arquivos grandes", + "review_large_files": "Rever ficheiros grandes", "role": "Função", "role_editor": "Editor", "role_viewer": "Visualizador", "running": "A executar", "save": "Guardar", "save_to_gallery": "Salvar na galeria", + "saved": "Guardado", "saved_api_key": "Chave de API guardada", "saved_profile": "Perfil guardado", "saved_settings": "Definições guardadas", @@ -1664,6 +1733,9 @@ "search_by_description_example": "Dia de caminhada em Leiria", "search_by_filename": "Pesquisar por nome de ficheiro ou extensão", "search_by_filename_example": "por exemplo, IMG_1234.JPG ou PNG", + "search_by_ocr": "Pesquisar por OCR", + "search_by_ocr_example": "Latte", + "search_camera_lens_model": "Pesquisar por modelo de lente...", "search_camera_make": "Pesquisar por marca da câmara...", "search_camera_model": "Pesquisar por modelo da câmara...", "search_city": "Pesquisar cidade...", @@ -1675,11 +1747,12 @@ "search_filter_date_title": "Selecione a data", "search_filter_display_option_not_in_album": "Fora de álbum", "search_filter_display_options": "Opções de exibição", - "search_filter_filename": "Pesquisar por nome do arquivo", + "search_filter_filename": "Pesquisar por nome do ficheiro", "search_filter_location": "Localização", "search_filter_location_title": "Selecione a localização", "search_filter_media_type": "Tipo da mídia", "search_filter_media_type_title": "Selecione o tipo da mídia", + "search_filter_ocr": "Pesquisar por OCR", "search_filter_people_title": "Selecionar pessoas", "search_for": "Pesquisar por", "search_for_existing_person": "Pesquisar por pessoas existentes", @@ -1742,6 +1815,7 @@ "server_online": "Servidor Online", "server_privacy": "Privacidade do Servidor", "server_stats": "Estado do servidor", + "server_update_available": "Está disponível uma atualização do servidor", "server_version": "Versão do servidor", "set": "Definir", "set_as_album_cover": "Definir como capa do álbum", @@ -1758,20 +1832,22 @@ "setting_image_viewer_preview_title": "Carregar imagem de pré-visualização", "setting_image_viewer_title": "Imagens", "setting_languages_apply": "Aplicar", - "setting_languages_subtitle": "Alterar o idioma do aplicativo", + "setting_languages_subtitle": "Alterar o idioma da aplicação", "setting_notifications_notify_failures_grace_period": "Notificar erros da cópia de segurança em segundo plano: {duration}", "setting_notifications_notify_hours": "{count} horas", "setting_notifications_notify_immediately": "imediatamente", "setting_notifications_notify_minutes": "{count} minutos", "setting_notifications_notify_never": "Nunca", "setting_notifications_notify_seconds": "{count} segundos", - "setting_notifications_single_progress_subtitle": "Informações detalhadas sobre o progresso do envio por arquivo", + "setting_notifications_single_progress_subtitle": "Informações detalhadas sobre o progresso do envio por ficheiro", "setting_notifications_single_progress_title": "Mostrar progresso detalhado do backup em segundo plano", "setting_notifications_subtitle": "Ajuste as preferências de notificação", - "setting_notifications_total_progress_subtitle": "Progresso do envio de arquivos (concluídos/total)", + "setting_notifications_total_progress_subtitle": "Progresso do envio de ficheiro (concluídos/total)", "setting_notifications_total_progress_title": "Mostrar progresso total do backup em segundo plano", + "setting_video_viewer_auto_play_subtitle": "Reproduzir os vídeos automaticamente quando abertos", + "setting_video_viewer_auto_play_title": "Reproduzir vídeos automaticamente", "setting_video_viewer_looping_title": "Repetir", - "setting_video_viewer_original_video_subtitle": "Ao transmitir um vídeo do servidor, usar o arquivo original, mesmo quando uma versão transcodificada esteja disponível. Pode fazer com que o vídeo demore para carregar. Vídeos disponíveis localmente são exibidos na qualidade original independente desta configuração.", + "setting_video_viewer_original_video_subtitle": "Ao transmitir um vídeo do servidor, usar o ficheiro original, mesmo se uma versão transcodificada estiver disponível. Pode causar interrupções. Vídeos disponíveis localmente são exibidos na qualidade original independentemente desta definição.", "setting_video_viewer_original_video_title": "Forçar vídeo original", "settings": "Definições", "settings_require_restart": "Reinicie o Immich para aplicar essa configuração", @@ -1789,7 +1865,7 @@ "shared_album_activity_remove_title": "Apagar atividade", "shared_album_section_people_action_error": "Erro ao sair/remover do álbum", "shared_album_section_people_action_leave": "Sair do álbum", - "shared_album_section_people_action_remove_user": "Remover usuário do álbum", + "shared_album_section_people_action_remove_user": "Remover utilizador do álbum", "shared_album_section_people_title": "PESSOAS", "shared_by": "Partilhado por", "shared_by_user": "Partilhado por {user}", @@ -1826,7 +1902,7 @@ "shared_link_info_chip_metadata": "EXIF", "shared_link_manage_links": "Gerenciar links compartilhados", "shared_link_options": "Opções de link partilhado", - "shared_link_password_description": "Exigir uma senha para aceder a este link partilhado", + "shared_link_password_description": "Exigir uma palavra-passe para aceder a este link partilhado", "shared_links": "Links partilhados", "shared_links_description": "Partilhar fotos e videos com um link", "shared_photos_and_videos_count": "{assetCount, plural, other {# Fotos & videos partilhados.}}", @@ -1861,6 +1937,7 @@ "show_slideshow_transition": "Mostrar transições no Modo de Apresentação", "show_supporter_badge": "Emblema de apoiante", "show_supporter_badge_description": "Mostrar um emblema de apoiante", + "show_text_search_menu": "Mostrar menu de pesquisa de texto", "shuffle": "Aleatório", "sidebar": "Barra lateral", "sidebar_display_description": "Mostrar um link para a vista na barra lateral", @@ -1891,6 +1968,7 @@ "stacktrace": "Stacktrace", "start": "Iniciar", "start_date": "Data de início", + "start_date_before_end_date": "A data de início deve ser anterior à data de fim", "state": "Estado/Distrito", "status": "Estado", "stop_casting": "Parar transmissão", @@ -1915,7 +1993,7 @@ "sync_albums_manual_subtitle": "Sincronizar todas as fotos e vídeos enviados para o álbum de backup selecionado", "sync_local": "Sincronização Local", "sync_remote": "Sincronização Remota", - "sync_status": "Status da sincronização", + "sync_status": "Estado da sincronização", "sync_status_subtitle": "Ver e gerir o sistema de sincronização", "sync_upload_album_setting_subtitle": "Crie e envie suas fotos e vídeos para o álbum selecionado no Immich", "tag": "Etiqueta", @@ -1941,12 +2019,13 @@ "theme_setting_primary_color_subtitle": "Selecione a cor primária, utilizada nas ações principais e nos realces.", "theme_setting_primary_color_title": "Cor primária", "theme_setting_system_primary_color_title": "Use a cor do sistema", - "theme_setting_system_theme_switch": "Automático (Siga a configuração do sistema)", - "theme_setting_theme_subtitle": "Escolha a configuração do tema do aplicativo", + "theme_setting_system_theme_switch": "Automático (Seguir a configuração do sistema)", + "theme_setting_theme_subtitle": "Escolha a configuração do tema da aplicação", "theme_setting_three_stage_loading_subtitle": "O carregamento em três estágios pode aumentar o desempenho do carregamento, mas causa uma carga de rede significativamente maior", "theme_setting_three_stage_loading_title": "Habilitar carregamento em três estágios", "they_will_be_merged_together": "Eles serão unidos", "third_party_resources": "Recursos de terceiros", + "time": "Hora", "time_based_memories": "Memórias baseadas no tempo", "timeline": "Linha de tempo", "timezone": "Fuso horário", @@ -1969,16 +2048,17 @@ "trash_emptied": "Lixeira esvaziada", "trash_no_results_message": "Fotos e vídeos enviados para a reciclagem aparecem aqui.", "trash_page_delete_all": "Excluir tudo", - "trash_page_empty_trash_dialog_content": "Deseja esvaziar a lixera? Estes arquivos serão apagados de forma permanente do Immich", + "trash_page_empty_trash_dialog_content": "Deseja esvaziar a reciclagem? Estes ficheiros serão apagados permanentemente do Immich", "trash_page_info": "Ficheiros na reciclagem irão ser eliminados permanentemente após {days} dias", "trash_page_no_assets": "Lixeira vazia", "trash_page_restore_all": "Restaurar tudo", - "trash_page_select_assets_btn": "Selecionar arquivos", + "trash_page_select_assets_btn": "Selecionar ficheiros", "trash_page_title": "Reciclagem ({count})", "trashed_items_will_be_permanently_deleted_after": "Os itens da reciclagem são eliminados permanentemente após {days, plural, one {# dia} other {# dias}}.", "troubleshoot": "Diagnosticar problemas", "type": "Tipo", "unable_to_change_pin_code": "Não foi possível alterar o código PIN", + "unable_to_check_version": "Ocorreu um erro ao verificar versão da aplicação ou do servidor", "unable_to_setup_pin_code": "Não foi possível configurar o código PIN", "unarchive": "Desarquivar", "unarchive_action_prompt": "{count} removidos do Arquivo", @@ -2007,15 +2087,15 @@ "unstacked_assets_count": "Desempilhados {count, plural, one {# ficheiro} other {# ficheiros}}", "untagged": "Marcador removido", "up_next": "A seguir", - "update_location_action_prompt": "Actualize a localização de {count} activos seleccionados com:", + "update_location_action_prompt": "Atualize a localização de {count} ficheiros selecionados com:", "updated_at": "Atualizado a", "updated_password": "Palavra-passe atualizada", "upload": "Carregar", "upload_action_prompt": "{count} à espera de carregar", "upload_concurrency": "Carregamentos em simultâneo", "upload_details": "Detalhes do Carregamento", - "upload_dialog_info": "Deseja fazer o backup dos arquivos selecionados no servidor?", - "upload_dialog_title": "Enviar arquivo", + "upload_dialog_info": "Deseja realizar uma cópia de segurança dos ficheiros selecionados para o servidor?", + "upload_dialog_title": "Enviar ficheiro", "upload_errors": "Envio completo com {count, plural, one {# erro} other {# erros}}, atualize a página para ver os novos ficheiros enviados.", "upload_finished": "Carregamento acabado", "upload_progress": "Restante(s) {remaining, number} - Processado(s) {processed, number}/{total, number}", @@ -2093,5 +2173,6 @@ "yes": "Sim", "you_dont_have_any_shared_links": "Não tem links partilhados", "your_wifi_name": "Nome da sua rede Wi-Fi", - "zoom_image": "Ampliar/Reduzir imagem" + "zoom_image": "Ampliar/Reduzir imagem", + "zoom_to_bounds": "Aproximar aos limites" } diff --git a/i18n/pt_BR.json b/i18n/pt_BR.json index ae5335c872..61dd5a7b75 100644 --- a/i18n/pt_BR.json +++ b/i18n/pt_BR.json @@ -28,10 +28,12 @@ "add_to_album": "Adicionar ao álbum", "add_to_album_bottom_sheet_added": "Adicionado ao {album}", "add_to_album_bottom_sheet_already_exists": "Já existe em {album}", + "add_to_album_bottom_sheet_some_local_assets": "Alguns arquivos / mídias não puderam ser adicionados ao álbum", "add_to_album_toggle": "Alternar a seleção de {album}", "add_to_albums": "Adicionar aos álbuns", "add_to_albums_count": "Adicionar aos álbuns ({count})", "add_to_shared_album": "Adicionar ao álbum compartilhado", + "add_upload_to_stack": "Adicionar upload ao grupo", "add_url": "Adicionar URL", "added_to_archive": "Adicionado ao arquivo", "added_to_favorites": "Adicionado aos favoritos", @@ -118,11 +120,18 @@ "library_settings_description": "Gerenciar configurações de biblioteca externa", "library_tasks_description": "Verificar se há arquivos novos ou modificados nas bibliotecas externas", "library_watching_enable_description": "Observe bibliotecas externas para alterações de arquivos", - "library_watching_settings": "Observação de biblioteca (EXPERIMENTAL)", + "library_watching_settings": "Observação de biblioteca [EXPERIMENTAL]", "library_watching_settings_description": "Observe automaticamente os arquivos alterados", "logging_enable_description": "Habilitar logs", "logging_level_description": "Quando ativado, qual nível de log usar.", "logging_settings": "Logs", + "machine_learning_availability_checks": "Verficações de disponibilidade", + "machine_learning_availability_checks_description": "Automaticamente detectar e preferir servidores de machine learning disponíveis", + "machine_learning_availability_checks_enabled": "Habilitar verificações de disponibilidade", + "machine_learning_availability_checks_interval": "Intervalo de verificação", + "machine_learning_availability_checks_interval_description": "Intervalo em milisegundos entre verificações de disponibilidade", + "machine_learning_availability_checks_timeout": "Tempo limite da solicitação", + "machine_learning_availability_checks_timeout_description": "Tempo limite em milisegundos para verificações de disponibilidade", "machine_learning_clip_model": "Modelo CLIP", "machine_learning_clip_model_description": "O nome de um modelo CLIP listado aqui. Lembre-se de executar novamente a tarefa de 'Pesquisa Inteligente' para todas as imagens após alterar o modelo.", "machine_learning_duplicate_detection": "Detecção de duplicidade", @@ -145,6 +154,16 @@ "machine_learning_min_detection_score_description": "Pontuação mínima de confiança para um rosto ser detectado, de 0 a 1. Valores mais baixos detectam mais rostos, mas poderão resultar em falsos positivos.", "machine_learning_min_recognized_faces": "Mínimo de rostos reconhecidos", "machine_learning_min_recognized_faces_description": "O número mínimo de rostos reconhecidos para uma pessoa ser criada. Aumentar isso torna o Reconhecimento Facial mais preciso, ao custo de aumentar a chance de um rosto não ser atribuído a uma pessoa.", + "machine_learning_ocr": "OCR", + "machine_learning_ocr_description": "Usar machine learning para reconhecer textos em imagens", + "machine_learning_ocr_enabled": "Habilitar OCR", + "machine_learning_ocr_enabled_description": "Se desabilitado, imagens não serão submetidas a reconhecimento de texto.", + "machine_learning_ocr_max_resolution": "Resolução máxima", + "machine_learning_ocr_max_resolution_description": "Prévias acima dessa resolução serão redimensionadas com a preservação da proporção da imagem. Valores maiores são mais precisos, mas levam mais tempo para serem processados e usam mais memória.", + "machine_learning_ocr_min_detection_score": "Pontuação mínima para detecção", + "machine_learning_ocr_min_detection_score_description": "Pontuação mínima de confiançá para o texto ser detectado de 0 a 1. Valores mais baixos detectarão mais texto mas podem resultar em falsos positivos.", + "machine_learning_ocr_min_recognition_score": "Pontuação mínima de reconhecimento", + "machine_learning_ocr_min_score_recognition_description": "Pontuação mínima de confiança para o texto ser detectado de 0 a 1. Valores mais baixos reconhecerão mais textos mas podem resultar em falsos positivos.", "machine_learning_settings": "Configurações de aprendizado de máquina", "machine_learning_settings_description": "Gerenciar recursos e configurações do aprendizado de máquina", "machine_learning_smart_search": "Pesquisa Inteligente", @@ -202,6 +221,8 @@ "notification_email_ignore_certificate_errors_description": "Ignorar erros de validação de certificado TLS (não recomendado)", "notification_email_password_description": "Senha a ser usada ao autenticar no servidor de e-mail", "notification_email_port_description": "Porta do servidor de e-mail (por exemplo, 25, 465 ou 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "Usar SMTPS (SMTP por TLS)", "notification_email_sent_test_email_button": "Envie e-mail de teste e salve", "notification_email_setting_description": "Configurações para envio de notificações por e-mail", "notification_email_test_email": "Enviar e-mail de teste", @@ -324,7 +345,7 @@ "transcoding_max_b_frames": "Máximo de quadros B", "transcoding_max_b_frames_description": "Valores mais altos melhoram a eficiência da compactação, mas retardam a codificação. Pode não ser compatível com aceleração de hardware em dispositivos mais antigos. 0 desativa os quadros B, enquanto -1 define esse valor automaticamente.", "transcoding_max_bitrate": "Taxa de bits máxima", - "transcoding_max_bitrate_description": "Definir uma taxa de bits máxima pode tornar os tamanhos dos arquivos mais previsíveis com um custo menor de qualidade. Em 720p, os valores típicos são 2.600 kbit/s para VP9 ou HEVC, ou 4.500 kbit/s para H.264. Desativado se definido como 0.", + "transcoding_max_bitrate_description": "Definir uma taxa de bits máxima pode tornar os tamanhos dos arquivos mais previsíveis, ao custo de pior qualidade. Em 720p, os valores típicos são 2.600 kbit/s para VP9 ou HEVC, ou 4.500 kbit/s para H.264. Desativado se definido como 0. Quando uma unidade não for especificada, k (de kbit/s) será utilizado, ou seja, 5000, 5000k e 5M (de Mbit/s) são equivalentes.", "transcoding_max_keyframe_interval": "Intervalo máximo de quadro-chave", "transcoding_max_keyframe_interval_description": "Define a distância máxima do quadro entre os quadros-chave. Valores mais baixos pioram a eficiência da compressão, mas melhoram os tempos de busca e podem melhorar a qualidade em cenas com movimento rápido. 0 define esse valor automaticamente.", "transcoding_optimal_description": "Vídeos com resolução superior à desejada ou em formato não aceito", @@ -341,8 +362,8 @@ "transcoding_settings_description": "Defina quais vídeos transcodificar e como serão processados", "transcoding_target_resolution": "Resolução desejada", "transcoding_target_resolution_description": "Resoluções mais altas podem preservar mais detalhes, mas demoram mais para codificar, têm tamanhos de arquivo maiores e podem reduzir a capacidade de resposta do aplicativo.", - "transcoding_temporal_aq": "QA temporal", - "transcoding_temporal_aq_description": "Aplica-se apenas ao NVENC. Aumenta a qualidade de cenas com alto detalhe e pouco movimento. Pode não ser compatível com dispositivos mais antigos.", + "transcoding_temporal_aq": "Quantização com adaptação temporal", + "transcoding_temporal_aq_description": "Aplica-se apenas ao NVENC. A Quantização com adaptação temporal aumenta a qualidade de cenas com alto detalhe e pouco movimento. Pode não ser compatível com dispositivos mais antigos.", "transcoding_threads": "Threads", "transcoding_threads_description": "Valores mais altos levam a uma codificação mais rápida, mas deixam menos espaço para o servidor processar outras tarefas enquanto estiver ativo. Este valor não deve ser superior ao número de núcleos da CPU. Maximiza a utilização se definido como 0.", "transcoding_tone_mapping": "Mapeamento de tons", @@ -363,7 +384,7 @@ "unlink_all_oauth_accounts_prompt": "Tem certeza que deseja desvincular todas as contas OAuth? Isto vai redefinir o ID OAuth de todos os usuário e não pode ser desfeito.", "user_cleanup_job": "Limpeza de usuários", "user_delete_delay": "A conta e os arquivos de {user} serão programados para exclusão permanente em {delay, plural, one {# dia} other {# dias}}.", - "user_delete_delay_settings": "Remover atraso", + "user_delete_delay_settings": "Período de carência", "user_delete_delay_settings_description": "Número de dias após a remoção para excluir permanentemente a conta e os arquivos de um usuário. A tarefa de exclusão de usuário é executada à meia-noite para verificar usuários que estão prontos para exclusão. As alterações nesta configuração serão avaliadas na próxima execução.", "user_delete_immediately": "A conta e os arquivos de {user} serão programados para exclusão permanente imediata.", "user_delete_immediately_checkbox": "Adicionar o usuário e seus arquivos na fila para serem deletados imediatamente", @@ -393,11 +414,11 @@ "advanced_settings_prefer_remote_subtitle": "Alguns dispositivos são extremamente lentos para carregar as miniaturas locais. Ative esta opção para preferir imagens do servidor.", "advanced_settings_prefer_remote_title": "Preferir imagens do servidor", "advanced_settings_proxy_headers_subtitle": "Defina os cabeçalhos do proxy que o Immich deve enviar em todas comunicações com a rede", - "advanced_settings_proxy_headers_title": "Cabeçalhos do Proxy", + "advanced_settings_proxy_headers_title": "Cabeçalhos de proxy customizados [EXPERIMENTAL]", "advanced_settings_readonly_mode_subtitle": "Ativar o modo de apenas visualização dos arquivos. As outras ações, como: selecionar várias imagens, compartilhar, transmitir ou deletar serão desabilitadas. Ative ou Desative este modo clicando na foto do usuário na tela principal", - "advanced_settings_readonly_mode_title": "Modo de apenas visualização", + "advanced_settings_readonly_mode_title": "Modo de leitura apenas", "advanced_settings_self_signed_ssl_subtitle": "Ignora a verificação do certificado SSL do servidor. Obrigatório para certificados auto assinados.", - "advanced_settings_self_signed_ssl_title": "Permitir certificados SSL auto assinados", + "advanced_settings_self_signed_ssl_title": "Permitir certificados SSL auto-assinados [EXPERIMENTAL]", "advanced_settings_sync_remote_deletions_subtitle": "Excluir ou restaurar os arquivos automaticamente neste dispositivo quando essas ações forem realizada na interface web", "advanced_settings_sync_remote_deletions_title": "Sincronizar exclusões remotas [EXPERIMENTAL]", "advanced_settings_tile_subtitle": "Configurações avançadas do usuário", @@ -457,10 +478,14 @@ "api_key_description": "Este valor será mostrado apenas uma vez. Por favor, certifique-se de copiá-lo antes de fechar a janela.", "api_key_empty": "O nome da sua chave de API não deve estar vazio", "api_keys": "Chaves de API", + "app_architecture_variant": "Variante (Arquitetura)", "app_bar_signout_dialog_content": "Tem certeza de que deseja sair?", "app_bar_signout_dialog_ok": "Sim", "app_bar_signout_dialog_title": "Sair", + "app_download_links": "Links de Download de App", "app_settings": "Configurações do Aplicativo", + "app_stores": "Loja de Aplicativos", + "app_update_available": "Atualizações de apps disponíveis", "appears_in": "Aparece em", "apply_count": "Aplicar ({count, number})", "archive": "Arquivar", @@ -544,6 +569,7 @@ "backup_albums_sync": "Backup de sincronização de álbuns", "backup_all": "Todos", "backup_background_service_backup_failed_message": "Falha ao fazer backup. Tentando novamente…", + "backup_background_service_complete_notification": "Backup de ativo completado", "backup_background_service_connection_failed_message": "Falha na conexão com o servidor. Tentando novamente…", "backup_background_service_current_upload_notification": "Enviando {filename}", "backup_background_service_default_notification": "Verificando se há novos arquivos…", @@ -591,6 +617,7 @@ "backup_controller_page_turn_on": "Ativar backup automático", "backup_controller_page_uploading_file_info": "Informações do arquivo", "backup_err_only_album": "Não é possível remover o único álbum", + "backup_error_sync_failed": "A sincronização falhou. Não foi possível processar o backup.", "backup_info_card_assets": "arquivos", "backup_manual_cancelled": "Cancelado", "backup_manual_in_progress": "Envio já está em progresso. Tente novamente mais tarde", @@ -678,8 +705,8 @@ "client_cert_import_success_msg": "Certificado do cliente importado", "client_cert_invalid_msg": "Arquivo de certificado inválido ou senha errada", "client_cert_remove_msg": "Certificado do cliente removido", - "client_cert_subtitle": "Suporta apenas o formato PKCS12 (.p12, .pfx). A importação/remoção de certificados está disponível somente antes do login", - "client_cert_title": "Certificado de Cliente SSL", + "client_cert_subtitle": "Suporta apenas o formato PKCS12 (.p12, .pfx). A importação/remoção de certificados está disponível apenas antes do login", + "client_cert_title": "Certificado de cliente SSL [EXPERIMENTAL]", "clockwise": "Horário", "close": "Fechar", "collapse": "Recolher", @@ -691,7 +718,6 @@ "comments_and_likes": "Comentários e curtidas", "comments_are_disabled": "Comentários estão desativados", "common_create_new_album": "Criar novo álbum", - "common_server_error": "Verifique a sua conexão de rede, certifique-se de que o servidor está acessível e de que as versões do aplicativo e servidor são compatíveis.", "completed": "Completado", "confirm": "Confirmar", "confirm_admin_password": "Confirmar senha de administrador", @@ -730,6 +756,7 @@ "create": "Criar", "create_album": "Criar álbum", "create_album_page_untitled": "Sem título", + "create_api_key": "Criar chave de API", "create_library": "Criar biblioteca", "create_link": "Criar link", "create_link_to_share": "Criar link e compartilhar", @@ -775,7 +802,7 @@ "default_locale": "Localização Padrão", "default_locale_description": "Formatar datas e números baseados na linguagem do seu navegador", "delete": "Excluir", - "delete_action_confirmation_message": "Confirma deletar este arquivo? O arquivo será enviado para a lixeira do servidor e depois perguntará se deseja deletar do seu dispositivo local", + "delete_action_confirmation_message": "Tem certeza? O arquivo será enviado para a lixeira do servidor, depois você poderá confirmar se deseja também deletar do seu dispositivo local", "delete_action_prompt": "{count} deletados", "delete_album": "Excluir álbum", "delete_api_key_prompt": "Tem certeza de que deseja excluir esta chave de API?", @@ -873,7 +900,6 @@ "edit_tag": "Editar marcador", "edit_title": "Editar Título", "edit_user": "Editar usuário", - "edited": "Editado", "editor": "Editar", "editor_close_without_save_prompt": "As alterações não serão salvas", "editor_close_without_save_title": "Fechar editor?", @@ -1029,6 +1055,7 @@ "exif_bottom_sheet_description_error": "Erro ao alterar a descrição", "exif_bottom_sheet_details": "DETALHES", "exif_bottom_sheet_location": "LOCALIZAÇÃO", + "exif_bottom_sheet_no_description": "Sem descrição", "exif_bottom_sheet_people": "PESSOAS", "exif_bottom_sheet_person_add_person": "Adicionar nome", "exit_slideshow": "Sair da apresentação", @@ -1106,11 +1133,10 @@ "hash_asset": "Calcular hash dos arquivos", "hashed_assets": "Com hash", "hashing": "Calculando", - "header_settings_add_header_tip": "Adicionar Cabeçalho", + "header_settings_add_header_tip": "Adicionar cabeçalho", "header_settings_field_validator_msg": "O valor não pode estar vazio", "header_settings_header_name_input": "Nome do cabeçalho", "header_settings_header_value_input": "Valor do cabeçalho", - "headers_settings_tile_subtitle": "Defina cabeçalhos de proxy que o aplicativo deve enviar com cada solicitação de rede", "headers_settings_tile_title": "Cabeçalhos de proxy personalizados", "hi_user": "Olá {name} ({email})", "hide_all_people": "Esconder todas as pessoas", @@ -1335,6 +1361,8 @@ "minute": "Minuto", "minutes": "Minutos", "missing": "Faltando", + "mobile_app": "Aplicativo Móvel", + "mobile_app_download_onboarding_note": "Baixe o aplicativo móvel usando as opções abaixo", "model": "Modelo", "month": "Mês", "monthly_title_text_date_format": "MMMM y", @@ -1353,6 +1381,8 @@ "my_albums": "Meus Álbuns", "name": "Nome", "name_or_nickname": "Nome ou apelido", + "navigate": "Navegar", + "navigate_to_time": "Navegar para Horário", "network_requirement_photos_upload": "Use a rede móvel para enviar fotos", "network_requirement_videos_upload": "Use a rede móvel para enviar vídeos", "network_requirements": "Requerimentos de Rede", @@ -1362,6 +1392,7 @@ "never": "Nunca", "new_album": "Novo Álbum", "new_api_key": "Nova Chave de API", + "new_date_range": "Nova faixa de datas", "new_password": "Nova senha", "new_person": "Nova Pessoa", "new_pin_code": "Novo código PIN", @@ -1412,6 +1443,8 @@ "notifications": "Notificações", "notifications_setting_description": "Gerenciar notificações", "oauth": "OAuth", + "obtainium_configurator": "Configurador Obtainium", + "obtainium_configurator_instructions": "Use o Obtainium para instalar e atualizar o aplicativo Android diretamente do lançamento do Immich no GitHub. Crie uma chave API e selecione a variante para criar o seu link de configuração Obtainium", "official_immich_resources": "Recursos oficiais do Immich", "offline": "Desconectado", "offset": "Deslocamento", @@ -1516,10 +1549,14 @@ "play_memories": "Reproduzir memórias", "play_motion_photo": "Reproduzir foto em movimento", "play_or_pause_video": "Reproduzir ou Pausar vídeo", + "play_original_video": "Reproduzir o vídeo original", + "play_original_video_setting_description": "Preferir por reprodução dos vídeos originais ao invés de vídeos transcodificados. Se o arquivo original não for compatível, ele pode não ser reproduzido corretamente.", + "play_transcoded_video": "Reproduzir vídeo transcodificado", "please_auth_to_access": "Por favor autentique-se para acessar", "port": "Porta", "preferences_settings_subtitle": "Gerenciar as preferências do aplicativo", "preferences_settings_title": "Preferências", + "preparing": "Preparando", "preset": "Predefinição", "preview": "Pré-visualizar", "previous": "Anterior", @@ -1532,13 +1569,9 @@ "privacy": "Privacidade", "profile": "Perfil", "profile_drawer_app_logs": "Logs", - "profile_drawer_client_out_of_date_major": "O aplicativo está desatualizado. Por favor, atualize para a versão mais recente.", - "profile_drawer_client_out_of_date_minor": "O aplicativo está desatualizado. Por favor, atualize para a versão mais recente.", "profile_drawer_client_server_up_to_date": "Cliente e Servidor estão atualizados", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Modo apenas leitura habilidato. Dê um toque prolongado na foto do usuário para sair deste modo.", - "profile_drawer_server_out_of_date_major": "O servidor está desatualizado. Atualize para a versão principal mais recente.", - "profile_drawer_server_out_of_date_minor": "O servidor está desatualizado. Atualize para a versão mais recente.", "profile_image_of_user": "Imagem do perfil de {user}", "profile_picture_set": "Foto de perfil definida.", "public_album": "Álbum público", @@ -1585,6 +1618,7 @@ "read_changelog": "Ler Novidades", "readonly_mode_disabled": "Modo apenas visualização desativado", "readonly_mode_enabled": "Modo apenas visualização ativado", + "ready_for_upload": "Pronto para upload", "reassign": "Reatribuir", "reassigned_assets_to_existing_person": "{count, plural, one {# arquivo reatribuído} other {# arquivos reatribuídos}} a {name, select, null {uma pessoa} other {{name}}}", "reassigned_assets_to_new_person": "{count, plural, one {# arquivo reatribuído} other {# arquivos reatribuídos}} a uma nova pessoa", @@ -1688,6 +1722,7 @@ "search_by_description_example": "Dia de caminhada no Ibirapuera", "search_by_filename": "Pesquisa por nome de arquivo ou extensão", "search_by_filename_example": "Por exemplo, IMG_1234.JPG ou PNG", + "search_camera_lens_model": "Buscar por modelo de lente...", "search_camera_make": "Pesquisar câmeras da marca...", "search_camera_model": "Pesquisar câmera do modelo...", "search_city": "Pesquisar cidade...", @@ -1766,6 +1801,7 @@ "server_online": "Servidor Disponível", "server_privacy": "Privacidade do servidor", "server_stats": "Status do servidor", + "server_update_available": "Uma atualização para o servidor está disponível", "server_version": "Versão do servidor", "set": "Definir", "set_as_album_cover": "Definir como capa do álbum", @@ -1794,6 +1830,8 @@ "setting_notifications_subtitle": "Ajuste suas preferências de notificação", "setting_notifications_total_progress_subtitle": "Progresso do envio de arquivos (concluídos/total)", "setting_notifications_total_progress_title": "Mostrar o progresso total do backup em segundo plano", + "setting_video_viewer_auto_play_subtitle": "Reproduzir os vídeos automaticamente quando abertos", + "setting_video_viewer_auto_play_title": "Reproduzir vídeos automaticamente", "setting_video_viewer_looping_title": "Repetir", "setting_video_viewer_original_video_subtitle": "Ao transmitir um vídeo do servidor, usar o arquivo original, mesmo quando uma versão transcodificada esteja disponível. Pode fazer com que o vídeo demore para carregar. Vídeos disponíveis localmente são exibidos na qualidade original independente desta configuração.", "setting_video_viewer_original_video_title": "Forçar vídeo original", @@ -1916,6 +1954,7 @@ "stacktrace": "Stacktrace", "start": "Início", "start_date": "Data inicial", + "start_date_before_end_date": "A data de início deve ser antes da data final", "state": "Estado", "status": "Status", "stop_casting": "Parar transmissão", @@ -2004,6 +2043,7 @@ "troubleshoot": "Diagnosticar", "type": "Tipo", "unable_to_change_pin_code": "Não foi possível alterar o código PIN", + "unable_to_check_version": "Não foi possível verificar a versão do aplicativo ou do servidor", "unable_to_setup_pin_code": "Não foi possível criar o código PIN", "unarchive": "Desarquivar", "unarchive_action_prompt": "{count} desarquivado", @@ -2027,7 +2067,7 @@ "unselect_all": "Desselecionar todos", "unselect_all_duplicates": "Desselecionar todas as duplicatas", "unselect_all_in": "Remover seleção de {group}", - "unstack": "Retirar do grupo", + "unstack": "Desagrupar", "unstack_action_prompt": "{count} desagrupados", "unstacked_assets_count": "{count, plural, one {# arquivo retirado} other {# arquivos retirados}} do grupo", "untagged": "Marcador removido", diff --git a/i18n/ro.json b/i18n/ro.json index 637460ae34..3d08b27d75 100644 --- a/i18n/ro.json +++ b/i18n/ro.json @@ -1,12 +1,12 @@ { "about": "Despre", "account": "Cont", - "account_settings": "Setări Cont", + "account_settings": "Setări cont", "acknowledge": "Văzut", "action": "Acţiune", "action_common_update": "Actualizează", "actions": "Acţiuni", - "active": "Activ", + "active": "Active", "activity": "Activitate", "activity_changed": "Activitatea este {enabled, select, true {activată} other {dezactivată}}", "add": "Adaugă", @@ -28,10 +28,12 @@ "add_to_album": "Adaugă în album", "add_to_album_bottom_sheet_added": "Adăugat în {album}", "add_to_album_bottom_sheet_already_exists": "Deja în {album}", + "add_to_album_bottom_sheet_some_local_assets": "Unele resurse locale nu au putut fi adăugate la album", "add_to_album_toggle": "Selectează/deselectează {album}", "add_to_albums": "Adaugă la albume", "add_to_albums_count": "Adaugă la albume ({count})", "add_to_shared_album": "Adaugă la album partajat", + "add_upload_to_stack": "Încarcă și adaugă la stivă", "add_url": "Adăugați adresa URL", "added_to_archive": "Adăugat la arhivă", "added_to_favorites": "Adaugă la favorite", @@ -40,12 +42,12 @@ "add_exclusion_pattern_description": "Adăugați modele de excludere. Globing folosind *, ** și ? este suportat. Pentru a ignora toate fișierele din orice director numit „Raw”, utilizați „**/Raw/**”. Pentru a ignora toate fișierele care se termină în „.tif”, utilizați „**/*.tif”. Pentru a ignora o cale absolută, utilizați „/path/to/ignore/**”.", "admin_user": "Utilizator admin", "asset_offline_description": "Acest material din biblioteca externă nu se mai găsește pe disc și a fost mutat în coșul de gunoi. Dacă fișierul a fost mutat în bibliotecă, verificați cronologia pentru noul material corespunzător. Pentru a restabili acest material, asigurați-vă că calea fișierului de mai jos poate fi accesată de Immich și scanați biblioteca.", - "authentication_settings": "Setări de Autentificare", + "authentication_settings": "Setări de autentificare", "authentication_settings_description": "Gestionează parola, OAuth și alte setări de autentificare", "authentication_settings_disable_all": "Ești sigur că vrei sa dezactivezi toate metodele de autentificare? Autentificarea va fi complet dezactivată.", "authentication_settings_reenable": "Pentru a reactiva, folosește Comandă Server.", "background_task_job": "Activități de Fundal", - "backup_database": "Salvare Bază de Date", + "backup_database": "Salvare bază de date", "backup_database_enable_description": "Activare salvarea bazei de date", "backup_keep_last_amount": "Număr de copii de rezervă anterioare de păstrat", "backup_onboarding_1_description": "copie externă în cloud sau într-o altă locație fizică.", @@ -72,9 +74,9 @@ "disable_login": "Dezactivați autentificarea", "duplicate_detection_job_description": "Rulați învățarea automată pe materiale pentru a detecta imagini similare. Se bazează pe Căutare Inteligentă", "exclusion_pattern_description": "Modelele de excludere vă permit să ignorați fișierele și folderele atunci când vă scanați biblioteca. Acest lucru este util dacă aveți foldere care conțin fișiere pe care nu doriți să le importați, cum ar fi fișierele RAW.", - "external_library_management": "Managementul Bibliotecii Externe", + "external_library_management": "Gestionarea bibliotecilor externe", "face_detection": "Detecție facială", - "face_detection_description": "Detectează fețele din fișiere folosind învățare automată. Pentru videoclipuri, este luată în considerare doar miniatura. „Reînprospătează” (re)procesează toate fișierele. „Resetează” adaugă în coadă fișierele care nu au fost încă procesate. Fețele detectate vor fi puse în coadă pentru recunoașterea facială după finalizarea detectării feței, grupându-le în persoane existente sau noi.", + "face_detection_description": "Detectează fețele din fișiere folosind învățare automată. Pentru videoclipuri, este luată în considerare doar miniatura. „Reîmprospătează” (re)procesează toate fișierele. „Resetează” adaugă în coadă fișierele care nu au fost încă procesate. Fețele detectate vor fi puse în coadă pentru recunoașterea facială după finalizarea detectării feței, grupându-le în persoane existente sau noi.", "facial_recognition_job_description": "Grupați fețele detectate în persoane. Acest pas rulează după ce Detectarea Feței este finalizată. „Resetează” (re)grupează toate fețele. „Lipsă” adaugă în coadă fețe care nu au o persoană desemnată.", "failed_job_command": "Comanda {command} a eșuat pentru jobul: {job}", "force_delete_user_warning": "AVERTISMENT: Acest lucru va elimina imediat utilizatorul și toate activele sale. Acest lucru nu poate fi anulat și fișierele nu pot fi recuperate.", @@ -91,30 +93,30 @@ "image_prefer_wide_gamut_setting_description": "Utilizați Display P3 pentru miniaturi. Acest lucru păstrează mai bine vibrația imaginilor cu spații de culoare largi, dar imaginile pot apărea diferit pe dispozitivele cu o versiune mai veche de browser. Imaginile sRGB sunt păstrate ca sRGB pentru a evita schimbările de culoare.", "image_preview_description": "Imagine de dimensiune medie cu metadate eliminate, utilizată la vizualizarea unui singur element și pentru învățarea automată", "image_preview_quality_description": "Calitatea previzualizării de la 1 la 100. O valoare mai mare oferă o calitate mai bună, dar produce fișiere mai mari și poate reduce receptivitatea aplicației. Setarea unei valori scăzute poate afecta calitatea învățării automate.", - "image_preview_title": "Previzualizați Setările", + "image_preview_title": "Previzualizați setările", "image_quality": "Calitate", "image_resolution": "Rezolutie", "image_resolution_description": "Rezoluțiile mai mari pot păstra mai multe detalii, dar necesită mai mult timp pentru a fi codificate, au dimensiuni mai mari ale fișierelor și pot reduce răspunsul aplicației.", - "image_settings": "Setări Imagine", + "image_settings": "Setări imagine", "image_settings_description": "Gestionează calitatea și rezoluția imaginilor generate", "image_thumbnail_description": "Miniatură mică cu metadate eliminate, utilizată la vizualizarea grupurilor de fotografii, cum ar fi în cronologia principală", "image_thumbnail_quality_description": "Calitatea miniaturii de la 1 la 100. O valoare mai mare oferă o calitate mai bună, dar produce fișiere mai mari și poate reduce receptivitatea aplicației.", - "image_thumbnail_title": "Setari Miniaturi", + "image_thumbnail_title": "Setari miniaturi", "job_concurrency": "Concurență {job}", "job_created": "Sarcină creată", "job_not_concurrency_safe": "Această sarcină nu este sigură pentru a rula în concurență.", - "job_settings": "Setări Sarcină", + "job_settings": "Setări sarcină", "job_settings_description": "Administrează concurența sarcinilor", - "job_status": "Starea Sarcinii", + "job_status": "Starea sarcinii", "jobs_delayed": "{jobCount, plural, other {# întârziat}}", "jobs_failed": "{jobCount, plural, other {# eșuat}}", - "library_created": "Librărie creată:{library}", + "library_created": "Librărie creată: {library}", "library_deleted": "Bibliotecă ștearsă", "library_import_path_description": "Specificați un folder pentru a îl importa. Acest folder, inclusiv sub-folderele, vor fi scanate pentru imagini și videoclipuri.", - "library_scanning": "Scanare Periodică", + "library_scanning": "Scanare periodică", "library_scanning_description": "Configurează scanarea periodică pentru bibliotecă", "library_scanning_enable_description": "Activează scanarea periodică pentru bibliotecă", - "library_settings": "Bibliotecă Externă", + "library_settings": "Bibliotecă externă", "library_settings_description": "Administrează setările pentru biblioteci externe", "library_tasks_description": "Scanează bibliotecile externe de active noi sau modificate", "library_watching_enable_description": "Urmărește bibliotecile externe pentru schimbări ale fișierelor", @@ -128,9 +130,11 @@ "machine_learning_availability_checks_enabled": "Activează verificare disponibilitate", "machine_learning_availability_checks_interval": "Interval verificare", "machine_learning_availability_checks_interval_description": "Interval in milisecunde între verificările de disponibilitate", + "machine_learning_availability_checks_timeout": "Timp de expirare cerere", + "machine_learning_availability_checks_timeout_description": "Timp de așteptare în milisecunde pentru verificările de disponibilitate", "machine_learning_clip_model": "Model CLIP", "machine_learning_clip_model_description": "Numele unui model CLIP listat aici. Rețineți că trebuie să rulați din nou funcția „Smart Search” pentru toate imaginile la schimbarea unui model.", - "machine_learning_duplicate_detection": "Detectare Duplicate", + "machine_learning_duplicate_detection": "Detectare duplicate", "machine_learning_duplicate_detection_enabled": "Activează detectarea duplicatelor", "machine_learning_duplicate_detection_enabled_description": "Dacă este dezactivată, elementele identice vor fi în continuare de-duplicate.", "machine_learning_duplicate_detection_setting_description": "Utilizați încorporările CLIP pentru a găsi dubluri probabile", @@ -150,6 +154,18 @@ "machine_learning_min_detection_score_description": "Scorul minim de încredere pentru ca o față să fie detectată de la 0 la 1. Valorile mai mici vor detecta mai multe fețe, dar pot duce la fals pozitive.", "machine_learning_min_recognized_faces": "Fețe minim recunoscute", "machine_learning_min_recognized_faces_description": "Numărul minim de fețe recunoscute pentru ca o persoană să fie creată. Creșterea acestui număr face ca recunoașterea facială să fie mai precisă, cu prețul creșterii șanselor ca o față să nu fie atribuită unei persoane.", + "machine_learning_ocr": "OCR", + "machine_learning_ocr_description": "Recunoașterea textului în imagini folosind învățarea automată", + "machine_learning_ocr_enabled": "Activează OCR", + "machine_learning_ocr_enabled_description": "Dacă este dezactivat, imaginile nu vor fi supuse procesului de recunoaștere a textului.", + "machine_learning_ocr_max_resolution": "Rezoluție maximă", + "machine_learning_ocr_max_resolution_description": "Previzualizările imaginilor care depășesc această rezoluție vor fi redimensionate, păstrând proporțiile. Valorile mai mari oferă o acuratețe mai bună, dar necesită mai mult timp pentru procesare și mai multă memorie.", + "machine_learning_ocr_min_detection_score": "Scor minim de detecție", + "machine_learning_ocr_min_detection_score_description": "Scor minim de încredere pentru detectarea textului, între 0 și 1. Valorile mai mici vor detecta mai mult text, dar pot genera rezultate fals pozitive.", + "machine_learning_ocr_min_recognition_score": "Scor minim de recunoaștere", + "machine_learning_ocr_min_score_recognition_description": "Scor minim de încredere pentru recunoașterea textului detectat, între 0 și 1. Valorile mai mici vor recunoaște mai mult text, dar pot produce rezultate de fals pozitiv.", + "machine_learning_ocr_model": "Model OCR", + "machine_learning_ocr_model_description": "Modelele de server sunt mai precise decât modelele mobile, dar necesită mai mult timp pentru procesare și folosesc mai multă memorie.", "machine_learning_settings": "Setări de învățare automată", "machine_learning_settings_description": "Gestionați caracteristicile și setările de învățare automată", "machine_learning_smart_search": "Căutare inteligentă", @@ -157,11 +173,11 @@ "machine_learning_smart_search_enabled": "Activați căutarea inteligentă", "machine_learning_smart_search_enabled_description": "Dacă este dezactivată, imaginile nu vor fi codificate pentru căutarea inteligentă.", "machine_learning_url_description": "URL-ul serverului de învățare automată. Dacă sunt furnizate mai multe URL-uri, fiecare server va fi încercat pe rând, până când unul răspunde cu succes, în ordine de la primul până la ultimul. Serverele care nu răspund vor fi ignorate temporar până revin online.", - "manage_concurrency": "Gestionarea Simultaneității", + "manage_concurrency": "Gestionarea simultaneității", "manage_log_settings": "Administrați setările jurnalului", "map_dark_style": "Mod întunecat", "map_enable_description": "Activați funcțiile hărții", - "map_gps_settings": "Setări Hartă & GPS", + "map_gps_settings": "Setări hartă & GPS", "map_gps_settings_description": "Gestionare setări Hartă & GPS (localizare inversă)", "map_implications": "Caracteristica hărții se bazează pe un serviciu extern de planșe (tiles.immich.cloud)", "map_light_style": "Mod deschis", @@ -178,7 +194,7 @@ "metadata_extraction_job_description": "Extragere informații metadate din fiecare fișier cum ar fi localizare GPS, fețe și rezoluție,", "metadata_faces_import_setting": "Activare import fețe", "metadata_faces_import_setting_description": "Importă fețe din datele EXIF ale imaginii și din fișiere tip \"sidecar\"", - "metadata_settings": "Setări Metadate", + "metadata_settings": "Setări metadate", "metadata_settings_description": "Gestionează setările pentru metadate", "migration_job": "Migrare", "migration_job_description": "Migrați miniaturile pentru elemente și fețe la cea mai recentă structură de foldere", @@ -207,6 +223,8 @@ "notification_email_ignore_certificate_errors_description": "Ignoră erorile de validare a certificatului TLS (nerecomandat)", "notification_email_password_description": "Parola utilizată pentru autentificarea în serverul de email", "notification_email_port_description": "Portul utilizat de serverul de email (ex. 25, 465 sau 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "Folosește SMTPS (SMTP prin TLS)", "notification_email_sent_test_email_button": "Trimite un email de test și salvează configurația", "notification_email_setting_description": "Setări pentru trimiterea de notificări pe email", "notification_email_test_email": "Trimitere email de test", @@ -239,6 +257,7 @@ "oauth_storage_quota_default_description": "Spațiul în GiB ce urmează a fi utilizat atunci când nu este furnizată nicio solicitare.", "oauth_timeout": "Solicitarea a expirat", "oauth_timeout_description": "Timp de expirare pentru solicitări în milisecunde", + "ocr_job_description": "Folosește învățarea automată pentru recunoașterea textului din imagini", "password_enable_description": "Autentificare cu email și parolǎ", "password_settings": "Autentificare cu Parolǎ", "password_settings_description": "Gestioneazǎ setǎrile de autentificare cu parola", @@ -256,7 +275,7 @@ "send_welcome_email": "Trimite email de bun-venit", "server_external_domain_settings": "Domeniu extern", "server_external_domain_settings_description": "Domeniu pentru distribuire publicǎ a scurtǎturilor, incluzând http(s)://", - "server_public_users": "Utilizatori Publici", + "server_public_users": "Utilizatori publici", "server_public_users_description": "Toți utilizatorii (nume și e-mail) sunt listați atunci când adăugați un utilizator la albumele partajate. Când este dezactivată, lista de utilizatori va fi disponibilă numai pentru utilizatorii admin.", "server_settings": "Setǎri Server", "server_settings_description": "Gestioneazǎ setǎrile serverului", @@ -278,7 +297,7 @@ "storage_template_more_details": "Pentru mai multe detalii despre aceasta caracteristică, accesați Șablon stocare si implicațiile", "storage_template_onboarding_description_v2": "Când este activată, această funcție va organiza automat fișierele pe baza șablonului definit de către utilizator. Pentru mai multe informații, accesează documentația.", "storage_template_path_length": "Limita de lungime pentru calea aproximativă: {length, number}/{limit, number}", - "storage_template_settings": "Șablon Stocare", + "storage_template_settings": "Șablon stocare", "storage_template_settings_description": "Gestionează structura folderelor și numele fișierelor pentru elementele încărcate", "storage_template_user_label": "{label} este eticheta de stocare a utilizatorului", "system_settings": "Setǎri de Sistem", @@ -294,9 +313,9 @@ "template_settings_description": "Gestionați șabloanele personalizate pentru notificări", "theme_custom_css_settings": "CSS personalizat", "theme_custom_css_settings_description": "Foile de stil în cascadă (CSS) permit personalizarea designului Immich.", - "theme_settings": "Setări Temă", + "theme_settings": "Setări temă", "theme_settings_description": "Gestionează personalizarea interfeței web Immich", - "thumbnail_generation_job": "Generare Miniaturi", + "thumbnail_generation_job": "Generare miniaturi", "thumbnail_generation_job_description": "Generează miniaturi mari, mici și estompate pentru fiecare resursă, precum și miniaturi pentru fiecare persoană", "transcoding_acceleration_api": "API de accelerare", "transcoding_acceleration_api_description": "API-ul care va interacționa cu dispozitivul tău pentru a accelera transcodarea. Această setare este 'cel mai bun efort': va reveni la transcodarea software în caz de eșec. VP9 poate funcționa sau nu, în funcție de hardware-ul tău.", @@ -322,7 +341,7 @@ "transcoding_disabled_description": "Nu transcodifică niciun videoclip; acest lucru poate afecta redarea pe anumite dispozitive", "transcoding_encoding_options": "Opțiuni codificare", "transcoding_encoding_options_description": "Setează codecuri , calitatea, rezoluția și alte opțiuni pentru videoclipuri codificare", - "transcoding_hardware_acceleration": "Accelerare Hardware", + "transcoding_hardware_acceleration": "Accelerare hardware", "transcoding_hardware_acceleration_description": "Experimental: transcodare mai rapidă, dar poate reduce calitatea la aceeași rată de biți", "transcoding_hardware_decoding": "Decodare hardware", "transcoding_hardware_decoding_setting_description": "Se aplică doar pentru NVENC, QSV și RKMPP. Activează accelerarea completă în loc de doar accelerarea codificării. S-ar putea să nu funcționeze pentru toate videoclipurile.", @@ -342,7 +361,7 @@ "transcoding_reference_frames": "Cadre de referință", "transcoding_reference_frames_description": "Numărul de cadre de referință atunci când se comprimă un cadru dat. Valorile mai mari îmbunătățesc eficiența compresiei, dar încetinesc codarea. 0 setează această valoare automat.", "transcoding_required_description": "Numai videoclipuri care nu sunt într-un format acceptat", - "transcoding_settings": "Setări de Transcodare Video", + "transcoding_settings": "Setări de transcodare video", "transcoding_settings_description": "Gestionează care videoclipuri să transcodam și cum să le procesam", "transcoding_target_resolution": "Rezoluția țintă", "transcoding_target_resolution_description": "Rezoluțiile mai mari pot păstra mai multe detalii, dar necesită mai mult timp pentru codare, au dimensiuni mai mari ale fișierelor și pot reduce răspunsul aplicației.", @@ -373,23 +392,23 @@ "user_delete_immediately": "Contul și resursele utilizatorului {user} vor fi puse în coadă pentru ștergere permanentă imediat.", "user_delete_immediately_checkbox": "Pune utilizatorul și resursele în coadă pentru ștergere imediată", "user_details": "Detalii utilizator", - "user_management": "Gestionarea Utilizatorilor", + "user_management": "Gestionarea utilizatorilor", "user_password_has_been_reset": "Parola utilizatorului a fost resetată:", "user_password_reset_description": "Vă rugăm să furnizați utilizatorului parola temporară și să îi informați că va trebui să o schimbe la următoarea autentificare.", "user_restore_description": "Contul utilizatorului {user} va fi restaurat.", "user_restore_scheduled_removal": "Restaurare utilizator - ștergere programată pe {date, date, long}", - "user_settings": "Setǎri Utilizator", + "user_settings": "Setǎri utilizator", "user_settings_description": "Gestioneazǎ setǎrile utilizatorului", "user_successfully_removed": "Utilizatorul {email} a fost eliminat cu succes.", "version_check_enabled_description": "Activează verificarea versiunii", "version_check_implications": "Funcția de verificare a versiunii se bazează pe comunicarea periodică cu github.com", - "version_check_settings": "Verificare Versiune", + "version_check_settings": "Verificare versiune", "version_check_settings_description": "Activeazǎ/dezactiveazǎ notificarea unei noi versiuni", "video_conversion_job": "Transcodați videoclipuri", "video_conversion_job_description": "Transcodați videoclipurile pentru o compatibilitate mai mare cu browserele și dispozitivele" }, - "admin_email": "E-mail Administrator", - "admin_password": "Parolă Administrator", + "admin_email": "E-mail administrator", + "admin_password": "Parolă administrator", "administration": "Administrare", "advanced": "Avansat", "advanced_settings_enable_alternate_media_filter_subtitle": "Utilizați această opțiune pentru a filtra conținutul media în timpul sincronizării pe baza unor criterii alternative. Încercați numai dacă întâmpinați probleme cu aplicația la detectarea tuturor albumelor.", @@ -398,9 +417,9 @@ "advanced_settings_prefer_remote_subtitle": "Unele dispozitive încarcă extrem de lent miniaturile din resursele locale. Activați această setare pentru a încărca imagini la distanță.", "advanced_settings_prefer_remote_title": "Preferă fotografii la distanță", "advanced_settings_proxy_headers_subtitle": "Definește antetele proxy pe care Immich ar trebui să le trimită cu fiecare solicitare de rețea", - "advanced_settings_proxy_headers_title": "Antete Proxy", - "advanced_settings_readonly_mode_subtitle": "Activează modul doar-citire, în care fotografiile pot fi doar vizualizate, iar acțiuni precum selectarea mai multor imagini, partajarea, redarea pe alt dispozitiv sau ștergerea sunt dezactivate. Activează/Dezactivează modul doar-citire din avatarul utilizatorului de pe ecranul principal.", - "advanced_settings_readonly_mode_title": "Mod doar-citire", + "advanced_settings_proxy_headers_title": "Headere proxy personalizate", + "advanced_settings_readonly_mode_subtitle": "Activează modul doar-citire, în care fotografiile pot fi doar vizualizate, iar acțiuni precum selectarea mai multor imagini, partajarea, redarea pe alt dispozitiv sau ștergerea sunt dezactivate. Activează/Dezactivează modul doar-citire din avatarul utilizatorului de pe ecranul principal", + "advanced_settings_readonly_mode_title": "Mod doar citire", "advanced_settings_self_signed_ssl_subtitle": "Omite verificare certificate SSL pentru distinația server-ului, necesar pentru certificate auto-semnate.", "advanced_settings_self_signed_ssl_title": "Permite certificate SSL auto-semnate", "advanced_settings_sync_remote_deletions_subtitle": "Ștergeți sau restaurați automat un element de pe acest dispozitiv atunci când acțiunea este efectuată pe web", @@ -462,10 +481,14 @@ "api_key_description": "Această valoare va fi afișată o singură dată. Vă rugăm să vă asigurați că o copiați înainte de a închide fereastra.", "api_key_empty": "Numele cheii API nu trebuie să fie gol", "api_keys": "Chei API", + "app_architecture_variant": "Variantă (Arhitectură)", "app_bar_signout_dialog_content": "Ești sigur că vrei să te deconectezi?", "app_bar_signout_dialog_ok": "Da", "app_bar_signout_dialog_title": "Deconectare", - "app_settings": "Setări Aplicație", + "app_download_links": "Linkuri de descărcare în aplicație", + "app_settings": "Setări aplicație", + "app_stores": "Magazine de aplicații", + "app_update_available": "Este disponibilă o actualizare a aplicației", "appears_in": "Apare în", "apply_count": "Aplică ({count, number})", "archive": "Arhivă", @@ -476,7 +499,7 @@ "archive_size": "Mărime arhivă", "archive_size_description": "Configurează dimensiunea arhivei pentru descărcări (în GiB)", "archived": "Arhivat", - "archived_count": "{count, plural, other {Arhivat/e#}}", + "archived_count": "{count, plural, one {Arhivat} few {# arhivate} other {# arhivate}}", "are_these_the_same_person": "Sunt aceștia aceeași persoană?", "are_you_sure_to_do_this": "Sunteți sigur că doriți să faceți acest lucru?", "asset_action_delete_err_read_only": "Fișierele cu permisiuni doar de citire nu au putut fi șterse, omitere", @@ -528,6 +551,7 @@ "assets_trashed_count": "Mutat în coșul de gunoi {count, plural, one {# resursă} other {# resurse}}", "assets_trashed_from_server": "{count} resursă(e) eliminate de pe serverul Immich", "assets_were_part_of_album_count": "{count, plural, one {Resursa era} other {Resursele erau}} deja parte din album", + "assets_were_part_of_albums_count": "{count, plural, one {Asset was} other {Assets were}} deja parte din albume", "authorized_devices": "Dispozitive Autorizate", "automatic_endpoint_switching_subtitle": "Conectează-te local prin rețeaua Wi‐Fi configurată când este valabilă și prin rețele alternative în caz contrar", "automatic_endpoint_switching_title": "Alternare URL automată", @@ -537,6 +561,7 @@ "background_backup_running_error": "Procesul de backup în fundal este activ, nu se poate porni backup manual", "background_location_permission": "Permisiune locație în fundal", "background_location_permission_content": "Pentru a putea schimba rețeaua activă în fundal, Immich are nevoie de acces *permanent* la locația precisă pentru a citi numele rețelei Wi-Fi", + "background_options": "Opțiuni de fundal", "backup": "Backup", "backup_album_selection_page_albums_device": "Albume în dispozitiv ({count})", "backup_album_selection_page_albums_tap": "Apasă odata pentru a include, de două ori pentru a exclude", @@ -544,8 +569,10 @@ "backup_album_selection_page_select_albums": "Selectează albume", "backup_album_selection_page_selection_info": "Informații selecție", "backup_album_selection_page_total_assets": "Total resurse unice", + "backup_albums_sync": "Sincronizarea albumelor de backup", "backup_all": "Toate", "backup_background_service_backup_failed_message": "Eșuare backup resurse. Reîncercare…", + "backup_background_service_complete_notification": "Backup resurse finalizat", "backup_background_service_connection_failed_message": "Conectare la server eșuată. Reîncercare…", "backup_background_service_current_upload_notification": "Încărcare {filename}", "backup_background_service_default_notification": "Verificare resurse noi…", @@ -583,7 +610,7 @@ "backup_controller_page_remainder": "Rămas(e)", "backup_controller_page_remainder_sub": "Fotografii și videoclipuri din selecție rămase pentru backup", "backup_controller_page_server_storage": "Stocare server", - "backup_controller_page_start_backup": "Începe backup", + "backup_controller_page_start_backup": "Începe copia de rezervă", "backup_controller_page_status_off": "Backup-ul automat în prim-plan este oprit", "backup_controller_page_status_on": "Backup-ul automat în prim-plan este pornit", "backup_controller_page_storage_format": "{used} din {total} folosit", @@ -593,12 +620,14 @@ "backup_controller_page_turn_on": "Activează backup-ul în prim-plan", "backup_controller_page_uploading_file_info": "Informații încărcare fișier", "backup_err_only_album": "Nu poți șterge singurul album", + "backup_error_sync_failed": "Sincronizarea a eșuat. Nu se poate procesa copia de rezervă.", "backup_info_card_assets": "resurse", "backup_manual_cancelled": "Anulat", "backup_manual_in_progress": "Încărcarea este deja în curs. Încearcă din nou mai târziu", "backup_manual_success": "Succes", "backup_manual_title": "Status încărcare", - "backup_options_page_title": "Opțiuni Backup", + "backup_options": "Opțiuni copie de rezervă", + "backup_options_page_title": "Opțiuni copie de rezervă", "backup_setting_subtitle": "Schimbă opțiuni pentru backup în prim-plan și în fundal", "backup_settings_subtitle": "Gestionați setările de încărcare", "backward": "În sens invers", @@ -609,7 +638,7 @@ "birthdate_saved": "Data nașterii salvată cu succes", "birthdate_set_description": "Data nașterii este utilizată pentru a calcula vârsta acestei persoane la momentul realizării fotografiei.", "blurred_background": "Fundal neclar", - "bugs_and_feature_requests": "Erori și Solicitări de Caracteristici", + "bugs_and_feature_requests": "Erori și solicitări de caracteristici", "build": "Versiunea", "build_image": "Versiune Imagine", "bulk_delete_duplicates_confirmation": "Ești sigur că vrei să ștergi în masă {count, plural, one {# resursă duplicată} other {# resurse duplicate}}? Aceasta va păstra cea mai mare resursă din fiecare grup și va șterge permanent toate celelalte duplicate. Nu poți anula această acțiune!", @@ -653,12 +682,16 @@ "change_password_description": "Aceasta este fie prima dată când te conectezi în sistem, fie s-a făcut o solicitare pentru a schimba parola ta. Te rog să introduci noua parolă mai jos.", "change_password_form_confirm_password": "Confirmă parola", "change_password_form_description": "Salut {name},\n\nAceasta este fie prima dată când te conectazi la sistem, fie s-a făcut o cerere pentru schimbarea parolei. Te rugăm să introduci noua parolă mai jos.", + "change_password_form_log_out": "Deconectează toate celelalte dispozitive", + "change_password_form_log_out_description": "Se recomandă deconectarea de pe toate celelalte dispozitive", "change_password_form_new_password": "Parolă nouă", "change_password_form_password_mismatch": "Parolele nu se potrivesc", "change_password_form_reenter_new_password": "Reintrodu noua parolă", "change_pin_code": "Schimbă codul PIN", "change_your_password": "Schimbă-ți parola", "changed_visibility_successfully": "Schimbare vizibilitate cu succes", + "charging": "Încărcare", + "charging_requirement_mobile_backup": "Pentru copia de rezervă în fundal, dispozitivul trebuie să fie în curs de încărcare", "check_corrupt_asset_backup": "Verifică copii de rezervă a resurselor corupte", "check_corrupt_asset_backup_button": "Efectuează verificarea", "check_corrupt_asset_backup_description": "Rulează această verificare doar prin Wi-Fi și doar după ce toate resursele au fost salvate în copia de rezerva. Procedura poate dura câteva minute.", @@ -677,7 +710,7 @@ "client_cert_import_success_msg": "Certificatul de client este importat", "client_cert_invalid_msg": "Fisier cu certificat invalid sau parola este greșită", "client_cert_remove_msg": "Certificatul de client este șters", - "client_cert_subtitle": "Acceptă doar formatul PKCS12 (.p12, .pfx). Importul/ștergerea certificatului este disponibil(ă) doar înainte de autentificare", + "client_cert_subtitle": "Este suportat doar formatul PKCS12 (.p12, .pfx). Importul/ștergerea certificatului este disponibil(ă) doar înainte de autentificare", "client_cert_title": "Certificat SSL pentru client", "clockwise": "În sensul acelor de ceas", "close": "Închideți", @@ -690,7 +723,6 @@ "comments_and_likes": "Comentarii & aprecieri", "comments_are_disabled": "Comentariile sunt dezactivate", "common_create_new_album": "Creează album nou", - "common_server_error": "Te rugăm să verifici conexiunea la rețea, asigura-te că server-ul este accesibil și că versiunile aplicației/server-ului sunt compatibile.", "completed": "Finalizat", "confirm": "Confirmați", "confirm_admin_password": "Confirmați Parola de Administrator", @@ -710,7 +742,7 @@ "control_bottom_app_bar_delete_from_immich": "Șterge din Immich", "control_bottom_app_bar_delete_from_local": "Șterge din dispozitiv", "control_bottom_app_bar_edit_location": "Editează locație", - "control_bottom_app_bar_edit_time": "Editează Data și Ora", + "control_bottom_app_bar_edit_time": "Editează data și ora", "control_bottom_app_bar_share_link": "Partajează linkul", "control_bottom_app_bar_share_to": "Distribuire către", "control_bottom_app_bar_trash_from_immich": "Mută în coș", @@ -729,6 +761,7 @@ "create": "Creează", "create_album": "Creează album", "create_album_page_untitled": "Fără nume", + "create_api_key": "Creează cheie API", "create_library": "Creează Bibliotecă", "create_link": "Creează link", "create_link_to_share": "Creează link pentru a distribui", @@ -745,6 +778,7 @@ "create_user": "Creează utilizator", "created": "Creat", "created_at": "Creat", + "creating_linked_albums": "Crearea albumelor cu link...", "crop": "Decupează", "curated_object_page_title": "Obiecte", "current_device": "Dispozitiv curent", @@ -757,6 +791,7 @@ "daily_title_text_date_year": "E, MMM dd, yyyy", "dark": "Întunecat", "dark_theme": "Comută tema întunecată", + "date": "Dată", "date_after": "După data", "date_and_time": "Dată și oră", "date_before": "Anterior datei", @@ -764,6 +799,7 @@ "date_of_birth_saved": "Data nașterii salvată cu succes", "date_range": "Interval de date", "day": "Zi", + "days": "Zile", "deduplicate_all": "Deduplicați Toate", "deduplication_criteria_1": "Marimea imagini în octeți", "deduplication_criteria_2": "Numărul de date EXIF", @@ -848,10 +884,12 @@ "edit": "Editare", "edit_album": "Editare album", "edit_avatar": "Editare avatar", - "edit_birthday": "Editează ziua de naștere", + "edit_birthday": "Modifică ziua de naștere", "edit_date": "Editare dată", "edit_date_and_time": "Editare dată și oră", "edit_date_and_time_action_prompt": "{count} data și ora modificării", + "edit_date_and_time_by_offset": "Schimbă data prin decalaj", + "edit_date_and_time_by_offset_interval": "Noul interval de date: {from} - {to}", "edit_description": "Editează descrierea", "edit_description_prompt": "Vă rugăm să selectați o descriere nouă:", "edit_exclusion_pattern": "Editarea modelului de excludere", @@ -861,14 +899,13 @@ "edit_key": "Tastă de editare", "edit_link": "Editare link", "edit_location": "Editare locație", - "edit_location_action_prompt": "{count} locație(i) editată(e)", + "edit_location_action_prompt": "{count} locație(i) modificată(e)", "edit_location_dialog_title": "Locație", "edit_name": "Editare nume", "edit_people": "Editare persoane", "edit_tag": "Editare etichetă", "edit_title": "Editare Titlu", "edit_user": "Editare utilizator", - "edited": "Editat", "editor": "Editor", "editor_close_without_save_prompt": "Schimbările nu vor fi salvate", "editor_close_without_save_title": "Închideți editorul?", @@ -891,7 +928,9 @@ "error": "Eroare", "error_change_sort_album": "Nu s-a putut modifica ordinea de sortare a albumului", "error_delete_face": "Eroare la ștergerea feței din activ", + "error_getting_places": "Eroare la obținerea locațiilor", "error_loading_image": "Eroare la încărcarea imaginii", + "error_loading_partners": "Eroare la încărcarea partenerilor: {error}", "error_saving_image": "Eroare: {error}", "error_tag_face_bounding_box": "Eroare la etichetarea feței - nu se pot obține coordonatele casetei de delimitare", "error_title": "Eroare - ceva nu a mers", @@ -924,6 +963,7 @@ "failed_to_load_notifications": "Nu s-au putut încărca notificările", "failed_to_load_people": "Eșec la încărcarea persoanelor", "failed_to_remove_product_key": "Eșec la eliminarea cheii de produs", + "failed_to_reset_pin_code": "Nu s-a reușit resetarea codului PIN", "failed_to_stack_assets": "Eșec la combinarea resurselor", "failed_to_unstack_assets": "Eșec la desfășurarea resurselor", "failed_to_update_notification_status": "Nu s-a putut actualiza starea notificării", @@ -932,6 +972,7 @@ "paths_validation_failed": "{paths, plural, one {# cale} other {# căi}} nu a trecut validarea", "profile_picture_transparent_pixels": "Pozele de profil nu pot avea pixeli transparenți. Te rugăm să mărești imaginea și/sau să o muți.", "quota_higher_than_disk_size": "Ați stabilit o valoare a spațiului de stocare mai mare decât dimensiunea discului", + "something_went_wrong": "Ceva nu a mers bine", "unable_to_add_album_users": "Imposibil de adăugat utilizatori în album", "unable_to_add_assets_to_shared_link": "Imposibil de adăugat resurse la link-ul partajat", "unable_to_add_comment": "Imposibil de adăugat comentariu", @@ -1020,6 +1061,7 @@ "exif_bottom_sheet_description_error": "Eroare la actualizarea descrierii", "exif_bottom_sheet_details": "DETALII", "exif_bottom_sheet_location": "LOCAȚIE", + "exif_bottom_sheet_no_description": "Fără descriere", "exif_bottom_sheet_people": "PERSOANE", "exif_bottom_sheet_person_add_person": "Adăugați nume", "exit_slideshow": "Ieșire din Prezentare", @@ -1039,7 +1081,7 @@ "export_database_description": "Exportați baza de date SQLite", "extension": "Extensie", "external": "Extern", - "external_libraries": "Biblioteci Externe", + "external_libraries": "Biblioteci externe", "external_network": "Rețea externă", "external_network_sheet_info": "Când nu se află în rețeaua Wi-Fi preferată, aplicația se va conecta la server prin prima dintre adresele URL de mai jos pe care o poate accesa, începând de sus în jos", "face_unassigned": "Nealocat", @@ -1054,30 +1096,37 @@ "favorites_page_no_favorites": "Nu au fost găsite resurse favorite", "feature_photo_updated": "Fotografie caracteristică actualizată", "features": "Caracteristici", + "features_in_development": "Funcții în dezvoltare", "features_setting_description": "Gestionați funcțiile aplicației", "file_name": "Nume de fișier", "file_name_or_extension": "Numele sau extensia fișierului", + "file_size": "Mărime fișier", "filename": "Numele fișierului", "filetype": "Tipul fișierului", "filter": "Filtre", "filter_people": "Filtrați persoanele", "filter_places": "Filtrează locurile", "find_them_fast": "Găsiți-le rapid prin căutare după nume", + "first": "Primul", "fix_incorrect_match": "Remediați potrivirea incorectă", "folder": "Dosar", "folder_not_found": "Dosar negăsit", - "folders": "Foldere", + "folders": "Fișiere", "folders_feature_description": "Răsfoire în conținutul folderului pentru fotografiile și videoclipurile din sistemul de fișiere", + "forgot_pin_code_question": "Ai uitat codul PIN?", "forward": "Redirecționare", "gcast_enabled": "Google Cast", "gcast_enabled_description": "Această funcție încarcă resurse externe de la Google pentru a funcționa.", "general": "General", + "geolocation_instruction_location": "Apasă pe o resursă cu coordonate GPS pentru a folosi locația sa, sau selectează direct o locație de pe hartă", "get_help": "Obțineți Ajutor", "get_wifiname_error": "Nu s-a putut obține numele rețelei Wi-Fi. Asigurați-vă că ați acordat permisiunile necesare și că sunteți conectat la o rețea Wi-Fi", "getting_started": "Noțiuni de Bază", "go_back": "Întoarcere", "go_to_folder": "Accesați folderul", "go_to_search": "Spre căutare", + "gps": "GPS", + "gps_missing": "Fără GPS", "grant_permission": "Acordați permisiunea", "group_albums_by": "Grupați albume de...", "group_country": "Grupare după țară", @@ -1088,11 +1137,13 @@ "haptic_feedback_switch": "Activează feedback-ul haptic", "haptic_feedback_title": "Feedback haptic", "has_quota": "Are spațiu de stocare", + "hash_asset": "Hash-ul resursei", + "hashed_assets": "Resurse hashed", + "hashing": "Generare hash", "header_settings_add_header_tip": "Adăugați antet", "header_settings_field_validator_msg": "Valoarea nu poate fi goală", "header_settings_header_name_input": "Numele antetului", "header_settings_header_value_input": "Valoarea antetului", - "headers_settings_tile_subtitle": "Definiți header-urile proxy pe care aplicația ar trebui să le trimită cu fiecare solicitare de rețea", "headers_settings_tile_title": "Header-uri proxy personalizate", "hi_user": "Bună {name} ({email})", "hide_all_people": "Ascundeți toate persoanele", @@ -1114,11 +1165,12 @@ "home_page_favorite_err_partner": "Momentan nu se pot adăuga fișierele partenerului la favorite, omitere", "home_page_first_time_notice": "Dacă este prima dată când utilizezi aplicația, te rugăm să te asiguri că alegi unul sau mai multe albume de backup, astfel încât cronologia să poată fi populată cu fotografiile și videoclipurile din aceste albume", "home_page_locked_error_local": "Nu se pot muta resursele locale în folderul blocat, se omit", - "home_page_locked_error_partner": "Nu se pot muta materialele partenerului în folderul blocat, se omit.", + "home_page_locked_error_partner": "Nu se pot muta resursele partenerului în folderul blocat, se omit.", "home_page_share_err_local": "Nu se pot distribui fișiere locale prin link, omitere", "home_page_upload_err_limit": "Se pot încărca maxim 30 de resurse odată, omitere", "host": "Gazdă", "hour": "Oră", + "hours": "Ore", "id": "ID", "idle": "Inactiv", "ignore_icloud_photos": "Ignoră fotografiile din iCloud", @@ -1178,10 +1230,13 @@ "language_no_results_title": "Nu au fost găsite limbi", "language_search_hint": "Căutați limbi...", "language_setting_description": "Selectați limba preferată", + "large_files": "Fișiere mari", + "last": "Ultimul", "last_seen": "Văzut ultima dată", "latest_version": "Ultima Versiune", "latitude": "Latitudine", "leave": "Părăsiți", + "leave_album": "Părăsește albumul", "lens_model": "Model obiectiv", "let_others_respond": "Permite altora să răspundă", "level": "Nivel", @@ -1195,6 +1250,7 @@ "library_page_sort_title": "Titlu album", "licenses": "Licențe", "light": "Lumină", + "like": "Îmi place", "like_deleted": "Preferat șters", "link_motion_video": "Link video în mișcare", "link_to_oauth": "Link către OAuth", @@ -1205,8 +1261,10 @@ "local": "Local", "local_asset_cast_failed": "Nu se poate converti un element care nu este încărcat pe server", "local_assets": "Asset-uri locale", + "local_media_summary": "Rezumatul fișierelor media locale", "local_network": "Rețea locală", "local_network_sheet_info": "Aplicația se va conecta la server prin intermediul acestei adrese URL atunci când utilizează rețeaua Wi-Fi specificată", + "location": "Locație", "location_permission": "Permisiunea de locație", "location_permission_content": "Pentru a utiliza funcția de comutare automată, Immich are nevoie de permisiune pentru locația precisă, astfel încât să poată citi numele rețelei Wi-Fi curente", "location_picker_choose_on_map": "Alege pe hartă", @@ -1216,6 +1274,7 @@ "location_picker_longitude_hint": "Introdu longitudinea aici", "lock": "Blocare", "locked_folder": "Dosar blocat", + "log_detail_title": "Detalii jurnal", "log_out": "Deconectare", "log_out_all_devices": "Deconectați-vă de la toate dispozitivele", "logged_in_as": "Conectat ca {user}", @@ -1246,13 +1305,15 @@ "login_password_changed_success": "Parola a fost actualizată cu succes", "logout_all_device_confirmation": "Sigur doriți să deconectați toate dispozitivele?", "logout_this_device_confirmation": "Sigur doriți să deconectați acest dispozitiv?", + "logs": "Jurnale", "longitude": "Longitudine", "look": "Examinare", "loop_videos": "Buclă videoclipuri", "loop_videos_description": "Activați pentru a rula in buclă automat un videoclip în vizualizatorul de detalii.", "main_branch_warning": "Utilizați o versiune de dezvoltare; vă recomandăm insistent să utilizați o versiune de lansare!", "main_menu": "Meniu principal", - "make": "Face", + "make": "Marcă", + "manage_geolocation": "Gestionați locația", "manage_shared_links": "Administrați link-urile distribuite", "manage_sharing_with_partners": "Gestionați partajarea cu partenerii", "manage_the_app_settings": "Gestionați setările aplicației", @@ -1261,7 +1322,7 @@ "manage_your_devices": "Gestionați-vă dispozitivele conectate", "manage_your_oauth_connection": "Gestionați-vă conexiunea OAuth", "map": "Hartă", - "map_assets_in_bounds": "{count, plural, one {# poză} other {# poze}}", + "map_assets_in_bounds": "{count, plural, =0 {Nu există fotografii în această zonă} one {# fotografie} other {# fotografii}}", "map_cannot_get_user_location": "Nu se poate obține locația utilizatorului", "map_location_dialog_yes": "Da", "map_location_picker_page_use_location": "Folosește această locație", @@ -1287,6 +1348,7 @@ "mark_as_read": "Marchează ca citit", "marked_all_as_read": "Marcate toate ca citite", "matches": "Corespunde", + "matching_assets": "Resurse similare", "media_type": "Tip media", "memories": "Amintiri", "memories_all_caught_up": "Sunteți la zi", @@ -1304,8 +1366,11 @@ "merge_people_successfully": "Persoane îmbinate cu succes", "merged_people_count": "Imbinate {count, plural, one {# persoană} other {# persoane}}", "minimize": "Minimizare", - "minute": "Minute", + "minute": "Minut", + "minutes": "Minute", "missing": "Lipsă", + "mobile_app": "Aplicație Mobilă", + "mobile_app_download_onboarding_note": "Descarcă aplicația mobilă folosind următoarele opțiuni", "model": "Model", "month": "Lună", "monthly_title_text_date_format": "MMMM y", @@ -1324,15 +1389,23 @@ "my_albums": "Albumele mele", "name": "Nume", "name_or_nickname": "Nume sau poreclǎ", + "navigate": "Navighează/Navigare", + "navigate_to_time": "Mergi la Timp", + "network_requirement_photos_upload": "Utilizați datele mobile pentru a face copii de rezervă ale fotografiilor", + "network_requirement_videos_upload": "Utilizați datele mobile pentru a face copii de rezervă ale videoclipurilor", + "network_requirements": "Cerințe privind rețeaua", + "network_requirements_updated": "Cerințele rețelei s-au modificat, resetarea cozii copiei de rezervă", "networking_settings": "Rețele", "networking_subtitle": "Gestionați setările endpoint-ului serverului", "never": "Niciodată", "new_album": "Album Nou", "new_api_key": "Cheie API nouǎ", + "new_date_range": "Interval de dată nou", "new_password": "Parolă nouă", "new_person": "Persoanǎ nouǎ", "new_pin_code": "Cod PIN nou", "new_pin_code_subtitle": "Aceasta este prima dată când accesați folderul blocat. Creați un cod PIN pentru a accesa în siguranță această pagină", + "new_timeline": "Noua cronologie", "new_user_created": "Utilizator nou creat", "new_version_available": "VERSIUNE NOUĂ DISPONIBILĂ", "newest_first": "Cel mai nou primul", @@ -1346,20 +1419,25 @@ "no_assets_message": "CLICK PENTRU A ÎNCĂRCA PRIMA TA FOTOGRAFIE", "no_assets_to_show": "Nicio resursă de afișat", "no_cast_devices_found": "Nu s-au găsit dispozitive de difuzare", + "no_checksum_local": "Nu există checksum – nu se pot prelua resursele locale", + "no_checksum_remote": "Nu există checksum – nu se pot prelua resursele la distanță", "no_duplicates_found": "Nu au fost găsite duplicate.", "no_exif_info_available": "Nu există informații exif disponibile", "no_explore_results_message": "Încarcați mai multe fotografii pentru a vă explora colecția.", "no_favorites_message": "Adăugați favorite pentru a găsi rapid cele mai bune fotografii și videoclipuri", "no_libraries_message": "Creați o bibliotecă externă pentru a vă vizualiza fotografiile și videoclipurile", + "no_local_assets_found": "Nicio resursă locală găsită cu acest checksum", "no_locked_photos_message": "Fotografiile și videoclipurile din folderul blocat sunt ascunse și nu vor apărea atunci când răsfoiți sau căutați în bibliotecă.", "no_name": "Fără Nume", "no_notifications": "Nicio notificare", "no_people_found": "Nu au fost găsite persoane potrivite căutării", "no_places": "Nu există locuri", + "no_remote_assets_found": "Nicio resursă de la distanță găsită cu acest checksum", "no_results": "Fără rezultate", "no_results_description": "Încercați un sinonim sau un cuvânt cheie mai general", "no_shared_albums_message": "Creați un album pentru a partaja fotografii și videoclipuri cu persoanele din rețeaua dvs", "no_uploads_in_progress": "Nicio încărcare în curs", + "not_available": "N/A", "not_in_any_album": "Nu există în niciun album", "not_selected": "Neselectat", "note_apply_storage_label_to_previously_uploaded assets": "Notă: Pentru a aplica eticheta de stocare la resursele încărcate anterior, rulați", @@ -1373,8 +1451,12 @@ "notifications": "Notificări", "notifications_setting_description": "Gestionați notificările", "oauth": "OAuth", + "obtainium_configurator": "Configurator Obtainium", + "obtainium_configurator_instructions": "Folosește Obtainium pentru a instala și actualiza aplicația Android direct din release-urile Immich de pe GitHub. Creează o cheie API și selectează o variantă pentru a genera linkul de configurare Obtainium", + "ocr": "OCR", "official_immich_resources": "Resurse Oficiale Immich", "offline": "Offline", + "offset": "Decalaj", "ok": "Bine", "oldest_first": "Cel mai vechi mai întâi", "on_this_device": "Pe acest dispozitiv", @@ -1393,6 +1475,8 @@ "open_the_search_filters": "Deschideți filtrele de căutare", "options": "Opțiuni", "or": "sau", + "organize_into_albums": "Organizați în albume", + "organize_into_albums_description": "Pune fotografiile existente în albume folosind setările curente de sincronizare", "organize_your_library": "Organizează-ți biblioteca", "original": "original", "other": "Alte", @@ -1452,6 +1536,9 @@ "permission_onboarding_permission_limited": "Permisiune limitată. Pentru a permite Immich să facă copii de siguranță și să gestioneze întreaga colecție de galerii, acordă permisiuni pentru fotografii și videoclipuri în Setări.", "permission_onboarding_request": "Immich necesită permisiunea de a vizualiza fotografiile și videoclipurile tale.", "person": "Persoanǎ", + "person_age_months": "{months, plural, one {# lună} other {# luni}}", + "person_age_year_months": "1 an, {months, plural, one {# lună} other {# luni}}", + "person_age_years": "{years, plural, other {# years}} vechime", "person_birthdate": "Născut pe {date}", "person_hidden": "{name}{hidden, select, true { (ascuns)} other {}}", "photo_shared_all_users": "Se pare că ți-ai partajat fotografiile tuturor utilizatorilor sau că nu ai niciun utilizator căruia să le distribui.", @@ -1471,10 +1558,14 @@ "play_memories": "Redare amintiri", "play_motion_photo": "Redare Fotografie în Mișcare", "play_or_pause_video": "Redați sau întrerupeți videoclipul", + "play_original_video": "Redă video original", + "play_original_video_setting_description": "Se preferă redarea videoclipurilor originale în locul celor transcodate. Dacă fișierul original nu este compatibil, redarea s-ar putea să nu fie corectă.", + "play_transcoded_video": "Redă video transcodificat", "please_auth_to_access": "Vă rugăm să vă autentificați pentru a accesa", "port": "Port", "preferences_settings_subtitle": "Gestionați preferințele aplicației", "preferences_settings_title": "Preferințe", + "preparing": "Se prepară", "preset": "Presetat", "preview": "Previzualizare", "previous": "Anterior", @@ -1487,12 +1578,9 @@ "privacy": "Confidențialitate", "profile": "Profil", "profile_drawer_app_logs": "Log-uri", - "profile_drawer_client_out_of_date_major": "Aplicația nu folosește ultima versiune. Te rugăm să actualizezi la ultima versiune majoră.", - "profile_drawer_client_out_of_date_minor": "Aplicația nu folosește ultima versiune. Te rugăm să actualizezi la ultima versiune minoră.", "profile_drawer_client_server_up_to_date": "Aplicația client și server-ul sunt actualizate", "profile_drawer_github": "GitHub", - "profile_drawer_server_out_of_date_major": "Server-ul nu folosește ultima versiune. Te rugăm să actualizezi la ultima versiune majoră.", - "profile_drawer_server_out_of_date_minor": "Server-ul nu folosește ultima versiune. Te rugăm să actulizezi la ultima versiune minoră.", + "profile_drawer_readonly_mode": "Mod doar citire activat. Ține apăsat pe pictograma avatarului utilizatorului pentru a ieși.", "profile_image_of_user": "Imagine de profil a lui {user}", "profile_picture_set": "Poză de profil setată.", "public_album": "Album public", @@ -1529,6 +1617,7 @@ "purchase_server_description_2": "Statutul de suporter", "purchase_server_title": "Server", "purchase_settings_server_activated": "Cheia de produs a serverului este gestionată de administrator", + "query_asset_id": "Interoghează ID-ul resursei", "queue_status": "Se pun în coadă {count}/{total}", "rating": "Evaluare cu stele", "rating_clear": "Anulați evaluarea", @@ -1536,6 +1625,9 @@ "rating_description": "Afișați evaluarea EXIF în panoul de informații", "reaction_options": "Opțiuni de reacție", "read_changelog": "Citiți Jurnalul de Modificări", + "readonly_mode_disabled": "Modul doar citire dezactivat", + "readonly_mode_enabled": "Modul doar citire activat", + "ready_for_upload": "Pregătit pentru încărcare", "reassign": "Reatribuiți", "reassigned_assets_to_existing_person": "Re-alocat {count, plural, one {# resursă} other {# resurse}} to {name, select, null {unei persoane existente} other {{name}}}", "reassigned_assets_to_new_person": "Re-alocat {count, plural, one {# resursă} other {# resurse}} unei noi persoane", @@ -1560,6 +1652,7 @@ "regenerating_thumbnails": "Se regenerează miniaturile", "remote": "De la distanță", "remote_assets": "Elemente la distanță", + "remote_media_summary": "Rezumat media de la distanță", "remove": "Eliminați", "remove_assets_album_confirmation": "Sigur doriți să eliminați {count, plural, one {# resursă} other {# resurse}} din album?", "remove_assets_shared_link_confirmation": "Sigur doriți să eliminați {count, plural, one {# resursă} other {# resurse}} din acest link comun?", @@ -1597,10 +1690,14 @@ "reset_password": "Resetare parolă", "reset_people_visibility": "Resetați vizibilitatea persoanelor", "reset_pin_code": "Resetare cod PIN", + "reset_pin_code_description": "Dacă ți-ai uitat codul PIN, poți contacta administratorul serverului pentru a-l reseta", + "reset_pin_code_success": "Codul PIN a fost resetat cu succes", + "reset_pin_code_with_password": "Puteți reseta oricând codul PIN cu ajutorul parolei", "reset_sqlite": "Resetare bază de date SQLite", "reset_sqlite_confirmation": "Sigur doriți să resetați baza de date SQLite? Va trebui să vă deconectați și să vă conectați din nou pentru a resincroniza datele", "reset_sqlite_success": "Resetarea cu succes a bazei de date SQLite", "reset_to_default": "Resetați la valoarea implicită", + "resolution": "Rezoluție", "resolve_duplicates": "Rezolvați duplicatele", "resolved_all_duplicates": "Rezolvați toate duplicatele", "restore": "Restaurați", @@ -1609,20 +1706,23 @@ "restore_user": "Restabiliți utilizatorul", "restored_asset": "Resursă restaurată", "resume": "Reluare", + "resume_paused_jobs": "Reluați {count, plural, one {# paused job} other {# paused jobs}}", "retry_upload": "Reîncercați încărcarea", "review_duplicates": "Examinați duplicatele", + "review_large_files": "Revizuirea fișierelor mari", "role": "Rol", "role_editor": "Editor", "role_viewer": "Vizualizator", "running": "Rulează", "save": "Salvați", "save_to_gallery": "Salvați în galerie", + "saved": "Salvat", "saved_api_key": "Cheie API salvată", "saved_profile": "Profil salvat", "saved_settings": "Setări salvate", "say_something": "Spuneți ceva", "scaffold_body_error_occurred": "A apărut o eroare", - "scan_all_libraries": "Scanați Toate Bibliotecile", + "scan_all_libraries": "Scanați toate bibliotecile", "scan_library": "Scanare", "scan_settings": "Setări Scanare", "scanning_for_album": "Se scanează după album...", @@ -1633,10 +1733,13 @@ "search_by_description_example": "Zi de drumeție în Sapa", "search_by_filename": "Căutați după numele fișierului sau extensie", "search_by_filename_example": "i.e. IMG_1234.JPG sau PNG", + "search_by_ocr": "Caută folosind OCR", + "search_by_ocr_example": "Latte", + "search_camera_lens_model": "Caută modelul lentilei...", "search_camera_make": "Se caută marca camerei...", "search_camera_model": "Se caută modelul camerei...", - "search_city": "Se caută orașul...", - "search_country": "Se caută țara...", + "search_city": "Caută în orașul...", + "search_country": "Caută în țara...", "search_filter_apply": "Aplicați filtrul", "search_filter_camera_title": "Selectați tipul de cameră", "search_filter_date": "Dată", @@ -1649,9 +1752,10 @@ "search_filter_location_title": "Selectați locația", "search_filter_media_type": "Tip media", "search_filter_media_type_title": "Selectați tipul media", + "search_filter_ocr": "Caută dupa OCR", "search_filter_people_title": "Selectați persoane", "search_for": "Căutare după", - "search_for_existing_person": "Se caută o persoană existentă", + "search_for_existing_person": "Caută o persoană existentă", "search_no_more_result": "Nu mai există rezultate", "search_no_people": "Fără persoane", "search_no_people_named": "Nicio persoană numită \"{name}\"", @@ -1673,7 +1777,7 @@ "search_rating": "Caută după notă...", "search_result_page_new_search_hint": "Căutare nouă", "search_settings": "Setări de căutare", - "search_state": "Starea căutării...", + "search_state": "Caută în Stat/Județ...", "search_suggestion_list_smart_search_hint_1": "Căutarea inteligentă este activată în mod implicit, pentru a căuta metadata, utilizează sintaxa ", "search_suggestion_list_smart_search_hint_2": "m:termen-de-căutare", "search_tags": "Căutați etichete...", @@ -1701,6 +1805,7 @@ "select_user_for_sharing_page_err_album": "Creare album eșuată", "selected": "Selectat", "selected_count": "{count, plural, other {# selectat}}", + "selected_gps_coordinates": "Coordonate GPS selectate", "send_message": "Trimiteți mesaj", "send_welcome_email": "Trimiteți email de bun venit", "server_endpoint": "Endpoint server", @@ -1709,7 +1814,8 @@ "server_offline": "Serverul este offline", "server_online": "Server online", "server_privacy": "Confidențialitatea serverului", - "server_stats": "Statistici Server", + "server_stats": "Statistici server", + "server_update_available": "Actualizare pentru server disponibilă", "server_version": "Versiune Server", "set": "Setați", "set_as_album_cover": "Setați ca și copertă a albumului", @@ -1738,6 +1844,8 @@ "setting_notifications_subtitle": "Ajustează preferințele pentru notificări", "setting_notifications_total_progress_subtitle": "Progresul general al încărcării (resurse finalizate/total)", "setting_notifications_total_progress_title": "Afișează progresul total al copiilor de siguranță în fundal", + "setting_video_viewer_auto_play_subtitle": "Pornește automat redarea videoclipurilor când sunt deschise", + "setting_video_viewer_auto_play_title": "Redare automată a videoclipurilor", "setting_video_viewer_looping_title": "Buclă", "setting_video_viewer_original_video_subtitle": "Când redați în flux un videoclip de pe server, redați originalul chiar și atunci când este disponibilă o transcodare. Poate duce la încărcare temporară. Videoclipurile disponibile local sunt redate la calitatea originală indiferent de această setare.", "setting_video_viewer_original_video_title": "Forțează videoclipul original", @@ -1768,6 +1876,7 @@ "shared_link_clipboard_copied_massage": "Copiat în clipboard", "shared_link_clipboard_text": "Link: {link}\nParolă: {password}", "shared_link_create_error": "Eroare în timpul creării linkului de distribuire", + "shared_link_custom_url_description": "Accesează acest link partajat cu un URL personalizat", "shared_link_edit_description_hint": "Introdu descrierea distribuirii", "shared_link_edit_expire_after_option_day": "1 zi", "shared_link_edit_expire_after_option_days": "{count} zile", @@ -1793,6 +1902,7 @@ "shared_link_info_chip_metadata": "EXIF", "shared_link_manage_links": "Administrează link-urile distribuite", "shared_link_options": "Opțiuni de link partajat", + "shared_link_password_description": "Solicită o parolă pentru a accesa acest link partajat", "shared_links": "Link-uri distribuite", "shared_links_description": "Partajare imagini și clipuri printr-un link", "shared_photos_and_videos_count": "{assetCount, plural, other {# fotografii și videoclipuri partajate.}}", @@ -1827,6 +1937,7 @@ "show_slideshow_transition": "Afișați tranziția de prezentare", "show_supporter_badge": "Insigna suporterului", "show_supporter_badge_description": "Arată o insignă de suporter", + "show_text_search_menu": "Afișează meniul de căutare text", "shuffle": "Amestecați", "sidebar": "Bara laterală", "sidebar_display_description": "Afișați un link către vizualizare în bara laterală", @@ -1842,6 +1953,7 @@ "sort_created": "Data creării", "sort_items": "Numărul de articole", "sort_modified": "Data modificării", + "sort_newest": "Cea mai nouă fotografie", "sort_oldest": "Cea mai veche fotografie", "sort_people_by_similarity": "Sortează oameni după asemanare", "sort_recent": "Cea mai recentă fotografie", @@ -1856,7 +1968,8 @@ "stacktrace": "Urmă stivă", "start": "Început", "start_date": "Data de începere", - "state": "Situaţie", + "start_date_before_end_date": "Data de început trebuie să fie înainte de data de sfârșit", + "state": "Stat/Județ", "status": "Stare", "stop_casting": "Opriți difuzarea", "stop_motion_photo": "Opriți Fotografia in Mișcare", @@ -1880,6 +1993,8 @@ "sync_albums_manual_subtitle": "Sincronizează toate videoclipurile și fotografiile încărcate cu albumele de rezervă selectate", "sync_local": "Sincronizare locală", "sync_remote": "Sincronizare la distanță", + "sync_status": "Status-ul sincronizării", + "sync_status_subtitle": "Vizualizează și gestionează sistemul de sincronizare", "sync_upload_album_setting_subtitle": "Creează și încarcă fotografiile și videoclipurile tale în albumele selectate de pe Immich", "tag": "Etichetă", "tag_assets": "Eticheta resurselor", @@ -1910,6 +2025,7 @@ "theme_setting_three_stage_loading_title": "Pornește încărcarea în 3 etape", "they_will_be_merged_together": "Vor fi îmbinate împreună", "third_party_resources": "Resurse Terță Parte", + "time": "Timp", "time_based_memories": "Amintiri bazate pe timp", "timeline": "Cronologie", "timezone": "Fus orar", @@ -1917,7 +2033,9 @@ "to_change_password": "Schimbaţi parola", "to_favorite": "Favorit", "to_login": "Conectare", + "to_multi_select": "pentru selecție multiplă", "to_parent": "Du-te la părinte", + "to_select": "a selecta", "to_trash": "Coș de gunoi", "toggle_settings": "Activați setările", "total": "Total", @@ -1937,8 +2055,10 @@ "trash_page_select_assets_btn": "Selectează resurse", "trash_page_title": "Coș ({count})", "trashed_items_will_be_permanently_deleted_after": "Elementele din coșul de gunoi vor fi șterse definitiv după {days, plural, one {# zi} other {# zile}}.", + "troubleshoot": "Depanați", "type": "Tip", "unable_to_change_pin_code": "Nu se poate schimba codul PIN", + "unable_to_check_version": "Verificarea versiunii aplicației sau serverului a eșuat", "unable_to_setup_pin_code": "Nu se poate configura codul PIN", "unarchive": "Dezarhivați", "unarchive_action_prompt": "{count} șters(e) din Arhivă", @@ -1963,10 +2083,11 @@ "unselect_all_duplicates": "Deselectați toate duplicatele", "unselect_all_in": "Deselectați toate din {group}", "unstack": "Dezasamblați", - "unstack_action_prompt": "{count} unstacked", + "unstack_action_prompt": "{count} neîmpachetate", "unstacked_assets_count": "Nestivuit {count, plural, one {# resursă} other {# resurse}}", "untagged": "Neetichetat", "up_next": "Mai departe", + "update_location_action_prompt": "Actualizează locația pentru {count} resurse selectate cu:", "updated_at": "Actualizat", "updated_password": "Parolă actualizată", "upload": "Încărcați", @@ -2033,13 +2154,14 @@ "view_next_asset": "Vizualizați următoarea resursă", "view_previous_asset": "Vizualizați resursa anterioară", "view_qr_code": "Vezi cod QR", - "view_stack": "Vizualizați Stiva", + "view_similar_photos": "Vizualizați poze similare", + "view_stack": "Vizualizare stivă", "view_user": "Vizualizare utilizator", "viewer_remove_from_stack": "Șterge din grup", "viewer_stack_use_as_main_asset": "Folosește ca resursă principală", "viewer_unstack": "Anulează grup", "visibility_changed": "Vizibilitatea schimbată pentru {count, plural, one {# persoană} other {# persoane}}", - "waiting": "Așteptați", + "waiting": "În așteptare", "warning": "Avertisment", "week": "Sǎptǎmânǎ", "welcome": "Bun venit", @@ -2051,5 +2173,6 @@ "yes": "Da", "you_dont_have_any_shared_links": "Nu aveți linkuri partajate", "your_wifi_name": "Numele rețelei tale WiFi", - "zoom_image": "Măriți Imaginea" + "zoom_image": "Măriți Imaginea", + "zoom_to_bounds": "Mărește la margini" } diff --git a/i18n/ru.json b/i18n/ru.json index 26d379d68e..52cd3adbc3 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -28,10 +28,12 @@ "add_to_album": "Добавить в альбом", "add_to_album_bottom_sheet_added": "Добавлено в альбом {album}", "add_to_album_bottom_sheet_already_exists": "Уже в альбоме {album}", + "add_to_album_bottom_sheet_some_local_assets": "Некоторые объекты не добавлены в альбом, поскольку еще не загружены на сервер", "add_to_album_toggle": "Переключить выделение для альбома {album}", "add_to_albums": "Добавить в альбомы", "add_to_albums_count": "Добавить в альбомы ({count})", "add_to_shared_album": "Добавить в общий альбом", + "add_upload_to_stack": "Загрузить и добавить в группу", "add_url": "Добавить URL", "added_to_archive": "Добавлено в архив", "added_to_favorites": "Добавлено в избранное", @@ -39,7 +41,7 @@ "admin": { "add_exclusion_pattern_description": "Добавьте шаблоны исключений. Поддерживаются символы подстановки *, ** и ?. Чтобы игнорировать все файлы в любом каталоге с именем \"Raw\", укажите \"**/Raw/**\". Чтобы игнорировать все файлы, заканчивающиеся на \".tif\", используйте \"**/*.tif\". Чтобы игнорировать путь целиком, укажите \"/path/to/ignore/**\".", "admin_user": "Администратор", - "asset_offline_description": "Этот файл внешней библиотеки не был найден на диске и был перемещён в корзину. Если файл был перемещён внутри библиотеки, проверьте временную шкалу, чтобы найти новый соответствующий ресурс. Чтобы восстановить файл, убедитесь, что путь ниже доступен для Immich и выполните сканирование библиотеки.", + "asset_offline_description": "Этот объект из внешней библиотеки не был обнаружен на диске и поэтому перемещён в корзину. Если файл объекта был перемещён внутри библиотеки, проверьте временную шкалу, чтобы найти новый соответствующий объект. Чтобы восстановить файл, убедитесь, что следующий путь доступен для Immich, и выполните сканирование библиотеки.", "authentication_settings": "Настройки аутентификации", "authentication_settings_description": "Управление паролями, OAuth и другими настройками аутентификации", "authentication_settings_disable_all": "Вы уверены, что хотите отключить все методы входа? Вход будет полностью отключен.", @@ -62,7 +64,7 @@ "confirm_delete_library": "Вы действительно хотите удалить библиотеку {library}?", "confirm_delete_library_assets": "Вы уверены, что хотите удалить эту библиотеку? Это безвозвратно удалит {count, plural, one {# объект} many {# объектов} other {# объекта}} из Immich. Файлы останутся на диске.", "confirm_email_below": "Введите \"{email}\" для подтверждения", - "confirm_reprocess_all_faces": "Вы уверены, что хотите повторно определить все лица? Будут также удалены имена со всех лиц.", + "confirm_reprocess_all_faces": "Вы действительно хотите переопределить все лица? Также будут очищены имена всех людей.", "confirm_user_password_reset": "Вы действительно хотите сбросить пароль пользователя {user}?", "confirm_user_pin_code_reset": "Вы действительно хотите сбросить PIN-код пользователя {user}?", "create_job": "Создать задачу", @@ -77,7 +79,7 @@ "face_detection_description": "Обнаруживает лица на объектах с использованием машинного обучения. Для видео анализируется только миниатюра. Кнопка \"Обновить\" запускает повторную обработку всех объектов. \"Сброс\" — дополнительно удаляет все имеющиеся данные о лицах. \"Отсутствующие\" — ставит в очередь объекты, которые ещё не были обработаны. Обнаруженные лица помещаются в очередь для задачи Распознавание лиц и последующей их привязки к существующим или новым людям.", "facial_recognition_job_description": "Группирует и назначает обнаруженные лица людям. Выполняется после завершения задачи Обнаружение лиц. Кнопка \"Сброс\" (пере)назначает все лица. \"Отсутствующие\" — добавляет в очередь обработки лица, не привязанные к человеку.", "failed_job_command": "Команда {command} не выполнена для задачи: {job}", - "force_delete_user_warning": "ПРЕДУПРЕЖДЕНИЕ: Это приведет к немедленному удалению пользователя и его ресурсов. Это действие невозможно отменить, и файлы не могут быть восстановлены.", + "force_delete_user_warning": "ПРЕДУПРЕЖДЕНИЕ: Это приведет к немедленному удалению пользователя и всех его объектов. Это действие невозможно отменить, файлы не смогут быть восстановлены.", "image_format": "Формат", "image_format_description": "WebP создает файлы меньшего размера, чем JPEG, но кодирует медленнее.", "image_fullsize_description": "Полноразмерное изображение без метаданных, используется при увеличении", @@ -118,7 +120,7 @@ "library_settings_description": "Управление внешними библиотеками", "library_tasks_description": "Сканирование внешних библиотек на наличие новых и/или изменённых объектов", "library_watching_enable_description": "Отслеживать изменения файлов во внешних библиотеках", - "library_watching_settings": "Слежение за библиотекой (ЭКСПЕРИМЕНТАЛЬНОЕ)", + "library_watching_settings": "[ЭКСПЕРИМЕНТАЛЬНО] Слежение за библиотекой", "library_watching_settings_description": "Автоматически следить за изменениями файлов", "logging_enable_description": "Включить ведение журнала", "logging_level_description": "Если включено, выберите желаемый уровень журналирования.", @@ -145,13 +147,25 @@ "machine_learning_facial_recognition_setting": "Включить функцию распознавания лиц", "machine_learning_facial_recognition_setting_description": "При отключении этой функции изображения не будут кодироваться для распознавания лиц, и не будет заполняться раздел Люди.", "machine_learning_max_detection_distance": "Максимальное различие изображений", - "machine_learning_max_detection_distance_description": "Максимальное различие между двумя изображениями, чтобы считать их дубликатами, в диапазоне 0,001-0,1. Более высокие значения позволяют обнаружить больше дубликатов, но могут привести к ложным срабатываниям.", + "machine_learning_max_detection_distance_description": "Максимальное различие между двумя изображениями, чтобы считать их дубликатами (в диапазоне от 0,001 до 0,1). Более высокое значение позволит найти больше дубликатов, но может привести к ложным срабатываниям.", "machine_learning_max_recognition_distance": "Порог распознавания", - "machine_learning_max_recognition_distance_description": "Максимальное различие между двумя лицами, которые можно считать одним и тем же человеком, в диапазоне 0-2. Понижение этого параметра может предотвратить распознавание двух людей как одного и того же человека, а повышение — как двух разных людей. Имейте в виду, что проще объединить двух людей, чем разделить одного человека на двоих, поэтому по возможности выбирайте меньший порог.", + "machine_learning_max_recognition_distance_description": "Максимальное различие между двумя лицами, которые можно считать одним человеком (в диапазоне от 0 до 2). Понижение этого параметра может предотвратить распознавание двух людей как одного и того же человека, а повышение — как двух разных людей. Имейте в виду, что проще объединить двух людей, чем разделить одного человека на двоих, поэтому по возможности выбирайте меньший порог.", "machine_learning_min_detection_score": "Минимальный порог распознавания", - "machine_learning_min_detection_score_description": "Минимальный порог для обнаружения лица от 0 до 1. Более низкие значения позволяют обнаружить больше лиц, но могут привести к ложным срабатываниям.", + "machine_learning_min_detection_score_description": "Минимальный порог для обнаружения лица (от 0 до 1). Более низкое значение позволит находить больше лиц, но может привести к ложным срабатываниям.", "machine_learning_min_recognized_faces": "Минимум распознанных лиц", "machine_learning_min_recognized_faces_description": "Минимальное количество распознанных лиц для создания человека. Увеличение этого параметра делает распознавание лиц более точным, но при этом увеличивается вероятность того, что лицо не будет присвоено человеку.", + "machine_learning_ocr": "Распознавание текста", + "machine_learning_ocr_description": "Использование машинного обучения для распознавания текста на изображениях", + "machine_learning_ocr_enabled": "Включить распознавание текста", + "machine_learning_ocr_enabled_description": "При отключении этой функции изображения не будут подвергаться обнаружению и распознаванию текста на них.", + "machine_learning_ocr_max_resolution": "Максимальное разрешение", + "machine_learning_ocr_max_resolution_description": "Изображения выше этого разрешения будут уменьшены с сохранением соотношения сторон. Более высокие значения повышают точность распознавания, но требуют больше времени на обработку и используют больше памяти.", + "machine_learning_ocr_min_detection_score": "Порог обнаружения текста", + "machine_learning_ocr_min_detection_score_description": "Минимальный порог обнаружения текста (от 0 до 1). Более низкое значение позволит находить больше текста, но может привести к ложным срабатываниям.", + "machine_learning_ocr_min_recognition_score": "Порог распознавания текста", + "machine_learning_ocr_min_score_recognition_description": "Минимальный порог уверенности распознавания обнаруженного текста (от 0 до 1). Более низкое значение позволит распознать больше текста, но может привести к ложным срабатываниям.", + "machine_learning_ocr_model": "Модель для распознавания текста", + "machine_learning_ocr_model_description": "Серверные модели точнее мобильных, но требуют больше времени на обработку и используют больше памяти.", "machine_learning_settings": "Настройки машинного обучения", "machine_learning_settings_description": "Управление функциями и настройками машинного обучения (ML)", "machine_learning_smart_search": "Интеллектуальный поиск", @@ -209,6 +223,8 @@ "notification_email_ignore_certificate_errors_description": "Игнорировать ошибки проверки сертификата TLS (не рекомендуется)", "notification_email_password_description": "Пароль для аутентификации на сервере электронной почты", "notification_email_port_description": "Порт почтового сервера (например, 25, 465 или 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "Использовать SMTPS (SMTP через TLS)", "notification_email_sent_test_email_button": "Отправить проверочное письмо и сохранить", "notification_email_setting_description": "Настройки отправки уведомлений по электронной почте", "notification_email_test_email": "Отправить проверочное письмо", @@ -241,6 +257,7 @@ "oauth_storage_quota_default_description": "Квота в GiB, которая будет использоваться, если утверждение не задано.", "oauth_timeout": "Таймаут для запросов", "oauth_timeout_description": "Максимальное время, в течение которого ожидать ответа, в миллисекундах", + "ocr_job_description": "Использование машинного обучения для распознавания текста на изображениях", "password_enable_description": "Вход по электронной почте и паролю", "password_settings": "Настройки входа с паролем", "password_settings_description": "Управление настройками входа по паролю", @@ -275,7 +292,7 @@ "storage_template_hash_verification_enabled_description": "Включает проверку хеша, не отключайте её, если не уверены в последствиях", "storage_template_migration": "Применение шаблона хранилища", "storage_template_migration_description": "Применяет текущий {template} к ранее загруженным объектам", - "storage_template_migration_info": "Расширения файлов всегда будут сохраняться в нижнем регистре. Изменения в шаблоне будут применяться только к новым ресурсам. Чтобы применить шаблон к ранее загруженным ресурсам, запустите {job}.", + "storage_template_migration_info": "Расширения файлов всегда будут сохраняться в нижнем регистре. Изменения в шаблоне будут применяться только к новым объектам. Чтобы применить шаблон к ранее загруженным объектам, запустите {job}.", "storage_template_migration_job": "Задача по применению шаблона хранилища", "storage_template_more_details": "Для получения дополнительной информации об этой функции обратитесь к разделам документации Шаблон хранилища и Структура хранения файлов", "storage_template_onboarding_description_v2": "Если эта функция включена, она автоматически организует файлы на основе заданного пользователем шаблона. Для получения дополнительной информации обратитесь к документации.", @@ -301,7 +318,7 @@ "thumbnail_generation_job": "Создание миниатюр", "thumbnail_generation_job_description": "Создает большие, маленькие и размытые миниатюры для каждого файла и человека", "transcoding_acceleration_api": "API ускорителя", - "transcoding_acceleration_api_description": "API, который будет взаимодействовать с вашим устройством для ускорения транскодирования. Эта настройка является «наилучшим вариантом»: при сбое она будет возвращаться к программному транскодированию. VP9 может работать или не работать в зависимости от вашего оборудования.", + "transcoding_acceleration_api_description": "Выберите подходящий используемому оборудованию API для ускорения транскодирования. Выбранное значение является «наилучшим вариантом»: в случае проблем произойдет возврат на программное транскодирование. VP9 может работать или не работать в зависимости от используемого оборудования.", "transcoding_acceleration_nvenc": "NVENC (требуется графический процессор NVIDIA)", "transcoding_acceleration_qsv": "Quick Sync (требуется процессор Intel 7-го поколения или новее)", "transcoding_acceleration_rkmpp": "RKMPP (только для SOC Rockchip)", @@ -318,27 +335,27 @@ "transcoding_bitrate_description": "Видео с битрейтом выше максимального или в неподходящем формате может вызвать проблемы", "transcoding_codecs_learn_more": "Для изучения терминологии, используемой здесь, обратитесь к документации FFmpeg по кодекам H.264, HEVC и VP9.", "transcoding_constant_quality_mode": "Режим постоянного качества", - "transcoding_constant_quality_mode_description": "ICQ лучше, чем CQP, но некоторые устройства аппаратного ускорения не поддерживают этот режим. Установка этой опции будет отдавать предпочтение указанному режиму при использовании кодирования на основе качества. Игнорируется NVENC, поскольку не поддерживает ICQ.", + "transcoding_constant_quality_mode_description": "Режим ICQ лучше, чем CQP, но некоторые устройства аппаратного ускорения его не поддерживают. Установка этой опции будет отдавать предпочтение указанному режиму при использовании кодирования на основе качества. NVENC не поддерживает режим ICQ.", "transcoding_constant_rate_factor": "Коэффициент постоянной скорости (-crf)", "transcoding_constant_rate_factor_description": "Уровень качества видео. Типичные значения: 23 для H.264, 28 для HEVC, 31 для VP9 и 35 для AV1. Чем ниже, тем лучше, но при этом создаются файлы большего размера.", "transcoding_disabled_description": "Не перекодировать видео, это может привести к сбою воспроизведения на некоторых клиентах", "transcoding_encoding_options": "Параметры кодирования", "transcoding_encoding_options_description": "Установите кодеки, разрешение, качество и другие параметры для кодированного видео", "transcoding_hardware_acceleration": "Аппаратное ускорение", - "transcoding_hardware_acceleration_description": "Экспериментально: более быстрое транскодирование, но, возможна потеря качества при том же битрейте", + "transcoding_hardware_acceleration_description": "Экспериментально: более быстрое транскодирование, но с возможным ухудшением качества при том же битрейте", "transcoding_hardware_decoding": "Аппаратное декодирование", - "transcoding_hardware_decoding_setting_description": "Включает сквозное ускорение, а не только ускорение кодирования. Может работать не со всеми видео.", + "transcoding_hardware_decoding_setting_description": "Дополнительно ускоряет декодирование, а не только кодирование. Может работать не со всеми видео.", "transcoding_max_b_frames": "Максимально промежуточных кадров", "transcoding_max_b_frames_description": "Более высокие значения повышают эффективность сжатия, но замедляют кодирование. Может быть несовместимо с аппаратным ускорением на старых устройствах. 0 отключает B-кадры, а -1 устанавливает это значение автоматически.", "transcoding_max_bitrate": "Максимальный битрейт", - "transcoding_max_bitrate_description": "Установка максимального битрейта может сделать размер файла более предсказуемым при незначительном снижении качества. При 720p типичными значениями являются 2600 kbit/s для VP9 или HEVC или 4500 kbit/s для H.264. Отключено, если установлено значение 0.", + "transcoding_max_bitrate_description": "Ограничение битрейта может сделать размер файла более предсказуемым при незначительном снижении качества. При разрешении 720p типичными значениями являются 2600 кбит/с для кодеков VP9 или HEVC и 4500 кбит/с для H.264. Ограничение отключено, если задано значение 0. Когда единица измерения не указана, предполагается k (кбит/с); поэтому значения 5000, 5000k и 5M (для Мбит/с) эквивалентны.", "transcoding_max_keyframe_interval": "Максимальный интервал ключевых кадров", "transcoding_max_keyframe_interval_description": "Устанавливает максимальное расстояние между ключевыми кадрами. Более низкие значения ухудшают эффективность сжатия, но сокращают время поиска и могут улучшить качество в сценах с быстрым движением. 0 устанавливает это значение автоматически.", "transcoding_optimal_description": "Видео с разрешением выше целевого или не в принятом формате", "transcoding_policy": "Политика перекодировки", "transcoding_policy_description": "Установите, когда видео будет перекодировано", "transcoding_preferred_hardware_device": "Предпочитаемое аппаратное устройство", - "transcoding_preferred_hardware_device_description": "Применяется только к VAAPI и QSV. Устанавливает dri-узел, используемый для аппаратного перекодирования.", + "transcoding_preferred_hardware_device_description": "Применяется только к VAAPI и QSV. Устанавливает dri-узел, используемый для аппаратного транскодирования.", "transcoding_preset_preset": "Предустановка", "transcoding_preset_preset_description": "Скорость сжатия. Медленные настройки создают более маленькие файлы и повышают качество при достижении определенного битрейта. VP9 игнорирует скорости выше “faster”.", "transcoding_reference_frames": "Опорные кадры", @@ -348,8 +365,8 @@ "transcoding_settings_description": "Управляйте тем, какие видео нужно перекодировать и как их обрабатывать", "transcoding_target_resolution": "Целевое разрешение", "transcoding_target_resolution_description": "Более высокие разрешения позволяют сохранить больше деталей, но требуют больше времени для кодирования, имеют больший размер файлов и могут снизить скорость отклика приложения.", - "transcoding_temporal_aq": "Временной AQ", - "transcoding_temporal_aq_description": "Применяется только к NVENC. Повышает качество сцен с высокой детализацией и низким движением. Может быть несовместимо со старыми устройствами.", + "transcoding_temporal_aq": "Temporal Adaptive Quantization (временное адаптивное квантование)", + "transcoding_temporal_aq_description": "Применимо только к NVENC (NVIDIA Encoder). Технология повышает качество высокодетализированных нединамичных сцен. Может быть несовместима со старыми устройствами.", "transcoding_threads": "Потоки", "transcoding_threads_description": "Более высокие значения приводят к более быстрому кодированию, но оставляют серверу меньше места для обработки других задач во время активности. Это значение не должно превышать количество ядер процессора. Максимизирует использование, если установлено значение 0.", "transcoding_tone_mapping": "Отображение тонов", @@ -371,7 +388,7 @@ "user_cleanup_job": "Очистка пользователя", "user_delete_delay": "Аккаунт и файлы пользователя {user} будут отложены до окончательного удаления через {delay, plural, one {# день} few {# дня} many {# дней} other {# дня}}.", "user_delete_delay_settings": "Отложенное удаление", - "user_delete_delay_settings_description": "Срок в днях, по истечение которого происходит окончательное удаление учетной записи пользователя и его ресурсов. Задача по удалению пользователей выполняется в полночь. Изменения этой настройки будут учтены при следующем запуске задачи.", + "user_delete_delay_settings_description": "Срок в днях, по истечении которого происходит окончательное удаление учётной записи пользователя и всех его объектов. Задача по удалению пользователей выполняется в полночь. Изменение этой настройки будет учтено при следующем запуске задачи.", "user_delete_immediately": "Аккаунт и файлы пользователя {user} будут немедленно поставлены в очередь для окончательного удаления.", "user_delete_immediately_checkbox": "Поместить пользователя и его файлы в очередь для немедленного удаления", "user_details": "Данные пользователя", @@ -400,11 +417,11 @@ "advanced_settings_prefer_remote_subtitle": "Некоторые устройства очень медленно загружают локальные миниатюры. Активируйте эту настройку, чтобы изображения всегда загружались с сервера.", "advanced_settings_prefer_remote_title": "Предпочитать фото на сервере", "advanced_settings_proxy_headers_subtitle": "Определите заголовки прокси-сервера, которые Immich должен отправлять с каждым сетевым запросом", - "advanced_settings_proxy_headers_title": "Заголовки прокси", + "advanced_settings_proxy_headers_title": "[ЭКСПЕРИМЕНТАЛЬНО] Пользовательские заголовки прокси", "advanced_settings_readonly_mode_subtitle": "Включает режим, в котором можно только просматривать объекты. Функции выбора нескольких объектов, публикации, трансляции и удаления будут недоступны. Включить/отключить этот режим можно удерживая значок аватара пользователя на главном экране.", "advanced_settings_readonly_mode_title": "Режим «только просмотр»", "advanced_settings_self_signed_ssl_subtitle": "Пропускать проверку SSL-сертификата сервера. Требуется для самоподписанных сертификатов.", - "advanced_settings_self_signed_ssl_title": "Разрешить самоподписанные SSL-сертификаты", + "advanced_settings_self_signed_ssl_title": "[ЭКСПЕРИМЕНТАЛЬНО] Разрешить самоподписанные SSL-сертификаты", "advanced_settings_sync_remote_deletions_subtitle": "Автоматически удалять или восстанавливать объекты на этом устройстве, когда это действие выполняется через веб-интерфейс", "advanced_settings_sync_remote_deletions_title": "[ЭКСПЕРИМЕНТАЛЬНО] Синхронизация удаления объектов", "advanced_settings_tile_subtitle": "Расширенные настройки", @@ -425,7 +442,7 @@ "album_leave": "Покинуть альбом?", "album_leave_confirmation": "Вы уверены, что хотите покинуть {album}?", "album_name": "Название альбома", - "album_options": "Действия с альбомом", + "album_options": "Параметры альбома", "album_remove_user": "Удалить пользователя?", "album_remove_user_confirmation": "Вы уверены, что хотите удалить пользователя {user}?", "album_search_not_found": "Не найдено альбомов по вашему запросу", @@ -464,10 +481,14 @@ "api_key_description": "Это значение будет показано только один раз. Пожалуйста, убедитесь, что скопировали его перед закрытием окна.", "api_key_empty": "Имя API ключа не должно быть пустым", "api_keys": "Ключи API", + "app_architecture_variant": "Архитектура приложения", "app_bar_signout_dialog_content": "Вы уверены, что хотите выйти?", "app_bar_signout_dialog_ok": "Да", "app_bar_signout_dialog_title": "Выйти", + "app_download_links": "Ссылки на загрузку мобильного приложения", "app_settings": "Параметры приложения", + "app_stores": "Магазины приложений", + "app_update_available": "Доступна новая версия приложения", "appears_in": "Добавлено в", "apply_count": "Применить ({count, number})", "archive": "Архив", @@ -506,8 +527,8 @@ "asset_troubleshoot": "Данные для диагностики", "asset_uploaded": "Загружено", "asset_uploading": "Загрузка…", - "asset_viewer_settings_subtitle": "Настройка параметров отображения", - "asset_viewer_settings_title": "Просмотр изображений", + "asset_viewer_settings_subtitle": "Параметры отображения", + "asset_viewer_settings_title": "Просмотр объектов", "assets": "Объекты", "assets_added_count": "{count, plural, one {Добавлен # объект} many {Добавлено # объектов} other {Добавлено # объекта}}", "assets_added_to_album_count": "В альбом {count, plural, one {добавлен # объект} many {добавлено # объектов} other {добавлено # объекта}}", @@ -516,13 +537,13 @@ "assets_cannot_be_added_to_albums": "{count, plural, one {# объект} many {# объектов} other {# объекта}} не могут быть добавлены ни в один альбом", "assets_count": "{count, plural, one {# объект} many {# объектов} other {# объекта}}", "assets_deleted_permanently": "Объекты безвозвратно удалены ({count} шт.)", - "assets_deleted_permanently_from_server": "Объекты навсегда удалены с сервера Immich ({count} шт.)", + "assets_deleted_permanently_from_server": "Объекты безвозвратно удалены с сервера Immich ({count} шт.)", "assets_downloaded_failed": "{count, plural, one {Скачан # файл} many {Скачано # файлов} other {Скачано # файла}}, {error} - сбой", "assets_downloaded_successfully": "Успешно {count, plural, one {скачан # файл} many {скачано # файлов} other {скачано # файла}}", "assets_moved_to_trash_count": "{count, plural, one {# объект перемещён} many {# объектов перемещены} other {# объекта перемещены}} в корзину", - "assets_permanently_deleted_count": "{count, plural, one {# объект удалён} many {# объектов удалено} other {# объекта удалено}} навсегда", + "assets_permanently_deleted_count": "{count, plural, one {# объект безвозвратно удалён} many {# объектов безвозвратно удалено} other {# объекта безвозвратно удалены}}", "assets_removed_count": "{count, plural, one {# объект удалён} many {# объектов удалено} other {# объекта удалено}}", - "assets_removed_permanently_from_device": "Объекты навсегда удалены с вашего устройства ({count} шт.)", + "assets_removed_permanently_from_device": "Объекты безвозвратно удалены с вашего устройства ({count} шт.)", "assets_restore_confirmation": "Вы действительно хотите восстановить все объекты из корзины? Это действие нельзя отменить! Обратите внимание, объекты на устройстве не будут восстановлены таким способом.", "assets_restored_count": "{count, plural, one {# объект восстановлен} many {# объектов восстановлены} other {# объекта восстановлены}}", "assets_restored_successfully": "Объекты успешно восстановлены ({count} шт.)", @@ -540,7 +561,7 @@ "background_backup_running_error": "Выполняется фоновое резервное копирование, запуск вручную пока невозможен", "background_location_permission": "Доступ к местоположению в фоне", "background_location_permission_content": "Чтобы считывать имя Wi-Fi сети в фоне, приложению *всегда* необходим доступ к точному местоположению устройства", - "background_options": "Параметры фоновых задач", + "background_options": "Выполнение фоновых задач", "backup": "Резервное копирование", "backup_album_selection_page_albums_device": "Альбомы на устройстве ({count})", "backup_album_selection_page_albums_tap": "Нажмите, чтобы включить, дважды, чтобы исключить", @@ -551,6 +572,7 @@ "backup_albums_sync": "Синхронизация альбомов", "backup_all": "Все", "backup_background_service_backup_failed_message": "Не удалось выполнить резервное копирование. Повторная попытка…", + "backup_background_service_complete_notification": "Резервное копирование объектов завершено", "backup_background_service_connection_failed_message": "Не удалось подключиться к серверу. Повторная попытка…", "backup_background_service_current_upload_notification": "Загружается {filename}", "backup_background_service_default_notification": "Поиск новых объектов…", @@ -567,7 +589,7 @@ "backup_controller_page_background_battery_info_title": "Оптимизация батареи", "backup_controller_page_background_charging": "Только во время зарядки", "backup_controller_page_background_configure_error": "Не удалось настроить фоновую службу", - "backup_controller_page_background_delay": "Отложить резервное копирование новых объектов: {duration}", + "backup_controller_page_background_delay": "Задержка перед загрузкой новых объектов: {duration}", "backup_controller_page_background_description": "Включите фоновую службу для автоматического резервного копирования любых новых объектов без необходимости открывать приложение", "backup_controller_page_background_is_off": "Автоматическое резервное копирование в фоновом режиме отключено", "backup_controller_page_background_is_on": "Автоматическое резервное копирование в фоновом режиме включено", @@ -584,7 +606,7 @@ "backup_controller_page_filename": "Имя файла: {filename} [{size}]", "backup_controller_page_id": "ID: {id}", "backup_controller_page_info": "Информация о резервном копировании", - "backup_controller_page_none_selected": "Ничего не выбрано", + "backup_controller_page_none_selected": "Не выбрано", "backup_controller_page_remainder": "Осталось", "backup_controller_page_remainder_sub": "Фото и видео для загрузки", "backup_controller_page_server_storage": "Хранилище на сервере", @@ -598,6 +620,7 @@ "backup_controller_page_turn_on": "Включить", "backup_controller_page_uploading_file_info": "Информация о загружаемом файле", "backup_err_only_album": "Невозможно удалить единственный альбом", + "backup_error_sync_failed": "Сбой синхронизации. Невозможно выполнить резервное копирование.", "backup_info_card_assets": "объектов", "backup_manual_cancelled": "Отменено", "backup_manual_in_progress": "Загрузка в процессе. Попробуйте позже", @@ -618,7 +641,7 @@ "bugs_and_feature_requests": "Ошибки и запросы", "build": "Сборка", "build_image": "Версия сборки", - "bulk_delete_duplicates_confirmation": "Вы уверены, что хотите удалить {count, plural, one {# дублирующийся объект} many {# дублирующихся объектов} other {# дублирующихся объекта}}? Будет сохранён самый большой файл в каждой группе, а его дубликаты навсегда удалены. Это действие нельзя отменить!", + "bulk_delete_duplicates_confirmation": "Вы уверены, что хотите удалить {count, plural, one {# дублирующийся объект} many {# дублирующихся объектов} other {# дублирующихся объекта}}? Будет сохранён самый большой файл в каждой группе, а его дубликаты безвозвратно удалены. Это действие нельзя отменить!", "bulk_keep_duplicates_confirmation": "Вы уверены, что хотите оставить {count, plural, one {# дублирующийся объект} many {# дублирующихся объектов} other {# дублирующихся объекта}}? Это сохранит все дубликаты.", "bulk_trash_duplicates_confirmation": "Вы уверены, что хотите переместить в корзину {count, plural, one {# дублирующийся объект} many {# дублирующихся объектов} other {# дублирующихся объекта}}? Будет сохранён самый большой файл в каждой группе, а его дубликаты перемещены в корзину.", "buy": "Приобретение лицензии Immich", @@ -659,13 +682,15 @@ "change_password_description": "Это либо первый вход в систему, либо был сделан запрос на изменение пароля. Пожалуйста, введите новый пароль ниже.", "change_password_form_confirm_password": "Подтвердите пароль", "change_password_form_description": "Привет, {name}!\n\nЛибо ваш первый вход в систему, либо вы запросили смену пароля. Пожалуйста, введите новый пароль ниже.", + "change_password_form_log_out": "Завершить сеансы на других устройствах", + "change_password_form_log_out_description": "Рекомендуется сбросить авторизацию на всех других ранее подключенных устройствах", "change_password_form_new_password": "Новый пароль", "change_password_form_password_mismatch": "Пароли не совпадают", "change_password_form_reenter_new_password": "Повторно введите новый пароль", "change_pin_code": "Изменить PIN-код", "change_your_password": "Изменить свой пароль", "changed_visibility_successfully": "Видимость успешно изменена", - "charging": "На зарядке", + "charging": "При зарядке", "charging_requirement_mobile_backup": "Запускать резервное копирование только во время зарядки", "check_corrupt_asset_backup": "Проверка поврежденных резервных копий", "check_corrupt_asset_backup_button": "Проверить", @@ -685,8 +710,8 @@ "client_cert_import_success_msg": "Клиентский сертификат импортирован", "client_cert_invalid_msg": "Неверный файл сертификата или неверный пароль", "client_cert_remove_msg": "Клиентский сертификат удален", - "client_cert_subtitle": "Поддерживается только формат PKCS12 (.p12, .pfx). Импорт/удаление сертификата доступно только перед входом в систему", - "client_cert_title": "Клиентский SSL-сертификат", + "client_cert_subtitle": "Поддерживается только формат PKCS12 (.p12, .pfx). Импорт/удаление сертификата доступно только перед входом в систему.", + "client_cert_title": "[ЭКСПЕРИМЕНТАЛЬНО] Клиентский SSL-сертификат", "clockwise": "По часовой", "close": "Закрыть", "collapse": "Свернуть", @@ -698,13 +723,12 @@ "comments_and_likes": "Комментарии и отметки \"нравится\"", "comments_are_disabled": "Комментарии отключены", "common_create_new_album": "Создать новый альбом", - "common_server_error": "Пожалуйста, проверьте подключение к сети и убедитесь, что ваш сервер доступен, а версии приложения и сервера — совместимы.", "completed": "Завершено", "confirm": "Подтвердить", "confirm_admin_password": "Подтвердите пароль администратора", "confirm_delete_face": "Удалить лицо человека {name} из этого объекта?", - "confirm_delete_shared_link": "Вы уверены, что хотите удалить эту публичную ссылку?", - "confirm_keep_this_delete_others": "Все остальные объекты в группе будут удалены, кроме этого объекта. Вы уверены, что хотите продолжить?", + "confirm_delete_shared_link": "Вы действительно хотите удалить эту публичную ссылку?", + "confirm_keep_this_delete_others": "Все объекты в группе кроме текущего будут удалены. Продолжить?", "confirm_new_pin_code": "Подтвердите новый PIN-код", "confirm_password": "Подтвердите пароль", "confirm_tag_face": "Вы хотите отметить этого человека как {name}?", @@ -718,29 +742,30 @@ "control_bottom_app_bar_delete_from_immich": "Удалить из Immich", "control_bottom_app_bar_delete_from_local": "Удалить с устройства", "control_bottom_app_bar_edit_location": "Изменить место", - "control_bottom_app_bar_edit_time": "Изменить дату и время", - "control_bottom_app_bar_share_link": "Поделиться ссылкой", + "control_bottom_app_bar_edit_time": "Изменить дату", + "control_bottom_app_bar_share_link": "Создать ссылку", "control_bottom_app_bar_share_to": "Поделиться с", "control_bottom_app_bar_trash_from_immich": "В корзину", "copied_image_to_clipboard": "Изображение скопировано в буфер обмена.", "copied_to_clipboard": "Скопировано в буфер обмена!", - "copy_error": "Ошибка копирования", + "copy_error": "Скопировать ошибку", "copy_file_path": "Копировать путь к файлу", "copy_image": "Копировать", "copy_link": "Копировать ссылку", "copy_link_to_clipboard": "Скопировать ссылку в буфер обмена", "copy_password": "Скопировать пароль", - "copy_to_clipboard": "Скопировать настройки в буфер обмена", + "copy_to_clipboard": "Скопировать в буфер обмена", "country": "Страна", "cover": "Обложка", "covers": "Обложки", "create": "Создать", "create_album": "Создать альбом", "create_album_page_untitled": "Без названия", + "create_api_key": "Создать API ключ", "create_library": "Создать библиотеку", "create_link": "Создать ссылку", "create_link_to_share": "Создать ссылку общего доступа", - "create_link_to_share_description": "Разрешить всем, у кого есть ссылка, просмотреть выбранные фотографии", + "create_link_to_share_description": "Разрешить всем, у кого есть ссылка, просматривать выбранные фотографии", "create_new": "СОЗДАТЬ НОВЫЙ", "create_new_person": "Добавить нового человека", "create_new_person_hint": "Назначить выбранные объекты на нового человека", @@ -765,9 +790,10 @@ "daily_title_text_date": "E, MMM dd", "daily_title_text_date_year": "E, MMM dd, yyyy", "dark": "Тёмная", - "dark_theme": "Тёмная тема", + "dark_theme": "Включить/выключить тёмную тему", + "date": "Дата", "date_after": "Дата после", - "date_and_time": "Дата и Время", + "date_and_time": "Дата и время", "date_before": "Дата до", "date_format": "E, LLL d, y • h:mm a", "date_of_birth_saved": "Дата рождения успешно сохранена", @@ -788,11 +814,11 @@ "delete_api_key_prompt": "Вы действительно хотите удалить этот API ключ?", "delete_dialog_alert": "Эти элементы будут безвозвратно удалены с сервера, а также с вашего устройства", "delete_dialog_alert_local": "Эти объекты будут безвозвратно удалены с вашего устройства, но по-прежнему будут доступны на сервере Immich", - "delete_dialog_alert_local_non_backed_up": "Резервные копии некоторых объектов не были загружены в Immich и будут безвозвратно удалены с вашего устройства", + "delete_dialog_alert_local_non_backed_up": "Некоторые объекты не были загружены в Immich и будут безвозвратно удалены с вашего устройства", "delete_dialog_alert_remote": "Эти объекты будут безвозвратно удалены с сервера Immich", "delete_dialog_ok_force": "Все равно удалить", "delete_dialog_title": "Удалить навсегда", - "delete_duplicates_confirmation": "Вы уверены, что хотите навсегда удалить эти дубликаты?", + "delete_duplicates_confirmation": "Вы действительно хотите безвозвратно удалить эти дубликаты?", "delete_face": "Удалить лицо", "delete_key": "Удалить ключ", "delete_library": "Удалить библиотеку", @@ -802,7 +828,7 @@ "delete_local_dialog_ok_force": "Все равно удалить", "delete_others": "Удалить остальные", "delete_permanently": "Удалить навсегда", - "delete_permanently_action_prompt": "Объекты удалены навсегда ({count} шт.)", + "delete_permanently_action_prompt": "Объекты удалены безвозвратно ({count} шт.)", "delete_shared_link": "Удалить публичную ссылку", "delete_shared_link_dialog_title": "Удалить публичную ссылку", "delete_tag": "Удалить тег", @@ -818,7 +844,7 @@ "direction": "Направление", "disabled": "Отключено", "disallow_edits": "Запретить редактирование", - "discord": "Обсуждение в Discord", + "discord": "Discord", "discover": "Обнаружить", "discovered_devices": "Обнаруженные устройства", "dismiss_all_errors": "Сбросить все ошибки", @@ -827,7 +853,7 @@ "display_order": "Порядок отображения", "display_original_photos": "Отображение оригинальных фотографий", "display_original_photos_setting_description": "Открывать при просмотре оригинал фотографии вместо миниатюры, если исходный формат поддерживается браузером. Возможно снижение скорости отображения фотографий.", - "do_not_show_again": "Не показывать это сообщение в дальнейшем", + "do_not_show_again": "Больше не показывать это сообщение", "documentation": "Документация", "done": "Готово", "download": "Скачать", @@ -880,7 +906,6 @@ "edit_tag": "Изменить тег", "edit_title": "Изменить заголовок", "edit_user": "Изменить пользователя", - "edited": "Отредактировано", "editor": "Редактор", "editor_close_without_save_prompt": "Изменения не будут сохранены", "editor_close_without_save_title": "Закрыть редактор?", @@ -890,7 +915,7 @@ "email_notifications": "Уведомления по электронной почте", "empty_folder": "Пустая папка", "empty_trash": "Очистить корзину", - "empty_trash_confirmation": "Вы действительно хотите очистить корзину? Все объекты в ней будут навсегда удалены из Immich.\nВы не сможете отменить это действие!", + "empty_trash_confirmation": "Вы действительно хотите очистить корзину? Все объекты в ней будут безвозвратно удалены\nВы не сможете отменить это действие!", "enable": "Включить", "enable_backup": "Активировать", "enable_biometric_auth_description": "Введите свой PIN-код для включения биометрической аутентификации", @@ -911,22 +936,22 @@ "error_title": "Ошибка - Что-то пошло не так", "errors": { "cannot_navigate_next_asset": "Не удалось перейти к следующему объекту", - "cannot_navigate_previous_asset": "Не удалось перейти к предыдущему ресурсу", + "cannot_navigate_previous_asset": "Не удалось перейти к предыдущему объекту", "cant_apply_changes": "Не удается применить изменения", "cant_change_activity": "Не удается {enabled, select, true {отключить} other {включить}} активность", - "cant_change_asset_favorite": "Не удалось изменить статус \"избранное\" для ресурса", + "cant_change_asset_favorite": "Не удалось изменить статус \"Избранное\" для объекта", "cant_change_metadata_assets_count": "Не удалось изменить метаданные у {count, plural, one {# объекта} other {# объектов}}", "cant_get_faces": "Не удается получить лица", "cant_get_number_of_comments": "Не удается получить количество комментариев", "cant_search_people": "Не удается выполнить поиск людей", "cant_search_places": "Не удается выполнить поиск мест", - "error_adding_assets_to_album": "Ошибка при добавлении ресурсов в альбом", + "error_adding_assets_to_album": "Ошибка при добавлении объектов в альбом", "error_adding_users_to_album": "Ошибка при добавлении пользователей в альбом", "error_deleting_shared_user": "Ошибка при удалении пользователя с общим доступом", "error_downloading": "Ошибка при загрузке {filename}", "error_hiding_buy_button": "Ошибка скрытия кнопки", - "error_removing_assets_from_album": "Ошибка при удалении ресурсов из альбома, проверьте консоль для получения дополнительной информации", - "error_selecting_all_assets": "Ошибка при выборе всех ресурсов", + "error_removing_assets_from_album": "Ошибка при удалении объектов из альбома, проверьте консоль для получения дополнительной информации", + "error_selecting_all_assets": "Ошибка при выборе всех объектов", "exclusion_pattern_already_exists": "Такая модель исключения уже существует.", "failed_to_create_album": "Не удалось создать альбом", "failed_to_create_shared_link": "Не удалось создать публичную ссылку", @@ -1036,6 +1061,7 @@ "exif_bottom_sheet_description_error": "Не удалось обновить описание", "exif_bottom_sheet_details": "ПОДРОБНОСТИ", "exif_bottom_sheet_location": "МЕСТО", + "exif_bottom_sheet_no_description": "Нет описания", "exif_bottom_sheet_people": "ЛЮДИ", "exif_bottom_sheet_person_add_person": "Добавить имя", "exit_slideshow": "Выйти из слайд-шоу", @@ -1074,6 +1100,7 @@ "features_setting_description": "Управление дополнительными возможностями приложения", "file_name": "Имя файла", "file_name_or_extension": "Имя файла или расширение", + "file_size": "Размер файла", "filename": "Имя файла", "filetype": "Тип файла", "filter": "Фильтр", @@ -1117,7 +1144,6 @@ "header_settings_field_validator_msg": "Значение не может быть пустым", "header_settings_header_name_input": "Имя заголовка", "header_settings_header_value_input": "Значение заголовка", - "headers_settings_tile_subtitle": "Определите заголовки прокси, которые приложение должно отправлять с каждым сетевым запросом", "headers_settings_tile_title": "Пользовательские заголовки прокси", "hi_user": "Привет {name} ({email})", "hide_all_people": "Скрыть всех", @@ -1238,6 +1264,7 @@ "local_media_summary": "Информация об объекте на устройстве", "local_network": "Локальная сеть", "local_network_sheet_info": "Приложение будет подключаться к серверу по этому адресу, когда устройство подключено к указанной Wi-Fi сети", + "location": "Место", "location_permission": "Доступ к местоположению", "location_permission_content": "Чтобы использовать функцию автоматического переключения, Immich необходимо разрешение на точное определение местоположения, чтобы оно могло считывать название текущей Wi-Fi сети", "location_picker_choose_on_map": "Выбрать на карте", @@ -1317,9 +1344,9 @@ "map_settings_only_show_favorites": "Показать только избранное", "map_settings_theme_settings": "Цвет карты", "map_zoom_to_see_photos": "Уменьшение масштаба для просмотра фотографий", - "mark_all_as_read": "Отметить все как прочитанные", + "mark_all_as_read": "Прочитано", "mark_as_read": "Отметить как прочитанное", - "marked_all_as_read": "Отмечены как прочитанные", + "marked_all_as_read": "Все уведомления отмечены как прочитанные", "matches": "Совпадения", "matching_assets": "Соответствующие объекты", "media_type": "Тип медиа", @@ -1342,14 +1369,16 @@ "minute": "Минута", "minutes": "Минуты", "missing": "Отсутствующие", + "mobile_app": "Мобильное приложение", + "mobile_app_download_onboarding_note": "Загрузите мобильное приложение Immich любым из следующих способов", "model": "Модель", "month": "Месяц", "monthly_title_text_date_format": "MMMM y", "more": "Дополнительные действия", "move": "Переместить", - "move_off_locked_folder": "Переместить из личной папки", + "move_off_locked_folder": "Убрать из личной папки", "move_to_lock_folder_action_prompt": "Объекты добавлены в личную папку ({count} шт.)", - "move_to_locked_folder": "Переместить в личную папку", + "move_to_locked_folder": "В личную папку", "move_to_locked_folder_confirmation": "Эти фото и видео будут удалены из всех альбомов и будут доступны только в личной папке", "moved_to_archive": "{count, plural, one {# объект перемещён} many {# объектов перемещены} other {# объекта перемещены}} в архив", "moved_to_library": "{count, plural, one {# объект перемещён} many {# объектов перемещены} other {# объекта перемещены}} в библиотеку", @@ -1360,6 +1389,8 @@ "my_albums": "Мои альбомы", "name": "Имя", "name_or_nickname": "Имя или ник", + "navigate": "Перейти", + "navigate_to_time": "Перейти к дате", "network_requirement_photos_upload": "Использовать мобильный интернет для загрузки фото", "network_requirement_videos_upload": "Использовать мобильный интернет для загрузки видео", "network_requirements": "Требования к сети", @@ -1369,6 +1400,7 @@ "never": "никогда", "new_album": "Новый альбом", "new_api_key": "Новый API ключ", + "new_date_range": "Новый диапазон дат", "new_password": "Новый пароль", "new_person": "Новый человек", "new_pin_code": "Новый PIN-код", @@ -1408,7 +1440,7 @@ "not_available": "Нет данных", "not_in_any_album": "Ни в одном альбоме", "not_selected": "Не выбрано", - "note_apply_storage_label_to_previously_uploaded assets": "Примечание: Чтобы применить метку хранилища к ранее загруженным ресурсам, запустите", + "note_apply_storage_label_to_previously_uploaded assets": "Примечание: Чтобы применить метку хранилища к ранее загруженным объектам, запустите", "notes": "Примечание", "nothing_here_yet": "Здесь пока ничего нет", "notification_permission_dialog_content": "Чтобы включить уведомления, перейдите в «Настройки» и выберите «Разрешить».", @@ -1419,6 +1451,9 @@ "notifications": "Уведомления", "notifications_setting_description": "Управление уведомлениями", "oauth": "OAuth", + "obtainium_configurator": "Настройка Obtainium", + "obtainium_configurator_instructions": "Для установки и обновления Android приложения Immich напрямую из источников на GitHub (минуя магазины приложений) можно использовать Obtainium. Создайте новый API ключ и укажите архитектуру приложения для формирования ссылки для Obtainium.", + "ocr": "OCR", "official_immich_resources": "Официальные ресурсы Immich", "offline": "Недоступен", "offset": "Смещение", @@ -1487,9 +1522,9 @@ "permanent_deletion_warning_setting_description": "Предупреждать перед безвозвратным удалением объектов", "permanently_delete": "Удалить навсегда", "permanently_delete_assets_count": "Удалить {count, plural, one {объект} other {объекты}} навсегда", - "permanently_delete_assets_prompt": "Вы действительно хотите навсегда удалить {count, plural, =1 {этот объект} one {этот # объект} many {эти # объектов} other {эти # объекта}}? {count, plural, =1 {Объект также будет удален} other {Объекты также будут удалены}} из всех альбомов.", + "permanently_delete_assets_prompt": "Вы действительно хотите безвозвратно удалить {count, plural, =1 {этот объект} one {этот # объект} many {эти # объектов} other {эти # объекта}}? {count, plural, =1 {Объект также будет удален} other {Объекты также будут удалены}} из всех альбомов.", "permanently_deleted_asset": "Удалить навсегда", - "permanently_deleted_assets_count": "{count, plural, one {# объект удалён} many {# объектов удалено} other {# объекта удалено}} навсегда", + "permanently_deleted_assets_count": "{count, plural, one {# объект безвозвратно удалён} many {# объектов безвозвратно удалено} other {# объекта безвозвратно удалены}}", "permission": "Разрешения", "permission_empty": "Разрешения не могут быть пустыми", "permission_onboarding_back": "Назад", @@ -1523,6 +1558,9 @@ "play_memories": "Воспроизвести воспоминания", "play_motion_photo": "Воспроизводить движущиеся фото", "play_or_pause_video": "Воспроизведение или приостановка видео", + "play_original_video": "Воспроизвести оригинальное видео", + "play_original_video_setting_description": "Проигрывать оригинальные видео вместо перекодированных. Если исходный формат не поддерживается, видео может воспроизводиться неправильно.", + "play_transcoded_video": "Воспроизвести перекодированное видео", "please_auth_to_access": "Пожалуйста, авторизуйтесь", "port": "Порт", "preferences_settings_subtitle": "Настройка внешнего вида", @@ -1539,14 +1577,10 @@ "primary": "Главное", "privacy": "Конфиденциальность", "profile": "Профиль", - "profile_drawer_app_logs": "Журнал", - "profile_drawer_client_out_of_date_major": "Версия мобильного приложения устарела. Пожалуйста, обновите его.", - "profile_drawer_client_out_of_date_minor": "Версия мобильного приложения устарела. Пожалуйста, обновите его.", + "profile_drawer_app_logs": "Журнал событий", "profile_drawer_client_server_up_to_date": "Клиент и сервер обновлены", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Включён режим «только просмотр». Удерживайте значок аватара пользователя для отключения.", - "profile_drawer_server_out_of_date_major": "Версия сервера устарела. Пожалуйста, обновите его.", - "profile_drawer_server_out_of_date_minor": "Версия сервера устарела. Пожалуйста, обновите его.", "profile_image_of_user": "Изображение профиля {user}", "profile_picture_set": "Фото профиля установлено.", "public_album": "Общий альбом", @@ -1590,14 +1624,14 @@ "rating_count": "{count, plural, one {# звезда} many {# звезд} other {# звезды}}", "rating_description": "Система оценки объектов в панели информации", "reaction_options": "Действия с отметкой", - "read_changelog": "Прочитать список изменений", + "read_changelog": "История релизов", "readonly_mode_disabled": "Режим «только просмотр» отключён", "readonly_mode_enabled": "Режим «только просмотр» включён", "ready_for_upload": "Готово к загрузке", "reassign": "Переназначить", "reassigned_assets_to_existing_person": "Лица на {count, plural, one {# объекте} other {# объектах}} переназначены на {name, select, null {другого человека} other {человека с именем {name}}}", "reassigned_assets_to_new_person": "Лица на {count, plural, one {# объекте} other {# объектах}} переназначены на нового человека", - "reassing_hint": "Назначить выбранные ресурсы указанному человеку", + "reassing_hint": "Назначить выбранные объекты указанному человеку", "recent": "Недавние", "recent-albums": "Недавние альбомы", "recent_searches": "Недавние поисковые запросы", @@ -1630,14 +1664,14 @@ "remove_from_favorites": "Удалить из избранного", "remove_from_lock_folder_action_prompt": "Объекты удалены из личной папки ({count} шт.)", "remove_from_locked_folder": "Удалить из личной папки", - "remove_from_locked_folder_confirmation": "Вы действительно хотите переместить эти фото и видео из личной папки? Они станут доступны в вашей библиотеке.", + "remove_from_locked_folder_confirmation": "Вы хотите убрать выделенные объекты из личной папки? Они снова станут доступны в вашей библиотеке.", "remove_from_shared_link": "Удалить из публичной ссылки", "remove_memory": "Удалить воспоминание", "remove_photo_from_memory": "Удалить фото из воспоминания", "remove_tag": "Удалить тег", "remove_url": "Удалить URL", "remove_user": "Удалить пользователя", - "removed_api_key": "Удалён API ключ {name}", + "removed_api_key": "API ключ \"{name}\" удалён", "removed_from_archive": "Удален из архива", "removed_from_favorites": "Удалено из избранного", "removed_from_favorites_count": "{count, plural, one {# объект удалён} many {# объектов удалено} other {# объекта удалено}} из избранного", @@ -1663,6 +1697,7 @@ "reset_sqlite_confirmation": "Вы действительно хотите очистить базу данных SQLite? Вам потребуется выйти из приложения и снова войти для повторной синхронизации данных.", "reset_sqlite_success": "База данных SQLite успешно очищена", "reset_to_default": "Восстановление значений по умолчанию", + "resolution": "Разрешение", "resolve_duplicates": "Устранить дубликаты", "resolved_all_duplicates": "Все дубликаты устранены", "restore": "Восстановить", @@ -1681,6 +1716,7 @@ "running": "Выполняется", "save": "Сохранить", "save_to_gallery": "Сохранить в галерею", + "saved": "Сохранено", "saved_api_key": "API ключ изменён", "saved_profile": "Профиль сохранён", "saved_settings": "Настройки сохранены", @@ -1695,8 +1731,11 @@ "search_by_context": "Поиск по контексту", "search_by_description": "Поиск по описанию", "search_by_description_example": "День пешего туризма в Сапе", - "search_by_filename": "Искать по имени файла или расширению", + "search_by_filename": "Поиск по имени файла или расширению", "search_by_filename_example": "например, IMG_1234.JPG или PNG", + "search_by_ocr": "Поиск текста", + "search_by_ocr_example": "Латте", + "search_camera_lens_model": "Поиск модели объектива...", "search_camera_make": "Поиск производителя камеры...", "search_camera_model": "Поиск модели камеры...", "search_city": "Поиск города...", @@ -1713,6 +1752,7 @@ "search_filter_location_title": "Выберите место", "search_filter_media_type": "Тип файла", "search_filter_media_type_title": "Выберите тип медиа", + "search_filter_ocr": "Поиск текста", "search_filter_people_title": "Выберите людей", "search_for": "Поиск по", "search_for_existing_person": "Поиск существующего человека", @@ -1775,9 +1815,10 @@ "server_online": "Сервер в сети", "server_privacy": "Конфиденциальность сервера", "server_stats": "Статистика сервера", + "server_update_available": "Доступна новая версия сервера", "server_version": "Версия сервера", "set": "Установить", - "set_as_album_cover": "Установить в качестве обложки альбома", + "set_as_album_cover": "Установить обложкой альбома", "set_as_featured_photo": "Установить как основное фото", "set_as_profile_picture": "Установить как фото профиля", "set_date_of_birth": "Установить дату рождения", @@ -1791,7 +1832,7 @@ "setting_image_viewer_preview_title": "Загружать уменьшенное изображение", "setting_image_viewer_title": "Изображения", "setting_languages_apply": "Применить", - "setting_languages_subtitle": "Изменить язык приложения", + "setting_languages_subtitle": "Изменение языка приложения", "setting_notifications_notify_failures_grace_period": "Уведомлять об ошибках фонового резервного копирования: {duration}", "setting_notifications_notify_hours": "{count} ч.", "setting_notifications_notify_immediately": "немедленно", @@ -1800,9 +1841,11 @@ "setting_notifications_notify_seconds": "{count} сек.", "setting_notifications_single_progress_subtitle": "Подробная информация о ходе загрузки для каждого объекта", "setting_notifications_single_progress_title": "Показать ход выполнения фонового резервного копирования", - "setting_notifications_subtitle": "Настройка параметров уведомлений", + "setting_notifications_subtitle": "Параметры уведомлений", "setting_notifications_total_progress_subtitle": "Общий прогресс загрузки (выполнено/всего объектов)", "setting_notifications_total_progress_title": "Показать общий прогресс фонового резервного копирования", + "setting_video_viewer_auto_play_subtitle": "Автоматически запускать воспроизведение видео при открытии", + "setting_video_viewer_auto_play_title": "Автовоспроизведение видео", "setting_video_viewer_looping_title": "Циклическое воспроизведение", "setting_video_viewer_original_video_subtitle": "При воспроизведении видео с сервера загружать оригинал, даже если доступна транскодированная версия. Может привести к буферизации. Видео, доступные локально, всегда воспроизводятся в исходном качестве.", "setting_video_viewer_original_video_title": "Только оригинальное видео", @@ -1815,7 +1858,7 @@ "share_add_photos": "Добавить фото", "share_assets_selected": "{count} выбрано", "share_dialog_preparing": "Подготовка...", - "share_link": "Поделиться ссылкой", + "share_link": "Создать ссылку", "shared": "Общиe", "shared_album_activities_input_disable": "Комментарии отключены", "shared_album_activity_remove_content": "Удалить сообщение?", @@ -1833,17 +1876,17 @@ "shared_link_clipboard_copied_massage": "Скопировано в буфер обмена", "shared_link_clipboard_text": "Ссылка: {link}\nПароль: {password}", "shared_link_create_error": "Ошибка при создании публичной ссылки", - "shared_link_custom_url_description": "Доступ к этой общей ссылке с помощью заданного пользователем URL-адреса", - "shared_link_edit_description_hint": "Введите описание публичного доступа", + "shared_link_custom_url_description": "Пользовательский URL-адрес общего доступа", + "shared_link_edit_description_hint": "Введите описание", "shared_link_edit_expire_after_option_day": "1 день", "shared_link_edit_expire_after_option_days": "{count} дней", "shared_link_edit_expire_after_option_hour": "1 час", "shared_link_edit_expire_after_option_hours": "{count} часов", "shared_link_edit_expire_after_option_minute": "1 минуту", "shared_link_edit_expire_after_option_minutes": "{count} минут", - "shared_link_edit_expire_after_option_months": "{count} месяцев", + "shared_link_edit_expire_after_option_months": "{count} месяца", "shared_link_edit_expire_after_option_year": "{count} лет", - "shared_link_edit_password_hint": "Введите пароль для публичного доступа", + "shared_link_edit_password_hint": "Защитите доступ паролем", "shared_link_edit_submit_button": "Обновить ссылку", "shared_link_error_server_url_fetch": "Невозможно запросить URL с сервера", "shared_link_expires_day": "Истечёт через {count} день", @@ -1859,7 +1902,7 @@ "shared_link_info_chip_metadata": "EXIF", "shared_link_manage_links": "Управление публичными ссылками", "shared_link_options": "Действия со ссылкой", - "shared_link_password_description": "Требовать пароль для доступа к этой общей ссылке", + "shared_link_password_description": "Требовать пароль для доступа к объектам", "shared_links": "Публичные ссылки", "shared_links_description": "Делитесь фотографиями и видео по ссылке", "shared_photos_and_videos_count": "{assetCount, plural, other {# фото и видео.}}", @@ -1874,7 +1917,7 @@ "sharing_silver_appbar_create_shared_album": "Создать общий альбом", "sharing_silver_appbar_share_partner": "Поделиться с партнёром", "shift_to_permanent_delete": "нажмите ⇧ чтобы удалить объект навсегда", - "show_album_options": "Показать параметры альбома", + "show_album_options": "Действия с альбомом", "show_albums": "Показать альбомы", "show_all_people": "Показать всех людей", "show_and_hide_people": "Показать и скрыть людей", @@ -1967,7 +2010,7 @@ "theme": "Тема", "theme_selection": "Выбор темы", "theme_selection_description": "Автоматически устанавливать светлую или тёмную тему в зависимости от настроек вашего браузера", - "theme_setting_asset_list_storage_indicator_title": "Показать индикатор хранилища на плитках объектов", + "theme_setting_asset_list_storage_indicator_title": "Отображать индикатор хранилища на плитках объектов", "theme_setting_asset_list_tiles_per_row_title": "Количество объектов в строке ({count})", "theme_setting_colorful_interface_subtitle": "Добавить оттенок к фону.", "theme_setting_colorful_interface_title": "Цвет фона", @@ -1982,6 +2025,7 @@ "theme_setting_three_stage_loading_title": "Включить трехэтапную загрузку", "they_will_be_merged_together": "Они будут объединены вместе", "third_party_resources": "Сторонние ресурсы", + "time": "Время", "time_based_memories": "Воспоминания, основанные на времени", "timeline": "Временная шкала", "timezone": "Часовой пояс", @@ -2004,8 +2048,8 @@ "trash_emptied": "Корзина очищена", "trash_no_results_message": "Здесь будут отображаться удалённые фотографии и видео.", "trash_page_delete_all": "Удалить все", - "trash_page_empty_trash_dialog_content": "Очистить корзину? Объекты в ней будут навсегда удалены из Immich.", - "trash_page_info": "Объекты в корзине будут окончательно удалены через {days} дней", + "trash_page_empty_trash_dialog_content": "Очистить корзину? Объекты в ней будут безвозвратно удалены из Immich.", + "trash_page_info": "Объекты в корзине будут безвозвратно удалены через {days} дней", "trash_page_no_assets": "Корзина пуста", "trash_page_restore_all": "Восстановить все", "trash_page_select_assets_btn": "Выбранные объекты", @@ -2014,6 +2058,7 @@ "troubleshoot": "Диагностика", "type": "Тип", "unable_to_change_pin_code": "Ошибка при изменении PIN-кода", + "unable_to_check_version": "Не удалось проверить наличие обновлений", "unable_to_setup_pin_code": "Ошибка при создании PIN-кода", "unarchive": "Восстановить", "unarchive_action_prompt": "Объекты удалены из архива ({count} шт.)", @@ -2109,7 +2154,7 @@ "view_next_asset": "Показать следующий объект", "view_previous_asset": "Показать предыдущий объект", "view_qr_code": "Посмотреть QR код", - "view_similar_photos": "Найти похожие фотографии", + "view_similar_photos": "Найти похожие", "view_stack": "Показать группу", "view_user": "Просмотреть пользователя", "viewer_remove_from_stack": "Убрать из группы", diff --git a/i18n/sk.json b/i18n/sk.json index 35975823e7..010ee89b3a 100644 --- a/i18n/sk.json +++ b/i18n/sk.json @@ -28,10 +28,12 @@ "add_to_album": "Pridať do albumu", "add_to_album_bottom_sheet_added": "Pridané do {album}", "add_to_album_bottom_sheet_already_exists": "Už je v {album}", + "add_to_album_bottom_sheet_some_local_assets": "Niektoré lokálne súbory nebolo možné pridať do albumu", "add_to_album_toggle": "Prepnúť výber pre {album}", "add_to_albums": "Pridať do albumov", "add_to_albums_count": "Pridať do albumov ({count})", "add_to_shared_album": "Pridať do zdieľaného albumu", + "add_upload_to_stack": "Nahrať a pridať do zoskupených", "add_url": "Pridať URL", "added_to_archive": "Pridané do archívu", "added_to_favorites": "Pridané do obľúbených", @@ -118,7 +120,7 @@ "library_settings_description": "Spravovať nastavenia externej knižnice", "library_tasks_description": "Vyhľadajte nové alebo zmenené médiá v externých knižniciach", "library_watching_enable_description": "Sledovať externé knižnice pre zmeny v súboroch", - "library_watching_settings": "Sledovanie knižnice (EXPERIMENTÁLNE)", + "library_watching_settings": "Sledovanie knižnice [EXPERIMENTÁLNE]", "library_watching_settings_description": "Automaticky sledovať zmenené súbory", "logging_enable_description": "Povoliť ukladanie záznamov", "logging_level_description": "Ak je povolené, akú úroveň záznamov použiť.", @@ -152,6 +154,18 @@ "machine_learning_min_detection_score_description": "Minimálne skóre dôveryhodnosti pre detekciu tváre v rozsahu od 0 do 1. Nižšie hodnoty odhalia viac tvárí, ale môžu viesť k falošným pozitivním výsledkom.", "machine_learning_min_recognized_faces": "Minimum rozpoznaných tvárí", "machine_learning_min_recognized_faces_description": "Minimálny počet rozpoznaných tvárí potrebných na vytvorenie osoby. Zvýšením tejto hodnoty sa zvyšuje presnosť rozpoznávania tvárí, ale tiež sa zvyšuje pravdepodobnosť, že tvár nebude priradená osobe.", + "machine_learning_ocr": "OCR", + "machine_learning_ocr_description": "Použiť strojové učenie na rozpoznávanie textu na obrázkoch", + "machine_learning_ocr_enabled": "Povoliť OCR", + "machine_learning_ocr_enabled_description": "Ak je táto funkcia vypnutá, obrázky nebudú podliehať rozpoznávaniu textu.", + "machine_learning_ocr_max_resolution": "Maximálne rozlíšenie", + "machine_learning_ocr_max_resolution_description": "Náhľady presahujúce toto rozlíšenie budú zmenené tak, aby sa zachoval pomer strán. Vyššie hodnoty sú presnejšie, ale ich spracovanie trvá dlhšie a využívajú viac pamäte.", + "machine_learning_ocr_min_detection_score": "Minimálne skóre detekcie", + "machine_learning_ocr_min_detection_score_description": "Minimálne skóre spoľahlivosti pre detekciu textu od 0 do 1. Nižšie hodnoty detekujú viac textu, ale môžu viesť k falošným pozitívam.", + "machine_learning_ocr_min_recognition_score": "Minimálne skóre rozpoznania", + "machine_learning_ocr_min_score_recognition_description": "Minimálne skóre spoľahlivosti pre rozpoznanie detegovaného textu v rozmedzí od 0 po 1. Nižšie hodnoty rozpoznajú viac textu, ale môžu viesť k falošným pozitívam.", + "machine_learning_ocr_model": "OCR model", + "machine_learning_ocr_model_description": "Serverové modely sú presnejšie ako mobilné modely, ale ich spracovanie trvá dlhšie a využívajú viac pamäte.", "machine_learning_settings": "Strojové učenie", "machine_learning_settings_description": "Spravovať funkcie a nastavenia strojového učenia", "machine_learning_smart_search": "Inteligentné vyhľadávanie", @@ -209,6 +223,8 @@ "notification_email_ignore_certificate_errors_description": "Ignorovať chyby pri overení TLS certifikátu (neodporúča sa)", "notification_email_password_description": "Heslo pre autentifikáciu s emailovým serverom", "notification_email_port_description": "Porty e-mailového servera (napr. 25, 465, alebo 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "Použiť SMTPS (SMTP cez TLS)", "notification_email_sent_test_email_button": "Odoslať testovací e-mail a uložiť", "notification_email_setting_description": "Nastavenie pre odosielanie e-mailových upozornení", "notification_email_test_email": "Odoslať testovací email", @@ -241,6 +257,7 @@ "oauth_storage_quota_default_description": "Kvóta v GiB, ktorá sa použije, ak nie je poskytnutá žiadna požiadavka.", "oauth_timeout": "Časový limit požiadavky", "oauth_timeout_description": "Časový limit pre požiadavky v milisekundách", + "ocr_job_description": "Použiť strojové učenie na rozpoznávanie textu na obrázkoch", "password_enable_description": "Prihlásiť sa pomocou emailu a hesla", "password_settings": "Prihlásenie cez heslo", "password_settings_description": "Spravovať nastavenia prihlásenia cez heslo", @@ -331,7 +348,7 @@ "transcoding_max_b_frames": "Maximálny počet B-snímkov", "transcoding_max_b_frames_description": "Vyššie hodnoty zvyšujú účinnosť kompresie, ale spomaľujú kódovanie. Nemusí byť kompatibilný s hardvérovou akceleráciou na starších zariadeniach. Hodnota 0 zakáže B-snímky, zatiaľ čo -1 nastaví túto hodnotu automaticky.", "transcoding_max_bitrate": "Maximálna bitová rýchlosť", - "transcoding_max_bitrate_description": "Nastavenie maximálneho dátového toku môže zvýšiť predvídateľnosť veľkosti súborov za cenu menšieho zníženia kvality. Pri rozlíšení 720p sú typické hodnoty 2600 kbit/s pre VP9 alebo HEVC alebo 4500 kbit/s pre H.264. Zakázané, ak je nastavená hodnota 0.", + "transcoding_max_bitrate_description": "Nastavenie maximálneho dátového toku môže zvýšiť predvídateľnosť veľkosti súborov za cenu menšieho zníženia kvality. Pri rozlíšení 720p sú typické hodnoty 2600 kbit/s pre VP9 alebo HEVC alebo 4500 kbit/s pre H.264. Zakázané, ak je nastavená hodnota 0. Ak nie je špecifikovaná žiadna jednotka, predpokladá sa k (pre kbit/s); preto sú 5000, 5000k a 5M (pre Mbit/s) ekvivalentné.", "transcoding_max_keyframe_interval": "Maximálny interval medzi kľúčovými snímkami", "transcoding_max_keyframe_interval_description": "Nastavuje maximálnu vzdialenosť medzi kľúčovými snímkami. Nižšie hodnoty zhoršujú účinnosť kompresie, ale zlepšujú časy vyhľadávania a môžu zlepšiť kvalitu v scénach s rýchlym pohybom. Hodnota 0 nastavuje túto hodnotu automaticky.", "transcoding_optimal_description": "Videá s vyšším ako cieľovým rozlíšením alebo videá, ktoré nie sú v prijateľnom formáte", @@ -349,7 +366,7 @@ "transcoding_target_resolution": "Cieľové rozlíšenie", "transcoding_target_resolution_description": "Vyššie rozlíšenia môžu zachovať viac detailov, ale ich kódovanie trvá dlhšie, majú väčšiu veľkosť súborov a môžu znížiť odozvu aplikácie.", "transcoding_temporal_aq": "Časové AQ", - "transcoding_temporal_aq_description": "Platí len pre NVENC. Zvyšuje kvalitu scén s vysokým počtom detailov a nízkym počtom pohybov. Nemusí byť kompatibilný so staršími zariadeniami.", + "transcoding_temporal_aq_description": "Platí len pre NVENC. Časová adaptívna kvantizácia zvyšuje kvalitu scén s vysokým počtom detailov a nízkym počtom pohybov. Nemusí byť kompatibilné so staršími zariadeniami.", "transcoding_threads": "Vlákna", "transcoding_threads_description": "Vyššie hodnoty vedú k rýchlejšiemu kódovaniu, ale ponechávajú serveru menej priestoru na spracovanie iných úloh počas aktivity. Táto hodnota by nemala byť väčšia ako počet jadier CPU. Maximalizuje využitie, ak je nastavená na hodnotu 0.", "transcoding_tone_mapping": "Tónové mapovanie", @@ -363,7 +380,7 @@ "trash_enabled_description": "Povoliť funkcie koša", "trash_number_of_days": "Počet dní", "trash_number_of_days_description": "Počet dní, počas ktorých sa majú médiá ponechať v koši pred ich trvalým odstránením", - "trash_settings": "Kôš", + "trash_settings": "Nastavenia koša", "trash_settings_description": "Spravovať nastavenia koša", "unlink_all_oauth_accounts": "Odpojiť všetky účty OAuth", "unlink_all_oauth_accounts_description": "Nezabudnite odpojiť všetky účty OAuth pred prechodom na nového poskytovateľa.", @@ -400,11 +417,11 @@ "advanced_settings_prefer_remote_subtitle": "V niektorých zariadeniach sa miniatúry z miestnych položiek načítavajú veľmi pomaly. Aktivovaním tohto nastavenia sa namiesto toho načítajú vzdialené obrázky.", "advanced_settings_prefer_remote_title": "Uprednostniť vzdialené obrázky", "advanced_settings_proxy_headers_subtitle": "Určite hlavičky proxy servera, ktoré by mal Immich posielať s každou požiadavkou na sieť", - "advanced_settings_proxy_headers_title": "Proxy hlavičky", + "advanced_settings_proxy_headers_title": "Vlastné proxy hlavičky [EXPERIMENTÁLNE]", "advanced_settings_readonly_mode_subtitle": "Aktivuje režim iba na čítanie, v ktorom je možné fotografie iba prezerať, pričom funkcie ako výber viacerých obrázkov, zdieľanie, prenášanie a mazanie sú deaktivované. Aktivácia/deaktivácia režimu iba na čítanie prostredníctvom obrázku používateľa na hlavnej obrazovke", "advanced_settings_readonly_mode_title": "Režim iba na čítanie", "advanced_settings_self_signed_ssl_subtitle": "Preskakuje overovanie SSL certifikátom zo strany servera. Vyžaduje sa pre samo-podpísané certifikáty.", - "advanced_settings_self_signed_ssl_title": "Povoliť samo-podpísané SSL certifikáty", + "advanced_settings_self_signed_ssl_title": "Povoliť samo-podpísané SSL certifikáty [EXPERIMENTÁLNE]", "advanced_settings_sync_remote_deletions_subtitle": "Automaticky vymazať alebo obnoviť položku na tomto zariadení, keď sa táto akcia vykoná na webe", "advanced_settings_sync_remote_deletions_title": "Synchronizovať vzdialené vymazania [EXPERIMENTÁLNE]", "advanced_settings_tile_subtitle": "Pokročilé nastavenia používateľa", @@ -464,10 +481,14 @@ "api_key_description": "Táto hodnota sa zobrazí iba raz. Pred zatvorením okna ju určite skopírujte.", "api_key_empty": "Názov vášho API kĺuča by nemal byť prázdny", "api_keys": "API Kľúče", + "app_architecture_variant": "Variant (Architektúra)", "app_bar_signout_dialog_content": "Skutočne sa chcete odhlásiť?", "app_bar_signout_dialog_ok": "Áno", "app_bar_signout_dialog_title": "Odhlásiť sa", + "app_download_links": "Odkazy na stiahnutie aplikácie", "app_settings": "Nastavenia aplikácie", + "app_stores": "Obchody s aplikáciami", + "app_update_available": "Aktualizácia aplikácie je k dispozícii", "appears_in": "Vyskytuje sa v", "apply_count": "Použiť ({count, number})", "archive": "Archív", @@ -496,9 +517,9 @@ "asset_list_layout_settings_group_by_month_day": "Mesiac + deň", "asset_list_layout_sub_title": "Rozvrhnutie", "asset_list_settings_subtitle": "Nastavenia rozloženia mriežky fotografií", - "asset_list_settings_title": "Fotografická mriežka", + "asset_list_settings_title": "Mriežka fotografií", "asset_offline": "Médium je offline", - "asset_offline_description": "Toto externý obsah sa už nenachádza na disku. Požiadajte o pomoc svojho správcu Immich.", + "asset_offline_description": "Tento externá položka sa už nenachádza na disku. Pre pomoc sa prosím obráťte na správcu systému Immich.", "asset_restored_successfully": "Položky boli úspešne obnovené", "asset_skipped": "Preskočené", "asset_skipped_in_trash": "V koši", @@ -551,6 +572,7 @@ "backup_albums_sync": "Synchronizácia zálohovaných albumov", "backup_all": "Všetko", "backup_background_service_backup_failed_message": "Zálohovanie médií zlyhalo. Skúšam to znova…", + "backup_background_service_complete_notification": "Zálohovanie položiek dokončené", "backup_background_service_connection_failed_message": "Nepodarilo sa pripojiť k serveru. Skúšam to znova…", "backup_background_service_current_upload_notification": "Nahrávanie {filename}", "backup_background_service_default_notification": "Kontrola nových zdrojov…", @@ -598,6 +620,7 @@ "backup_controller_page_turn_on": "Povoliť zálohovanie na popredí", "backup_controller_page_uploading_file_info": "Nahrávaný súbor", "backup_err_only_album": "Nie je možné odstrániť jediný vybraný album", + "backup_error_sync_failed": "Synchronizácia sa nepodarila. Nie je možné spracovať zálohu.", "backup_info_card_assets": "položiek", "backup_manual_cancelled": "Zrušené", "backup_manual_in_progress": "Nahrávanie už prebieha. Vyskúšajte neskôr", @@ -659,6 +682,8 @@ "change_password_description": "Buď sa do systému prihlasujete prvýkrát, alebo bola podaná žiadosť o zmenu hesla. Nižšie zadajte nové heslo.", "change_password_form_confirm_password": "Potvrďte heslo", "change_password_form_description": "Dobrý deň, {name},\n\nBuď sa do systému prihlasujete prvýkrát, alebo bola podaná žiadosť o zmenu hesla. Prosím, zadajte nové heslo nižšie.", + "change_password_form_log_out": "Odhlásiť všetky ostatné zariadenia", + "change_password_form_log_out_description": "Odporúča sa odhlásiť sa zo všetkých ostatných zariadení", "change_password_form_new_password": "Nové heslo", "change_password_form_password_mismatch": "Heslá sa nezhodujú", "change_password_form_reenter_new_password": "Znova zadajte nové heslo", @@ -686,7 +711,7 @@ "client_cert_invalid_msg": "Neplatný súbor certifikátu alebo nesprávne heslo", "client_cert_remove_msg": "Certifikát klienta je odstránený", "client_cert_subtitle": "Podporuje iba formát PKCS12 (.p12, .pfx). Importovanie/odstránenie certifikátu je k dispozícii len pred prihlásením", - "client_cert_title": "SSL certifikát klienta", + "client_cert_title": "SSL certifikát klienta [EXPERIMENTÁLNE]", "clockwise": "V smere hodinových ručičiek", "close": "Zatvoriť", "collapse": "Zbaliť", @@ -698,13 +723,12 @@ "comments_and_likes": "Komentáre a páči sa mi to", "comments_are_disabled": "Komentáre sú vypnuté", "common_create_new_album": "Vytvoriť nový album", - "common_server_error": "Skontrolujte svoje sieťové pripojenie, uistite sa, že server je dostupný a verzie aplikácie/server sú kompatibilné.", "completed": "Dokončené", "confirm": "Potvrdiť", - "confirm_admin_password": "Potvrdiť Administrátorské Heslo", + "confirm_admin_password": "Potvrdiť heslo správcu", "confirm_delete_face": "Naozaj chcete z položky odstrániť tvár osoby {name}?", "confirm_delete_shared_link": "Ste si istý, že chcete odstrániť tento zdieľaný odkaz?", - "confirm_keep_this_delete_others": "Všetky ostatné položky v zásobníku budú odstránené okrem tejto položky. Naozaj chcete pokračovať?", + "confirm_keep_this_delete_others": "Všetky ostatné položky v zoskupení budú odstránené okrem tejto položky. Naozaj chcete pokračovať?", "confirm_new_pin_code": "Potvrdiť nový PIN kód", "confirm_password": "Potvrdiť heslo", "confirm_tag_face": "Chcete označiť túto tvár ako {name}?", @@ -737,6 +761,7 @@ "create": "Vytvoriť", "create_album": "Vytvoriť album", "create_album_page_untitled": "Bez názvu", + "create_api_key": "Vytvoriť API kľúč", "create_library": "Vytvoriť knižnicu", "create_link": "Vytvoriť odkaz", "create_link_to_share": "Vytvoriť odkaz na zdieľanie", @@ -766,6 +791,7 @@ "daily_title_text_date_year": "EEEE, d. MMMM y", "dark": "Tmavá", "dark_theme": "Prepnúť tmavú tému", + "date": "Dátum", "date_after": "Dátum po", "date_and_time": "Dátum a Čas", "date_before": "Dátum pred", @@ -880,7 +906,6 @@ "edit_tag": "Upraviť štítok", "edit_title": "Upraviť názov", "edit_user": "Upraviť používateľa", - "edited": "Upravené", "editor": "Editor", "editor_close_without_save_prompt": "Úpravy nebudú uložené", "editor_close_without_save_title": "Zavrieť editor?", @@ -940,7 +965,7 @@ "failed_to_remove_product_key": "Nepodarilo sa odstrániť produktový kľúč", "failed_to_reset_pin_code": "PIN kód sa nepodarilo obnoviť", "failed_to_stack_assets": "Nepodarilo sa zoskupiť položky", - "failed_to_unstack_assets": "Nepodarilo sa rozdeliť položky", + "failed_to_unstack_assets": "Nepodarilo sa zrušiť zoskupenie položiek", "failed_to_update_notification_status": "Nepodarilo sa aktualizovať stav oznámenia", "import_path_already_exists": "Táto cesta importu už existuje.", "incorrect_email_or_password": "Nesprávny e-mail alebo heslo", @@ -1036,6 +1061,7 @@ "exif_bottom_sheet_description_error": "Chyba pri aktualizácii popisu", "exif_bottom_sheet_details": "PODROBNOSTI", "exif_bottom_sheet_location": "POLOHA", + "exif_bottom_sheet_no_description": "Žiadny popis", "exif_bottom_sheet_people": "ĽUDIA", "exif_bottom_sheet_person_add_person": "Pridať meno", "exit_slideshow": "Opustiť prezentáciu", @@ -1054,7 +1080,7 @@ "export_database": "Exportovať databázu", "export_database_description": "Exportovať databázu SQLite", "extension": "Prípona", - "external": "Externý", + "external": "Externá", "external_libraries": "Externé knižnice", "external_network": "Externá sieť", "external_network_sheet_info": "Ak nie ste v preferovanej sieti Wi-Fi, aplikácia sa pripojí k serveru prostredníctvom prvej z nižšie uvedených adries URL, na ktorú sa dostane, počnúc zhora nadol", @@ -1074,6 +1100,7 @@ "features_setting_description": "Spravovať funkcie aplikácie", "file_name": "Názov súboru", "file_name_or_extension": "Názov alebo prípona súboru", + "file_size": "Veľkosť súboru", "filename": "Názov súboru", "filetype": "Typ súboru", "filter": "Filter", @@ -1117,7 +1144,6 @@ "header_settings_field_validator_msg": "Hodnota nemôže byť prázdna", "header_settings_header_name_input": "Názov hlavičky", "header_settings_header_value_input": "Hodnota hlavičky", - "headers_settings_tile_subtitle": "Určite hlavičky proxy servera, ktoré má aplikácia posielať s každou požiadavkou na sieť", "headers_settings_tile_title": "Vlastné hlavičky proxy servera", "hi_user": "Ahoj {name} ({email})", "hide_all_people": "Skryť všetky osoby", @@ -1196,8 +1222,8 @@ "jobs": "Úlohy", "keep": "Ponechať", "keep_all": "Ponechať všetko", - "keep_this_delete_others": "Ponechať toto, odstrániť ostatné", - "kept_this_deleted_others": "Ponechal túto položku a odstránil {count, plural, one {# položku} few {# položky} other {# položiek}}", + "keep_this_delete_others": "Ponechať túto, odstrániť ostatné", + "kept_this_deleted_others": "Táto položka bola ponechaná a {count, plural, one {odstránila sa # položka} few {odstránili sa # položky} other {odstránilo sa # položiek}}", "keyboard_shortcuts": "Klávesové skratky", "language": "Jazyk", "language_no_results_subtitle": "Skúste upraviť hľadaný výraz", @@ -1238,6 +1264,7 @@ "local_media_summary": "Súhrn lokálnych médií", "local_network": "Miestna sieť", "local_network_sheet_info": "Pri použití zadanej siete Wi-Fi sa aplikácia pripojí k serveru prostredníctvom tejto URL adresy", + "location": "Poloha", "location_permission": "Povolenie na určenie polohy", "location_permission_content": "Na používanie funkcie automatického prepínania potrebuje aplikácia Immich presné povolenie na určenie polohy, aby mohla prečítať názov aktuálnej Wi-Fi siete", "location_picker_choose_on_map": "Zvoľte na mape", @@ -1342,6 +1369,8 @@ "minute": "Minúta", "minutes": "Minút", "missing": "Chýbajúce", + "mobile_app": "Mobilná aplikácia", + "mobile_app_download_onboarding_note": "Stiahnite si sprievodnú mobilnú aplikáciu pomocou nasledujúcich možností", "model": "Model", "month": "Mesiac", "monthly_title_text_date_format": "LLLL y", @@ -1360,6 +1389,8 @@ "my_albums": "Moje albumy", "name": "Meno", "name_or_nickname": "Meno alebo prezývka", + "navigate": "Prejsť", + "navigate_to_time": "Prejsť na čas", "network_requirement_photos_upload": "Použiť mobilné dáta na zálohovanie fotografií", "network_requirement_videos_upload": "Použiť mobilné dáta na zálohovanie videí", "network_requirements": "Požiadavky na sieť", @@ -1369,6 +1400,7 @@ "never": "nikdy", "new_album": "Nový album", "new_api_key": "Nový API kľúč", + "new_date_range": "Nový rozsah dátumov", "new_password": "Nové heslo", "new_person": "Nová osoba", "new_pin_code": "Nový PIN kód", @@ -1380,7 +1412,7 @@ "next": "Ďalej", "next_memory": "Ďalšia spomienka", "no": "Nie", - "no_albums_message": "Vytvorí album na organizovanie fotiek a videí", + "no_albums_message": "Vytvorte album na usporiadanie svojich fotiek a videí", "no_albums_with_name_yet": "Vyzerá, že zatiaľ nemáte album s týmto názvom.", "no_albums_yet": "Vyzerá, že zatiaľ nemáte žiadne albumy.", "no_archived_assets_message": "Archivujte fotografie a videá a skryte ich z vášho zobrazenia fotografií", @@ -1393,7 +1425,7 @@ "no_exif_info_available": "Nie sú dostupné exif údaje", "no_explore_results_message": "Nahrajte viac fotiek na objavovanie vašej zbierky.", "no_favorites_message": "Pridajte si obľúbené, aby ste rýchlo našli svoje najlepšie obrázky a videá", - "no_libraries_message": "Vytvorí externú knižnicu na prezeranie fotiek a videí", + "no_libraries_message": "Vytvorte externú knižnicu na prezeranie fotiek a videí", "no_local_assets_found": "Neboli nájdené žiadne lokálne položky s touto kontrolnou sumou", "no_locked_photos_message": "Fotografie a videá v zamknutom priečinku sú skryté a nezobrazujú sa pri prehľadávaní alebo vyhľadávaní v knižnici.", "no_name": "Bez mena", @@ -1403,7 +1435,7 @@ "no_remote_assets_found": "Neboli nájdené žiadne vzdialené položky s touto kontrolnou sumou", "no_results": "Žiadne výsledky", "no_results_description": "Skúste synonymum alebo všeobecnejší výraz", - "no_shared_albums_message": "Vytvorí album na zdieľanie fotiek a videí s ľuďmi vo vašej sieti", + "no_shared_albums_message": "Vytvorte album na zdieľanie fotiek a videí s ľuďmi vo vašej sieti", "no_uploads_in_progress": "Žiadne prebiehajúce nahrávanie", "not_available": "Nedostupné", "not_in_any_album": "Nie je v žiadnom albume", @@ -1419,6 +1451,9 @@ "notifications": "Oznámenia", "notifications_setting_description": "Spravovať upozornenia", "oauth": "OAuth", + "obtainium_configurator": "Konfigurátor Obtainium", + "obtainium_configurator_instructions": "Použite Obtainium na inštaláciu a aktualizáciu aplikácie pre Android priamo z verzie Immich v službe GitHub. Vytvorte kľúč API a vyberte variantu, aby ste vytvorili konfiguračný odkaz Obtainium", + "ocr": "OCR", "official_immich_resources": "Oficiálne Immich zdroje", "offline": "Offline", "offset": "Posun", @@ -1448,7 +1483,7 @@ "other_devices": "Ďalšie zariadenia", "other_entities": "Ostatné subjekty", "other_variables": "Ostatné premenné", - "owned": "Vlastnené", + "owned": "Vlastné", "owner": "Vlastník", "partner": "Partner", "partner_can_access": "{partner} môže pristupovať", @@ -1508,7 +1543,7 @@ "person_hidden": "{name}{hidden, select, true { (skryté)} other {}}", "photo_shared_all_users": "Vyzerá, že zdieľate svoje fotky so všetkými používateľmi alebo nemáte žiadnych používateľov.", "photos": "Fotografie", - "photos_and_videos": "Fotografie & Videa", + "photos_and_videos": "Fotografie a videá", "photos_count": "{count, plural, one {{count, number} fotka} few {{count, number} fotky} other {{count, number} fotiek}}", "photos_from_previous_years": "Fotky z minulých rokov", "pick_a_location": "Vyberte polohu", @@ -1523,10 +1558,14 @@ "play_memories": "Prehrať spomienky", "play_motion_photo": "Prehrať pohyblivú fotku", "play_or_pause_video": "Pustí alebo pozastaví video", + "play_original_video": "Prehrať originálne video", + "play_original_video_setting_description": "Uprednostňujte prehrávanie originálnych videí pred prekódovanými videami. Ak originálny súbor nie je kompatibilný, nemusí sa správne prehrať.", + "play_transcoded_video": "Prehrať prekódované video", "please_auth_to_access": "Prosím, potvrďte overenie pre prístup", "port": "Port", "preferences_settings_subtitle": "Spravovať predvoľby aplikácie", "preferences_settings_title": "Predvoľby", + "preparing": "Pripravuje sa", "preset": "Predvoľba", "preview": "Náhľad", "previous": "Predošlé", @@ -1539,13 +1578,9 @@ "privacy": "Súkromie", "profile": "Profil", "profile_drawer_app_logs": "Záznamy", - "profile_drawer_client_out_of_date_major": "Mobilná aplikácia je zastaralá. Prosím aktualizujte na najnovšiu verziu.", - "profile_drawer_client_out_of_date_minor": "Mobilná aplikácia je zastaralá. Prosím aktualizujte na najnovšiu verziu.", "profile_drawer_client_server_up_to_date": "Klient a server sú aktuálne", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Režim iba na čítanie je aktivovaný. Dlhým stlačením ikony obrázku používateľa režim opustíte.", - "profile_drawer_server_out_of_date_major": "Server je zastaralý. Prosím aktualizujte na najnovšiu verziu.", - "profile_drawer_server_out_of_date_minor": "Server je zastaralý. Prosím aktualizujte na najnovšiu verziu.", "profile_image_of_user": "Profilový obrázok používateľa {user}", "profile_picture_set": "Profilový obrázok nastavený.", "public_album": "Verejný album", @@ -1592,6 +1627,7 @@ "read_changelog": "Prečítať zoznam zmien", "readonly_mode_disabled": "Režim iba na čítanie je vypnutý", "readonly_mode_enabled": "Režim iba na čítanie je zapnutý", + "ready_for_upload": "Pripravené na nahratie", "reassign": "Preradiť", "reassigned_assets_to_existing_person": "Opätovne {count, plural, one {priradená # položka} few {priradené # položky} other {priradených # položiek}} k {name, select, null {existujúcej osobe} other {{name}}}", "reassigned_assets_to_new_person": "Opätovne {count, plural, one {priradená # položka} few {priradené # položky} other {priradených # položiek}} novej osobe", @@ -1661,6 +1697,7 @@ "reset_sqlite_confirmation": "Ste si istí, že chcete obnoviť SQLite databázu? Na opätovnú synchronizáciu údajov sa budete musieť odhlásiť a znova prihlásiť", "reset_sqlite_success": "Úspešné obnovenie databázy SQLite", "reset_to_default": "Obnoviť na predvolené", + "resolution": "Rozlíšenie", "resolve_duplicates": "Vyriešiť duplicity", "resolved_all_duplicates": "Vyriešené všetky duplicity", "restore": "Navrátiť", @@ -1679,6 +1716,7 @@ "running": "Spustené", "save": "Uložiť", "save_to_gallery": "Uložiť do galérie", + "saved": "Uložené", "saved_api_key": "Uložený API Kľúč", "saved_profile": "Uložený profil", "saved_settings": "Nastavenia boli uložené", @@ -1695,6 +1733,9 @@ "search_by_description_example": "Pešia turistika v Sape", "search_by_filename": "Hľadať podľa názvu alebo prípony súboru", "search_by_filename_example": "napr. IMG_1234.JPG alebo PNG", + "search_by_ocr": "Hľadať podľa OCR", + "search_by_ocr_example": "Latte", + "search_camera_lens_model": "Hľadať model objektívu...", "search_camera_make": "Hľadať značku fotoaparátu...", "search_camera_model": "Hľadať model fotoaparátu...", "search_city": "Hľadať mesto...", @@ -1711,6 +1752,7 @@ "search_filter_location_title": "Vyberte polohu", "search_filter_media_type": "Typ média", "search_filter_media_type_title": "Vyberte typ média", + "search_filter_ocr": "Hľadať podľa OCR", "search_filter_people_title": "Vyberte ľudí", "search_for": "Vyhľadať", "search_for_existing_person": "Hľadať existujúcu osobu", @@ -1773,6 +1815,7 @@ "server_online": "Server je Online", "server_privacy": "Zásady ochrany osobných údajov servera", "server_stats": "Štatistiky servera", + "server_update_available": "Aktualizácia servera je k dispozícii", "server_version": "Verzia servera", "set": "Nastaviť", "set_as_album_cover": "Nastaviť ako obal albumu", @@ -1801,6 +1844,8 @@ "setting_notifications_subtitle": "Upravte svoje nastavenia oznámení", "setting_notifications_total_progress_subtitle": "Celkový priebeh nahrávania (nahraných/celkovo)", "setting_notifications_total_progress_title": "Zobraziť celkový priebeh zálohovania na pozadí", + "setting_video_viewer_auto_play_subtitle": "Automaticky spustiť prehrávanie videí po ich otvorení", + "setting_video_viewer_auto_play_title": "Automaticky prehrať videá", "setting_video_viewer_looping_title": "Opakovanie", "setting_video_viewer_original_video_subtitle": "Pri streamovaní videa zo servera prehrať originál, aj keď je k dispozícii prekódované video. Môže to viesť k prerušovanému prehrávaniu videa. Videá dostupné lokálne sa prehrajú v pôvodnej kvalite bez ohľadu na toto nastavenie.", "setting_video_viewer_original_video_title": "Vynútiť pôvodné video", @@ -1859,7 +1904,7 @@ "shared_link_options": "Možnosti zdieľaných odkazov", "shared_link_password_description": "Vyžadovať heslo pre prístup k tomuto zdieľanému odkazu", "shared_links": "Zdieľané odkazy", - "shared_links_description": "Zdieľanie fotografií a videí pomocou odkazu", + "shared_links_description": "Zdieľajte fotografie a videá pomocou odkazu", "shared_photos_and_videos_count": "{assetCount, plural, few {# zdieľané fotky a videá.} other {# zdieľaných fotiek a videí.}}", "shared_with_me": "Zdieľané so mnou", "shared_with_partner": "Zdieľané s {partner}", @@ -1914,9 +1959,9 @@ "sort_recent": "Najnovšia fotografia", "sort_title": "Názov", "source": "Zdroj", - "stack": "Zoskupenie", + "stack": "Zoskupiť", "stack_action_prompt": "{count} zoskupených", - "stack_duplicates": "Zoskupiť duplicity", + "stack_duplicates": "Zoskupiť duplikáty", "stack_select_one_photo": "Vyberte jednu hlavnú fotku pre zoskupenie", "stack_selected_photos": "Zoskupiť vybraté fotky", "stacked_assets_count": "{count, plural, one {Zoskupená # položka} few {Zoskupené # položky} other {Zoskupených # položiek}}", @@ -1928,7 +1973,7 @@ "status": "Stav", "stop_casting": "Zastaviť prenos", "stop_motion_photo": "Stopmotion fotka", - "stop_photo_sharing": "Zastaviť zdieľanie vašich fotiek?", + "stop_photo_sharing": "Zastaviť zdieľanie vašich fotografií?", "stop_photo_sharing_description": "{partner} už nebude mať prístup k vašim fotkám.", "stop_sharing_photos_with_user": "Zastaviť zdieľanie vašich fotiek s týmto používateľom", "storage": "Ukladací priestor", @@ -1980,6 +2025,7 @@ "theme_setting_three_stage_loading_title": "Povolenie trojstupňového načítavania", "they_will_be_merged_together": "Zlúčia sa dokopy", "third_party_resources": "Zdroje tretích strán", + "time": "Čas", "time_based_memories": "Časové spomienky", "timeline": "Časová os", "timezone": "Časové pásmo", @@ -1990,7 +2036,7 @@ "to_multi_select": "na viacnásobný výber", "to_parent": "Prejsť k nadradenému", "to_select": "na výber", - "to_trash": "Kôš", + "to_trash": "Do koša", "toggle_settings": "Prepnúť nastavenie", "total": "Celkom", "total_usage": "Celkové využitie", @@ -2002,7 +2048,7 @@ "trash_emptied": "Kôš vyprázdnený", "trash_no_results_message": "Vymazané fotografie a videá sa zobrazia tu.", "trash_page_delete_all": "Vymazať všetky", - "trash_page_empty_trash_dialog_content": "Skutočne chcete vyprázdniť kôš? Tieto položky budú permanentne odstránené z aplikácie Immich", + "trash_page_empty_trash_dialog_content": "Skutočne chcete vyprázdniť kôš? Tieto položky budú natrvalo odstránené z aplikácie Immich", "trash_page_info": "Médiá v koši sa permanentne odstránia po {days} dňoch", "trash_page_no_assets": "Žiadne médiá v koši", "trash_page_restore_all": "Obnoviť všetky", @@ -2012,6 +2058,7 @@ "troubleshoot": "Riešenie problémov", "type": "Typ", "unable_to_change_pin_code": "Nie je možné zmeniť PIN kód", + "unable_to_check_version": "Nie je možné skontrolovať verziu aplikácie alebo servera", "unable_to_setup_pin_code": "Nie je možné nastaviť PIN kód", "unarchive": "Odarchivovať", "unarchive_action_prompt": "{count} odstránené z archívu", @@ -2035,9 +2082,9 @@ "unselect_all": "Zrušiť výber všetkých", "unselect_all_duplicates": "Zrušiť výber všetkých duplicít", "unselect_all_in": "Zrušiť výber všetkých v {group}", - "unstack": "Odskupiť", + "unstack": "Zrušiť zoskupenie", "unstack_action_prompt": "{count} nezoskupených", - "unstacked_assets_count": "Zrušenie zoskupenia pre {count, plural, one {# položku} few {# položky} other {# položiek}}", + "unstacked_assets_count": "Zrušené zoskupenia pre {count, plural, one {# položku} few {# položky} other {# položiek}}", "untagged": "Bez štítku", "up_next": "To je všetko", "update_location_action_prompt": "Aktualizovať polohu {count} vybraných položiek pomocou:", @@ -2112,7 +2159,7 @@ "view_user": "Zobraziť používateľa", "viewer_remove_from_stack": "Odstrániť zo zoskupenia", "viewer_stack_use_as_main_asset": "Použiť ako hlavnú fotku", - "viewer_unstack": "Odskupiť", + "viewer_unstack": "Zrušiť zoskupenie", "visibility_changed": "Viditeľnosť zmenená pre {count, plural, one {# osobu} few {# osoby} other {# osôb}}", "waiting": "Čakajúce", "warning": "Varovanie", diff --git a/i18n/sl.json b/i18n/sl.json index a4e75727ed..9ae16549f5 100644 --- a/i18n/sl.json +++ b/i18n/sl.json @@ -28,10 +28,12 @@ "add_to_album": "Dodaj v album", "add_to_album_bottom_sheet_added": "Dodano v {album}", "add_to_album_bottom_sheet_already_exists": "Že v {album}", + "add_to_album_bottom_sheet_some_local_assets": "Nekaterih lokalnih sredstev ni bilo mogoče dodati v album", "add_to_album_toggle": "Preklopi izbiro za {album}", "add_to_albums": "Dodaj v albume", "add_to_albums_count": "Dodaj v albume ({count})", "add_to_shared_album": "Dodaj k deljenemu albumu", + "add_upload_to_stack": "Dodaj nalaganje v sklad", "add_url": "Dodaj URL", "added_to_archive": "Dodano v arhiv", "added_to_favorites": "Dodano med priljubljene", @@ -118,7 +120,7 @@ "library_settings_description": "Uredi nastavitve zunanje knjižnice", "library_tasks_description": "Izvedi nalogo knjižnice", "library_watching_enable_description": "Opazuj spremembe datotek v zunanji knjižnici", - "library_watching_settings": "Opazovanje knjižnice (EKSPERIMENTALNO)", + "library_watching_settings": "Opazovanje knjižnice [POSKUSNO]", "library_watching_settings_description": "Samodejno opazuj spremembo datotek", "logging_enable_description": "Omogoči dnevnik", "logging_level_description": "Nivo dnevnika, ko je le-ta omogočen.", @@ -152,6 +154,18 @@ "machine_learning_min_detection_score_description": "Najmanjši rezultat zaupanja za zaznavanje obraza od 0-1. Nižje vrednosti bodo zaznale več obrazov, vendar lahko povzročijo lažne pozitivne rezultate.", "machine_learning_min_recognized_faces": "Najmanjše število prepoznanih obrazov", "machine_learning_min_recognized_faces_description": "Najmanjše število prepoznanih obrazov za osebo, da se ustvari. Če to povečate, postane prepoznavanje obraza natančnejše na račun večje možnosti, da obraz ni dodeljen osebi.", + "machine_learning_ocr": "Optično prepoznavanje znakov (OCR)", + "machine_learning_ocr_description": "Uporaba strojnega učenja za prepoznavanje besedila na slikah", + "machine_learning_ocr_enabled": "Omogoči OCR", + "machine_learning_ocr_enabled_description": "Če je onemogočeno, slike ne bodo prepoznane po besedilu.", + "machine_learning_ocr_max_resolution": "Največja ločljivost", + "machine_learning_ocr_max_resolution_description": "Predogledi nad to ločljivostjo bodo spremenjeni, pri čemer se bo ohranilo razmerje stranic. Višje vrednosti so natančnejše, vendar obdelava traja dlje in porabi več pomnilnika.", + "machine_learning_ocr_min_detection_score": "Najnižji rezultat zaznavanja", + "machine_learning_ocr_min_detection_score_description": "Najnižja stopnja zaupanja za zaznavanje besedila je od 0 do 1. Nižje vrednosti bodo zaznale več besedila, vendar lahko povzročijo lažno pozitivne rezultate.", + "machine_learning_ocr_min_recognition_score": "Najnižji rezultat prepoznavanja", + "machine_learning_ocr_min_score_recognition_description": "Najnižja stopnja zaupanja za prepoznavanje zaznanega besedila je od 0 do 1. Nižje vrednosti bodo prepoznale več besedila, vendar lahko povzročijo lažno pozitivne rezultate.", + "machine_learning_ocr_model": "OCR model", + "machine_learning_ocr_model_description": "Strežniški modeli so natančnejši od mobilnih modelov, vendar obdelujejo podatke dlje in porabijo več pomnilnika.", "machine_learning_settings": "Nastavitve strojnega učenja", "machine_learning_settings_description": "Upravljajte funkcije in nastavitve strojnega učenja", "machine_learning_smart_search": "Pametno iskanje", @@ -209,6 +223,8 @@ "notification_email_ignore_certificate_errors_description": "Prezri napake pri preverjanju potrdila TLS (ni priporočljivo)", "notification_email_password_description": "Geslo za uporabo pri preverjanju pristnosti z e-poštnim strežnikom", "notification_email_port_description": "Vrata e-poštnega strežnika (npr. 25, 465 ali 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "Uporabite SMTPS (SMTP prek TLS)", "notification_email_sent_test_email_button": "Pošljite testno e-pošto in shrani", "notification_email_setting_description": "Nastavitve za pošiljanje e-poštnih obvestil", "notification_email_test_email": "Pošlji testno e-pošto", @@ -241,6 +257,7 @@ "oauth_storage_quota_default_description": "Kvota v GiB, ki se uporabi, kadar ni podanega zahtevka.", "oauth_timeout": "Časovna omejitev zahteve", "oauth_timeout_description": "Časovna omejitev za zahteve v milisekundah", + "ocr_job_description": "Uporaba strojnega učenja za prepoznavanje besedila na slikah", "password_enable_description": "Prijava z e-pošto in geslom", "password_settings": "Prijava z geslom", "password_settings_description": "Upravljajte nastavitve prijave z geslom", @@ -306,14 +323,14 @@ "transcoding_acceleration_qsv": "Hitra sinhronizacija (zahteva procesor Intel 7. generacije ali novejši)", "transcoding_acceleration_rkmpp": "RKMPP (samo na Rockchip SOC)", "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Dovoljeni zvočni kodeksi", - "transcoding_accepted_audio_codecs_description": "Izberite, katerih zvočnih kodeksov ni treba prekodirati. Uporablja se samo za določene politike prekodiranja.", + "transcoding_accepted_audio_codecs": "Dovoljeni zvočni kodeki", + "transcoding_accepted_audio_codecs_description": "Izberite, katerih zvočnih kodekov ni treba prekodirati. Uporablja se samo za določene politike prekodiranja.", "transcoding_accepted_containers": "Sprejeti zabojniki", "transcoding_accepted_containers_description": "Izberite, katerih formatov zabojnika ni treba ponovno muksirati v MP4. Uporablja se samo za določene politike prekodiranja.", - "transcoding_accepted_video_codecs": "Podprti video kodeksi", - "transcoding_accepted_video_codecs_description": "Izberite, katerih video kodeksov ni treba prekodirati. Uporablja se samo za določene politike prekodiranja.", + "transcoding_accepted_video_codecs": "Podprti video kodeki", + "transcoding_accepted_video_codecs_description": "Izberite, katerih video kodekov ni treba prekodirati. Uporablja se samo za določene politike prekodiranja.", "transcoding_advanced_options_description": "Možnosti, ki jih večini uporabnikov ne treba spreminjati", - "transcoding_audio_codec": "Zvočni kodeks", + "transcoding_audio_codec": "Zvočni kodek", "transcoding_audio_codec_description": "Opus je najbolj kakovostna možnost, vendar ima slabšo združljivost s starimi napravami ali programsko opremo.", "transcoding_bitrate_description": "Videoposnetki, ki presegajo največjo bitno hitrost ali niso v sprejemljivem formatu", "transcoding_codecs_learn_more": "Če želite izvedeti več o tukaj uporabljeni terminologiji, glejte dokumentacijo FFmpeg za kodek H.264, kodek HEVC in VP9 kodek.", @@ -323,7 +340,7 @@ "transcoding_constant_rate_factor_description": "Raven kakovosti videa. Tipične vrednosti so 23 za H.264, 28 za HEVC, 31 za VP9 in 35 za AV1. Nižje je boljše, vendar ustvarja večje datoteke.", "transcoding_disabled_description": "Ne prekodirajte nobenih videoposnetkov, lahko prekine predvajanje na nekaterih odjemalcih", "transcoding_encoding_options": "Možnosti kodiranja", - "transcoding_encoding_options_description": "Nastavite kodekse, ločljivost, kakovost in druge možnosti za kodirane videoposnetke", + "transcoding_encoding_options_description": "Nastavite kodeke, ločljivost, kakovost in druge možnosti za kodirane videoposnetke", "transcoding_hardware_acceleration": "Strojno pospeševanje", "transcoding_hardware_acceleration_description": "Eksperimentalno: hitrejše prekodiranje, vendar se lahko kakovost pri enaki bitni hitrosti zmanjša", "transcoding_hardware_decoding": "Strojno dekodiranje", @@ -331,7 +348,7 @@ "transcoding_max_b_frames": "Največji B-okvirji", "transcoding_max_b_frames_description": "Višje vrednosti izboljšajo učinkovitost stiskanja, vendar upočasnijo kodiranje. Morda ni združljivo s strojnim pospeševanjem na starejših napravah. 0 onemogoči okvirje B, medtem ko -1 samodejno nastavi to vrednost.", "transcoding_max_bitrate": "Največja bitna hitrost", - "transcoding_max_bitrate_description": "Z nastavitvijo največje bitne hitrosti so lahko velikosti datotek bolj predvidljive ob manjši ceni kakovosti. Pri 720p so tipične vrednosti 2600 kbit/s za VP9 ali HEVC ali 4500 kbit/s za H.264. Onemogočeno, če je nastavljeno na 0.", + "transcoding_max_bitrate_description": "Z nastavitvijo najvišje bitne hitrosti je mogoče povečati predvidljivost velikosti datotek, vendar z manjšim zmanjšanjem kakovosti. Pri ločljivosti 720p so tipične vrednosti 2600 kbit/s za VP9 ali HEVC oziroma 4500 kbit/s za H.264. Onemogočeno, če je nastavljeno na 0. Če enota ni določena, se predpostavlja k (za kbit/s); zato so 5000, 5000 kbit/s in 5 Mbit/s enakovredne.", "transcoding_max_keyframe_interval": "Največji interval ključnih sličic", "transcoding_max_keyframe_interval_description": "Nastavi največjo razdaljo med ključnimi slikami. Nižje vrednosti poslabšajo učinkovitost stiskanja, vendar izboljšajo čas iskanja in lahko izboljšajo kakovost prizorov s hitrim gibanjem. 0 samodejno nastavi to vrednost.", "transcoding_optimal_description": "Videoposnetki, ki so višji od ciljne ločljivosti ali niso v sprejemljivem formatu", @@ -349,7 +366,7 @@ "transcoding_target_resolution": "Ciljna ločljivost", "transcoding_target_resolution_description": "Višje ločljivosti lahko ohranijo več podrobnosti, vendar kodiranje traja dlje, imajo večje velikosti datotek in lahko zmanjšajo odzivnost aplikacije.", "transcoding_temporal_aq": "Časovni AQ", - "transcoding_temporal_aq_description": "Velja samo za NVENC. Poveča kakovost prizorov z veliko podrobnosti in malo gibanja. Morda ni združljiv s starejšimi napravami.", + "transcoding_temporal_aq_description": "Velja samo za NVENC. Časovna prilagodljiva kvantizacija izboljša kakovost prizorov z visoko stopnjo podrobnosti in nizko stopnjo gibanja. Morda ni združljivo s starejšimi napravami.", "transcoding_threads": "Niti", "transcoding_threads_description": "Višje vrednosti vodijo do hitrejšega kodiranja, vendar pustijo manj prostora strežniku za obdelavo drugih nalog, ko je aktiven. Ta vrednost ne sme biti večja od števila jeder procesorja. Maksimira uporabo, če je nastavljeno na 0.", "transcoding_tone_mapping": "Tonska preslikava", @@ -400,11 +417,11 @@ "advanced_settings_prefer_remote_subtitle": "Nekatere naprave zelo počasi nalagajo sličice iz lokalnih sredstev. Aktivirajte to nastavitev, če želite namesto tega naložiti oddaljene slike.", "advanced_settings_prefer_remote_title": "Uporabi raje oddaljene slike", "advanced_settings_proxy_headers_subtitle": "Določi proxy glavo, ki jo naj Immich pošlje ob vsaki mrežni zahtevi", - "advanced_settings_proxy_headers_title": "Proxy glave", + "advanced_settings_proxy_headers_title": "Proxy glave po meri [POSKUSNO]", "advanced_settings_readonly_mode_subtitle": "Omogoči način samo za branje, kjer si je mogoče fotografije samo ogledati, funkcije, kot so izbiranje več slik, deljenje, predvajanje in brisanje, so onemogočene. Omogoči/onemogoči način samo za branje prek uporabniškega avatarja na glavnem zaslonu", "advanced_settings_readonly_mode_title": "Način samo za branje", "advanced_settings_self_signed_ssl_subtitle": "Preskoči preverjanje potrdila SSL za končno točko strežnika. Zahtevano za samopodpisana potrdila.", - "advanced_settings_self_signed_ssl_title": "Dovoli samopodpisana SSL potrdila", + "advanced_settings_self_signed_ssl_title": "Dovoli samopodpisana potrdila SSL [POSKUSNO]", "advanced_settings_sync_remote_deletions_subtitle": "Samodejno izbriši ali obnovi sredstvo v tej napravi, ko je to dejanje izvedeno v spletu", "advanced_settings_sync_remote_deletions_title": "Sinhroniziraj oddaljene izbrise [EKSPERIMENTALNO]", "advanced_settings_tile_subtitle": "Napredne uporabniške nastavitve", @@ -464,10 +481,14 @@ "api_key_description": "Ta vrednost bo prikazana samo enkrat. Ne pozabite jo kopirati, preden zaprete okno.", "api_key_empty": "Ime ključa API ne sme biti prazno", "api_keys": "API ključi", + "app_architecture_variant": "Različica (Arhitektura)", "app_bar_signout_dialog_content": "Ste prepričani, da se želite odjaviti?", "app_bar_signout_dialog_ok": "Da", "app_bar_signout_dialog_title": "Odjava", + "app_download_links": "Povezave za prenos aplikacij", "app_settings": "Nastavitve aplikacije", + "app_stores": "Trgovine z aplikacijami", + "app_update_available": "Posodobitev aplikacije je na voljo", "appears_in": "Pojavi se v", "apply_count": "Uporabi ({count, number})", "archive": "Arhiv", @@ -551,6 +572,7 @@ "backup_albums_sync": "Sinhronizacija varnostnih kopij albumov", "backup_all": "Vse", "backup_background_service_backup_failed_message": "Varnostno kopiranje sredstev ni uspelo. Ponovno poskušam…", + "backup_background_service_complete_notification": "Varnostno kopiranje sredstev končano", "backup_background_service_connection_failed_message": "Povezava s strežnikom ni uspela. Ponovno poskušam…", "backup_background_service_current_upload_notification": "Nalagam {filename}", "backup_background_service_default_notification": "Preverjam za novimi sredstvi…", @@ -598,6 +620,7 @@ "backup_controller_page_turn_on": "Vklopite varnostno kopiranje v ospredju", "backup_controller_page_uploading_file_info": "Nalaganje podatkov o datoteki", "backup_err_only_album": "Edinega albuma ni mogoče odstraniti", + "backup_error_sync_failed": "Sinhronizacija ni uspela. Varnostne kopije ni mogoče obdelati.", "backup_info_card_assets": "sredstva", "backup_manual_cancelled": "Preklicano", "backup_manual_in_progress": "Nalaganje že poteka. Poskusite čez nekaj časa", @@ -659,6 +682,8 @@ "change_password_description": "To je bodisi prvič, da se vpisujete v sistem ali pa je bila podana zahteva za spremembo vašega gesla. Spodaj vnesite novo geslo.", "change_password_form_confirm_password": "Potrdi geslo", "change_password_form_description": "Pozdravljeni {name},\n\nTo je bodisi prvič, da se vpisujete v sistem ali pa je bila podana zahteva za spremembo vašega gesla. Spodaj vnesite novo geslo.", + "change_password_form_log_out": "Odjava vseh drugih naprav", + "change_password_form_log_out_description": "Priporočljivo je, da se odjavite iz vseh drugih naprav", "change_password_form_new_password": "Novo geslo", "change_password_form_password_mismatch": "Gesli se ne ujemata", "change_password_form_reenter_new_password": "Znova vnesi novo geslo", @@ -686,7 +711,7 @@ "client_cert_invalid_msg": "Neveljavna datoteka potrdila ali napačno geslo", "client_cert_remove_msg": "Potrdilo odjemalca je odstranjeno", "client_cert_subtitle": "Podpira samo format PKCS12 (.p12, .pfx). Uvoz/odstranitev potrdila je na voljo samo pred prijavo", - "client_cert_title": "Potrdilo odjemalca SSL", + "client_cert_title": "Potrdilo odjemalca SSL [POSKUSNO]", "clockwise": "V smeri urinega kazalca", "close": "Zapri", "collapse": "Strni", @@ -698,7 +723,6 @@ "comments_and_likes": "Komentarji in všečki", "comments_are_disabled": "Komentarji so onemogočeni", "common_create_new_album": "Ustvari nov album", - "common_server_error": "Preverite omrežno povezavo, preverite, ali je strežnik dosegljiv in ali sta različici aplikacije/strežnika združljivi.", "completed": "Končano", "confirm": "Potrdi", "confirm_admin_password": "Potrdite skrbniško geslo", @@ -737,6 +761,7 @@ "create": "Ustvari", "create_album": "Ustvari album", "create_album_page_untitled": "Brez naslova", + "create_api_key": "Ustvari API ključ", "create_library": "Ustvari knjižnico", "create_link": "Ustvari povezavo", "create_link_to_share": "Ustvari povezavo za skupno rabo", @@ -766,6 +791,7 @@ "daily_title_text_date_year": "E, MMM dd, yyyy", "dark": "Temno", "dark_theme": "Preklopi temno temo", + "date": "Datum", "date_after": "Datum po", "date_and_time": "Datum in ura", "date_before": "Datum pred", @@ -880,7 +906,6 @@ "edit_tag": "Uredi oznako", "edit_title": "Uredi naslov", "edit_user": "Uredi uporabnika", - "edited": "Urejeno", "editor": "Urejevalnik", "editor_close_without_save_prompt": "Spremembe ne bodo shranjene", "editor_close_without_save_title": "Zapri urejevalnik?", @@ -1036,6 +1061,7 @@ "exif_bottom_sheet_description_error": "Napaka pri posodabljanju opisa", "exif_bottom_sheet_details": "PODROBNOSTI", "exif_bottom_sheet_location": "LOKACIJA", + "exif_bottom_sheet_no_description": "Ni opisa", "exif_bottom_sheet_people": "OSEBE", "exif_bottom_sheet_person_add_person": "Dodaj ime", "exit_slideshow": "Zapustite diaprojekcijo", @@ -1074,6 +1100,7 @@ "features_setting_description": "Upravljaj funkcije aplikacije", "file_name": "Ime datoteke", "file_name_or_extension": "Ime ali končnica datoteke", + "file_size": "Velikost datoteke", "filename": "Ime datoteke", "filetype": "Vrsta datoteke", "filter": "Filter", @@ -1117,7 +1144,6 @@ "header_settings_field_validator_msg": "Vrednost ne sme biti prazna", "header_settings_header_name_input": "Ime glave", "header_settings_header_value_input": "Vrednost glave", - "headers_settings_tile_subtitle": "Določi proxy glavo, ki jo naj aplikacija pošlje ob vsaki mrežni zahtevi", "headers_settings_tile_title": "Proxy glave po meri", "hi_user": "Živijo {name} ({email})", "hide_all_people": "Skrij vse ljudi", @@ -1238,6 +1264,7 @@ "local_media_summary": "Povzetek lokalnih medijev", "local_network": "Lokalno omrežje", "local_network_sheet_info": "Aplikacija se bo povezala s strežnikom prek tega URL-ja, ko bo uporabljala navedeno omrežje Wi-Fi", + "location": "Lokacija", "location_permission": "Dovoljenje za lokacijo", "location_permission_content": "Za uporabo funkcije samodejnega preklapljanja potrebuje Immich dovoljenje za natančno lokacijo, da lahko prebere ime trenutnega omrežja Wi-Fi", "location_picker_choose_on_map": "Izberi na zemljevidu", @@ -1342,6 +1369,8 @@ "minute": "minuta", "minutes": "Minute", "missing": "manjka", + "mobile_app": "Mobilna aplikacija", + "mobile_app_download_onboarding_note": "Prenesite spremljevalno mobilno aplikacijo z uporabo naslednjih možnosti", "model": "Model", "month": "Mesec", "monthly_title_text_date_format": "MMMM y", @@ -1360,6 +1389,8 @@ "my_albums": "Moji albumi", "name": "Ime", "name_or_nickname": "Ime ali vzdevek", + "navigate": "Navigacija", + "navigate_to_time": "Pomaknite se do časa", "network_requirement_photos_upload": "Uporaba mobilnih podatkov za varnostno kopiranje fotografij", "network_requirement_videos_upload": "Uporaba mobilnih podatkov za varnostno kopiranje videoposnetkov", "network_requirements": "Omrežne zahteve", @@ -1369,6 +1400,7 @@ "never": "nikoli", "new_album": "Nov album", "new_api_key": "Nov API ključ", + "new_date_range": "Novo časovno obdobje", "new_password": "Novo geslo", "new_person": "Nova oseba", "new_pin_code": "Nova PIN koda", @@ -1419,6 +1451,9 @@ "notifications": "Obvestila", "notifications_setting_description": "Upravljanje obvestil", "oauth": "OAuth", + "obtainium_configurator": "Konfigurator Obtainium", + "obtainium_configurator_instructions": "Z Obtainium namestite in posodobite aplikacijo za Android neposredno iz izdaje Immich GitHub. Ustvarite ključ API in izberite različico, da ustvarite svojo konfiguracijsko povezavo Obtainium", + "ocr": "Optično prepoznavanje znakov (OCR)", "official_immich_resources": "Immich uradni viri", "offline": "Brez povezave", "offset": "Odmik", @@ -1523,10 +1558,14 @@ "play_memories": "Predvajaj spomine", "play_motion_photo": "Predvajaj premikajočo fotografijo", "play_or_pause_video": "Predvajaj ali zaustavi video", + "play_original_video": "Predvajaj izvirni videoposnetek", + "play_original_video_setting_description": "Prednostno predvajajte originalne videoposnetke pred prekodiranimi. Če originalno sredstvo ni združljivo, se morda ne bo pravilno predvajalo.", + "play_transcoded_video": "Predvajaj prekodiran video", "please_auth_to_access": "Za dostop se prijavite", "port": "Vrata", "preferences_settings_subtitle": "Upravljaj nastavitve aplikacije", "preferences_settings_title": "Nastavitve", + "preparing": "Priprava", "preset": "Prednastavitev", "preview": "Predogled", "previous": "Prejšnj-a/-i", @@ -1539,13 +1578,9 @@ "privacy": "Zasebnost", "profile": "Profil", "profile_drawer_app_logs": "Dnevniki", - "profile_drawer_client_out_of_date_major": "Mobilna aplikacija je zastarela. Posodobite na najnovejšo glavno različico.", - "profile_drawer_client_out_of_date_minor": "Mobilna aplikacija je zastarela. Posodobite na najnovejšo manjšo različico.", "profile_drawer_client_server_up_to_date": "Odjemalec in strežnik sta posodobljena", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Način samo za branje je omogočen. Za izhod dolgo pritisnite ikono uporabniškega avatarja.", - "profile_drawer_server_out_of_date_major": "Strežnik je zastarel. Posodobite na najnovejšo glavno različico.", - "profile_drawer_server_out_of_date_minor": "Strežnik je zastarel. Posodobite na najnovejšo manjšo različico.", "profile_image_of_user": "Profilna slika uporabnika {user}", "profile_picture_set": "Profilna slika nastavljena.", "public_album": "Javni album", @@ -1592,6 +1627,7 @@ "read_changelog": "Preberi dnevnik sprememb", "readonly_mode_disabled": "Način samo za branje je onemogočen", "readonly_mode_enabled": "Način samo za branje je omogočen", + "ready_for_upload": "Pripravljeno za nalaganje", "reassign": "Prerazporedi", "reassigned_assets_to_existing_person": "Ponovno dodeljeno {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}} za {name, select, null {an existing person} other {{name}}}", "reassigned_assets_to_new_person": "Ponovno dodeljeno {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}} za novo osebo", @@ -1661,6 +1697,7 @@ "reset_sqlite_confirmation": "Ali ste prepričani, da želite ponastaviti bazo podatkov SQLite? Za ponovno sinhronizacijo podatkov se boste morali odjaviti in znova prijaviti", "reset_sqlite_success": "Uspešno ponastavljena baza podatkov SQLite", "reset_to_default": "Ponastavi na privzeto", + "resolution": "Ločljivost", "resolve_duplicates": "Razreši dvojnike", "resolved_all_duplicates": "Razrešeni vsi dvojniki", "restore": "Obnovi", @@ -1679,6 +1716,7 @@ "running": "V teku", "save": "Shrani", "save_to_gallery": "Shrani v galerijo", + "saved": "Shranjeno", "saved_api_key": "Shranjen API ključ", "saved_profile": "Shranjen profil", "saved_settings": "Shranjene nastavitve", @@ -1695,6 +1733,9 @@ "search_by_description_example": "Pohodniški dan v Sapi", "search_by_filename": "Iskanje po imenu datoteke ali priponi", "search_by_filename_example": "na primer IMG_1234.JPG ali PNG", + "search_by_ocr": "Iskanje po optičnem prepoznavanju znakov (OCR)", + "search_by_ocr_example": "Latte", + "search_camera_lens_model": "Iskanje modela objektiva...", "search_camera_make": "Iskanje proizvajalca kamere...", "search_camera_model": "Išči model kamere...", "search_city": "Iskanje mesta...", @@ -1711,6 +1752,7 @@ "search_filter_location_title": "Izberi lokacijo", "search_filter_media_type": "Vrsta medija", "search_filter_media_type_title": "Izberi vrsto medija", + "search_filter_ocr": "Iskanje po optičnem prepoznavanju znakov (OCR)", "search_filter_people_title": "Izberi osebe", "search_for": "Poišči za", "search_for_existing_person": "Iskanje obstoječe osebe", @@ -1773,6 +1815,7 @@ "server_online": "Strežnik povezan", "server_privacy": "Zasebnost strežnika", "server_stats": "Statistika strežnika", + "server_update_available": "Posodobitev strežnika je na voljo", "server_version": "Različica strežnika", "set": "Nastavi", "set_as_album_cover": "Nastavi kot naslovnico albuma", @@ -1801,6 +1844,8 @@ "setting_notifications_subtitle": "Prilagodite svoje nastavitve obvestil", "setting_notifications_total_progress_subtitle": "Splošni napredek nalaganja (končano/skupaj sredstev)", "setting_notifications_total_progress_title": "Prikaži skupni napredek varnostnega kopiranja v ozadju", + "setting_video_viewer_auto_play_subtitle": "Samodejno začni predvajati videoposnetke, ko jih odpreš", + "setting_video_viewer_auto_play_title": "Samodejno predvajanje videoposnetkov", "setting_video_viewer_looping_title": "V zanki", "setting_video_viewer_original_video_subtitle": "Ko pretakate video iz strežnika, predvajajte izvirnik, tudi če je na voljo prekodiranje. Lahko povzroči medpomnjenje. Videoposnetki, ki so na voljo lokalno, se predvajajo v izvirni kakovosti ne glede na to nastavitev.", "setting_video_viewer_original_video_title": "Vsili izvirni video", @@ -1980,6 +2025,7 @@ "theme_setting_three_stage_loading_title": "Omogoči tristopenjsko nalaganje", "they_will_be_merged_together": "Združeni bodo skupaj", "third_party_resources": "Viri tretjih oseb", + "time": "Čas", "time_based_memories": "Časovni spomini", "timeline": "Časovnica", "timezone": "Časovni pas", @@ -2012,6 +2058,7 @@ "troubleshoot": "Odpravljanje težav", "type": "Vrsta", "unable_to_change_pin_code": "PIN kode ni mogoče spremeniti", + "unable_to_check_version": "Ni mogoče preveriti različice aplikacije ali strežnika", "unable_to_setup_pin_code": "PIN kode ni mogoče nastaviti", "unarchive": "Odstrani iz arhiva", "unarchive_action_prompt": "{count} odstranjenih iz arhiva", diff --git a/i18n/sr_Cyrl.json b/i18n/sr_Cyrl.json index 9c4d01dc3d..8a14e18de5 100644 --- a/i18n/sr_Cyrl.json +++ b/i18n/sr_Cyrl.json @@ -1,160 +1,176 @@ { "about": "О апликацији", "account": "Налог", - "account_settings": "Подешавања за Профил", + "account_settings": "Подешавања налога", "acknowledge": "Потврди", "action": "Поступак", - "action_common_update": "Упdate", + "action_common_update": "Ажурирај", "actions": "Поступци", "active": "Активни", "activity": "Активност", - "activity_changed": "Активност је {enabled, select, true {омогуц́ена} other {oneмогуц́ена}}", + "activity_changed": "Активност је {enabled, select, true {омогућена} other {онемогућена}}", "add": "Додај", "add_a_description": "Додај опис", - "add_a_location": "Додај Локацију", + "add_a_location": "Додај локацију", "add_a_name": "Додај име", "add_a_title": "Додај наслов", - "add_endpoint": "Додајте крајњу тачку", - "add_exclusion_pattern": "Додајте образац изузимања", - "add_import_path": "Додај путању за преузимање", + "add_birthday": "Додај рођендан", + "add_endpoint": "Додај адресу", + "add_exclusion_pattern": "Додај образац изузимања", + "add_import_path": "Додај путању увоза", "add_location": "Додај локацију", "add_more_users": "Додај кориснике", - "add_partner": "Додај партнер", + "add_partner": "Додај партнера", "add_path": "Додај путању", "add_photos": "Додај фотографије", + "add_tag": "Додај ознаку", "add_to": "Додај у…", "add_to_album": "Додај у албум", "add_to_album_bottom_sheet_added": "Додато у {album}", "add_to_album_bottom_sheet_already_exists": "Већ у {album}", - "add_to_shared_album": "Додај у дељен албум", + "add_to_album_bottom_sheet_some_local_assets": "Неке локалне датотеке није могуће додати у албум", + "add_to_album_toggle": "Обрни избор за {album}", + "add_to_albums": "Додај у албуме", + "add_to_albums_count": "Додај у албуме ({count})", + "add_to_shared_album": "Додај у дељени албум", + "add_upload_to_stack": "Додај пренесено у групу", "add_url": "Додај URL", "added_to_archive": "Додато у архиву", "added_to_favorites": "Додато у фаворите", "added_to_favorites_count": "Додато {count, number} у фаворите", "admin": { "add_exclusion_pattern_description": "Додајте обрасце искључења. Кориштење *, ** и ? је подржано. Да бисте игнорисали све датотеке у било ком директоријуму под називом „Рав“, користите „**/Рав/**“. Да бисте игнорисали све датотеке које се завршавају на „.тиф“, користите „**/*.тиф“. Да бисте игнорисали апсолутну путању, користите „/патх/то/игноре/**“.", - "asset_offline_description": "Ово екстерно библиотечко средство се више не налази на диску и премештено је у смец́е. Ако је датотека премештена унутар библиотеке, проверите своју временску линију за ново одговарајуц́е средство. Да бисте вратили ово средство, уверите се да Immich може да приступи доле наведеној путањи датотеке и скенирајте библиотеку.", + "admin_user": "Администратор", + "asset_offline_description": "Ова датотека спољне библиотеке се више не налази на диску и премештена је у смеће. Ако је датотека премештена унутар библиотеке, проверите своју временску линију за нову одговарајућу датотеку. Да бисте вратили ову датотеку, уверите се да Immich може да приступи доле наведеној путањи датотеке и скенирајте библиотеку.", "authentication_settings": "Подешавања за аутентификацију", "authentication_settings_description": "Управљајте лозинком, OAuth-ом и другим подешавањима аутентификације", - "authentication_settings_disable_all": "Да ли сте сигурни да желите да oneмогуц́ите све методе пријављивања? Пријава ц́е бити потпуно oneмогуц́ена.", - "authentication_settings_reenable": "Да бисте поново омогуц́или, користите команду сервера.", + "authentication_settings_disable_all": "Да ли сте сигурни да желите да онемогућите све методе пријављивања? Пријава ће бити потпуно онемогућена.", + "authentication_settings_reenable": "Да бисте поново омогућили, користите команду сервера.", "background_task_job": "Позадински задаци", "backup_database": "Креирајте резервну копију базе података", - "backup_database_enable_description": "Омогуц́и дампове базе података", + "backup_database_enable_description": "Омогући дампове базе података", "backup_keep_last_amount": "Количина претходних дампова које треба задржати", + "backup_onboarding_1_description": "копија изван места - у облаку или на другом физичком месту.", + "backup_onboarding_title": "Резервне копије", "backup_settings": "Подешавања дампа базе података", - "backup_settings_description": "Управљајте подешавањима дампа базе података. Напомена: Ови послови се не прате и нец́ете бити обавештени о неуспеху.", - "cleared_jobs": "Очишц́ени послови за: {job}", + "backup_settings_description": "Уреди подешавања дампа базе података.", + "cleared_jobs": "Очишћени послови за: {job}", "config_set_by_file": "Конфигурацију тренутно поставља конфигурациони фајл", "confirm_delete_library": "Да ли стварно желите да избришете библиотеку {library} ?", - "confirm_delete_library_assets": "Да ли сте сигурни да желите да избришете ову библиотеку? Ово ц́е избрисати {count, plural, one {1 садржену датотеку} few {# садржене датотеке} other {# садржених датотека}} из Immich-a и акција се не може опозвати. Датотеке ц́е остати на диску.", + "confirm_delete_library_assets": "Да ли сте сигурни да желите да избришете ову библиотеку? Ово ће избрисати {count, plural, one {1 садржену датотеку} few {# садржене датотеке} other {# садржених датотека}} из Immich-a и акција се не може опозвати. Датотеке ће остати на диску.", "confirm_email_below": "Да бисте потврдили, унесите \"{email}\" испод", - "confirm_reprocess_all_faces": "Да ли сте сигурни да желите да поново обрадите сва лица? Ово ц́е такође обрисати именоване особе.", + "confirm_reprocess_all_faces": "Да ли сте сигурни да желите да поново обрадите сва лица? Ово ће такође обрисати именоване особе.", "confirm_user_password_reset": "Да ли сте сигурни да желите да ресетујете лозинку корисника {user}?", "confirm_user_pin_code_reset": "Да ли сте сигурни да желите да ресетујете ПИН код корисника {user}?", "create_job": "Креирајте посао", "cron_expression": "Црон израз (еxпрессион)", - "cron_expression_description": "Подесите интервал скенирања користец́и црон формат. За више информација погледајте нпр. Цронтаб Гуру", + "cron_expression_description": "Подесите интервал скенирања користећи cron формат. За више информација погледајте нпр. Crontab Guru", "cron_expression_presets": "Предефинисана подешавања Црон израза (еxпрессион)", - "disable_login": "Онемогуц́и пријаву", + "disable_login": "Онемогући пријаву", "duplicate_detection_job_description": "Покрените машинско учење на средствима да бисте открили сличне слике. Ослања се на паметну претрагу", - "exclusion_pattern_description": "Обрасци изузимања вам омогуц́авају да игноришете датотеке и фасцикле када скенирате библиотеку. Ово је корисно ако имате фасцикле које садрже датотеке које не желите да увезете, као што су РАW датотеке.", + "exclusion_pattern_description": "Обрасци изузимања вам омогућавају да игноришете датотеке и фасцикле када скенирате библиотеку. Ово је корисно ако имате фасцикле које садрже датотеке које не желите да увезете, као што су RAW датотеке.", "external_library_management": "Управљање екстерним библиотекама", "face_detection": "Детекција лица", - "face_detection_description": "Откријте лица у датотекама помоц́у машинског учења. За видео снимке се узима у обзир само сличица. „Освежи“ (поновно) обрађује све датотеке. „Ресетовање“ додатно брише све тренутне податке о лицу. „Недостају“ датотеке у реду које још нису обрађене. Откривена лица ц́е бити стављена у ред за препознавање лица након што се препознавање лица заврши, групишуц́и их у постојец́е или нове особе.", - "facial_recognition_job_description": "Група је детектовала лица и додала их постојец́им особама. Овај корак се покрец́е након што је препознавање лица завршено. „Ресетуј“ (поновно) групише сва лица. „Недостају“ лица у редовима којима није додељена особа.", + "face_detection_description": "Откријте лица у датотекама помоћу машинског учења. За видео снимке се узима у обзир само сличица. „Освежи“ (поновно) обрађује све датотеке. „Ресетовање“ додатно брише све тренутне податке о лицу. „Недостајуће“ додаје датотеке које још нису обрађене. Откривена лица ће бити стављена у ред за препознавање лица након што се откривање лица заврши, групишући их у постојеће или нове особе.", + "facial_recognition_job_description": "Групише откривана лица и додаје их постојећим особама. Овај корак се покреће након што је препознавање лица завршено. „Ресетуј“ (поновно) групише сва лица. „Недостајућа“ додаје лица којима није додељена особа.", "failed_job_command": "Команда {command} није успела за посао: {job}", - "force_delete_user_warning": "УПОЗОРЕНЈЕ: Ово ц́е одмах уклонити корисника и све датотеке. Ово се не може опозвати и датотеке се не могу опоравити.", + "force_delete_user_warning": "УПОЗОРЕЊЕ: Ово ће одмах уклонити корисника и све датотеке. Ово се не може опозвати и датотеке се не могу опоравити.", "image_format": "Формат", "image_format_description": "WебП производи мање датотеке од ЈПЕГ, али се спорије кодира.", - "image_fullsize_description": "Слика у пуној величини са огољеним метаподацима, користи се када је увец́ана", - "image_fullsize_enabled": "Омогуц́ите генерисање слике у пуној величини", - "image_fullsize_enabled_description": "Генеришите слику пуне величине за формате који нису прилагођени вебу. Када је „Преферирај уграђени преглед“ омогуц́ен, уграђени прегледи се користе директно без конверзије. Не утиче на формате прилагођене вебу као што је ЈПЕГ.", - "image_fullsize_quality_description": "Квалитет слике у пуној величини од 1-100. Више је боље, али производи вец́е датотеке.", + "image_fullsize_description": "Слика у пуној величини са огољеним метаподацима, користи се када је увећана", + "image_fullsize_enabled": "Омогући генерисање слика у пуној величини", + "image_fullsize_enabled_description": "Генерише слику пуне величине за формате који нису прилагођени вебу. Када је „Преферирај уграђени преглед“ омогућен, уграђени прегледи се користе непосредно без конверзије. Не утиче на формате прилагођене вебу као што је JPEG.", + "image_fullsize_quality_description": "Квалитет слике у пуној величини од 1 до 100. Више је боље, али производи веће датотеке.", "image_fullsize_title": "Подешавања слике у пуној величини", "image_prefer_embedded_preview": "Преферирајте уграђени преглед", "image_prefer_embedded_preview_setting_description": "Користите уграђене прегледе у РАW фотографије као улаз за обраду слике када су доступне. Ово може да произведе прецизније боје за неке слике, али квалитет прегледа зависи од камере и слика може имати више неправилности компресије.", "image_prefer_wide_gamut": "Преферирајте широк спектар", "image_prefer_wide_gamut_setting_description": "Користите Дисплаy П3 за сличице. Ово боље чува живописност слика са широким просторима боја, али слике могу изгледати другачије на старим уређајима са старом верзијом претраживача. сРГБ слике се чувају као сРГБ да би се избегле промене боја.", "image_preview_description": "Слика средње величине са уклоњеним метаподацима, која се користи приликом прегледа једног елемента и за машинско учење", - "image_preview_quality_description": "Квалитет прегледа од 1-100. Више је боље, али производи вец́е датотеке и може смањити одзив апликације. Постављање ниске вредности може утицати на квалитет машинског учења.", + "image_preview_quality_description": "Квалитет прегледа од 1 до 100. Више је боље, али производи веће датотеке и може успорити одзив апликације. Постављање ниске вредности може утицати на квалитет машинског учења.", "image_preview_title": "Подешавања прегледа", "image_quality": "Квалитет", "image_resolution": "Резолуција", - "image_resolution_description": "Вец́е резолуције могу да сачувају више детаља, али им је потребно више времена за кодирање, имају вец́е величине датотека и могу да смање одзив апликације.", + "image_resolution_description": "Веће резолуције могу да сачувају више детаља, али им је потребно више времена за кодирање, имају веће величине датотека и могу да успоре одзив апликације.", "image_settings": "Подешавања слике", "image_settings_description": "Управљајте квалитетом и резолуцијом генерисаних слика", "image_thumbnail_description": "Мала сличица са огољеним метаподацима, која се користи приликом прегледа група фотографија као што је главна временска линија", - "image_thumbnail_quality_description": "Квалитет сличица од 1-100. Више је боље, али производи вец́е датотеке и може смањити одзив апликације.", + "image_thumbnail_quality_description": "Квалитет сличица од 1 до 100. Више је боље, али производи веће датотеке и може успорити одзив апликације.", "image_thumbnail_title": "Подешавања сличица", "job_concurrency": "{job} паралелност", "job_created": "Посао креиран", "job_not_concurrency_safe": "Овај посао није безбедан да буде паралелно активан.", "job_settings": "Подешавања посла", - "job_settings_description": "Управљајте паралелношц́у послова", + "job_settings_description": "Управљајте паралелношћу послова", "job_status": "Статус посла", "jobs_delayed": "{jobCount, plural, one {# одложени} few {# одложена} other {# одложених}}", "jobs_failed": "{jobCount, plural, one {# неуспешни} few {# неуспешна} other {# неуспешних}}", "library_created": "Направљена библиотека: {library}", "library_deleted": "Библиотека је избрисана", - "library_import_path_description": "Одредите фасциклу за увоз. Ова фасцикла, укључујуц́и подфасцикле, биц́е скенирана за слике и видео записе.", + "library_import_path_description": "Одредите фасциклу за увоз. Ова фасцикла, укључујући подфасцикле, биће скенирана за слике и видео записе.", "library_scanning": "Периодично скенирање", "library_scanning_description": "Конфигуришите периодично скенирање библиотеке", - "library_scanning_enable_description": "Омогуц́ите периодично скенирање библиотеке", + "library_scanning_enable_description": "Омогући периодично скенирање библиотеке", "library_settings": "Спољна библиотека", "library_settings_description": "Управљајте подешавањима спољне библиотеке", "library_tasks_description": "Обављај задатке библиотеке", "library_watching_enable_description": "Пратите спољне библиотеке за промене датотека", - "library_watching_settings": "Надгледање библиотеке (ЕКСПЕРИМЕНТАЛНО)", + "library_watching_settings": "Праћење библиотеке [ЕКСПЕРИМЕНТАЛНО]", "library_watching_settings_description": "Аутоматски пратите промењене датотеке", - "logging_enable_description": "Омогуц́и евидентирање", - "logging_level_description": "Када је омогуц́ено, који ниво евиденције користити.", + "logging_enable_description": "Омогући евидентирање", + "logging_level_description": "Када је омогућено, који ниво евиденције користити.", "logging_settings": "Евидентирање", + "machine_learning_availability_checks": "Провере доступности", + "machine_learning_availability_checks_enabled": "Омогући провере доступности", + "machine_learning_availability_checks_interval": "Интервал провере", + "machine_learning_availability_checks_interval_description": "Интервал у милисекундама између провера доступности", + "machine_learning_availability_checks_timeout": "Време за чекање на одговор", + "machine_learning_availability_checks_timeout_description": "Време за чекање на одговор у милисекундама", "machine_learning_clip_model": "Модел ЦЛИП", "machine_learning_clip_model_description": "Назив ЦЛИП модела је наведен овде. Имајте на уму да морате поново да покренете посао „Паметно претраживање“ за све слике након промене модела.", "machine_learning_duplicate_detection": "Детекција дупликата", - "machine_learning_duplicate_detection_enabled": "Омогуц́ите откривање дупликата", - "machine_learning_duplicate_detection_enabled_description": "Ако је oneмогуц́ено, потпуно идентична средства ц́е и даље бити уклоњена.", + "machine_learning_duplicate_detection_enabled": "Омогући откривање дупликата", + "machine_learning_duplicate_detection_enabled_description": "Ако је онемогућено, потпуно идентичне датотеке ће и даље бити уклоњене.", "machine_learning_duplicate_detection_setting_description": "Користите уграђен ЦЛИП да бисте пронашли вероватне дупликате", - "machine_learning_enabled": "Омогуц́ите машинско учење", - "machine_learning_enabled_description": "Ако је oneмогуц́ено, све функције МЛ ц́е бити oneмогуц́ене без обзира на доле-наведена подешавања.", + "machine_learning_enabled": "Омогући машинско учење", + "machine_learning_enabled_description": "Ако је онемогућено, све функције МЛ ће бити онемогућене без обзира на доле наведена подешавања.", "machine_learning_facial_recognition": "Препознавање лица", "machine_learning_facial_recognition_description": "Откривање, препознавање и груписање лица на сликама", "machine_learning_facial_recognition_model": "Модел за препознавање лица", - "machine_learning_facial_recognition_model_description": "Модели су наведени у опадајуц́ем редоследу величине. Вец́и модели су спорији и користе више меморије, али дају боље резултате. Имајте на уму да морате поново да покренете задатак детекције лица за све слике након промене модела.", - "machine_learning_facial_recognition_setting": "Омогуц́ите препознавање лица", - "machine_learning_facial_recognition_setting_description": "Ако је oneмогуц́ено, слике нец́е бити кодиране за препознавање лица и нец́е попуњавати одељак Људи на страници Истражи.", + "machine_learning_facial_recognition_model_description": "Модели су наведени у опадајућем редоследу величине. Већи модели су спорији и користе више меморије, али дају боље резултате. Имајте на уму да морате поново да покренете задатак откривања лица за све слике након промене модела.", + "machine_learning_facial_recognition_setting": "Омогући препознавање лица", + "machine_learning_facial_recognition_setting_description": "Ако је онемогућено, слике неће бити кодиране за препознавање лица и неће попуњавати одељак Људи на страници Истражи.", "machine_learning_max_detection_distance": "Максимална удаљеност детекције", - "machine_learning_max_detection_distance_description": "Максимално растојање између две слике да се сматрају дупликатима, у распону од 0,001-0,1. Вец́е вредности ц́е открити више дупликата, али могу довести до лажних позитивних резултата.", + "machine_learning_max_detection_distance_description": "Највеће растојање (разлика) између две слике да би се сматрале дупликатима, у распону од 0,001 до 0,1. Веће вредности ће открити више дупликата, али могу довести до лажних позитивних резултата.", "machine_learning_max_recognition_distance": "Максимална удаљеност препознавања", - "machine_learning_max_recognition_distance_description": "Максимална удаљеност између два лица која се сматра истом особом, у распону од 0-2. Смањење овог броја може спречити означавање две особе као исте особе, док повец́ање може спречити етикетирање исте особе као две различите особе. Имајте на уму да је лакше спојити две особе него поделити једну особу на двоје, па погрешите на страни нижег прага када је то могуц́е.", + "machine_learning_max_recognition_distance_description": "Највећа удаљеност (разлика) између два лица која се сматрају истом особом, у распону од 0 до 2. Смањење овог броја може спречити означавање два лица као исте особе, док повећање може спречити означавање истог лица као две различите особе. Имајте на уму да је лакше спојити две особе него поделити једну особу на двоје, па је боље грешити ка нижем прагу када је то могуће.", "machine_learning_min_detection_score": "Најмањи резултат детекције", - "machine_learning_min_detection_score_description": "Минимални резултат поузданости за лице које треба открити од 0-1. Ниже вредности ц́е открити више лица, али могу довести до лажних позитивних резултата.", + "machine_learning_min_detection_score_description": "Најмања вредност поузданости за открување лица, од 0 до 1. Ниже вредности ће открити више лица, али могу довести до лажних позитивних резултата.", "machine_learning_min_recognized_faces": "Најмање препознатих лица", - "machine_learning_min_recognized_faces_description": "Минимални број препознатих лица за креирање особе. Повец́ање овога чини препознавање лица прецизнијим по цену повец́ања шансе да лице није додељено особи.", + "machine_learning_min_recognized_faces_description": "Најмањи број препознатих лица за прављење особе. Повећање овога чини препознавање лица прецизнијим по цену повећања вероватноће да лице не буде додељено особи.", "machine_learning_settings": "Подешавања машинског учења", "machine_learning_settings_description": "Управљајте функцијама и подешавањима машинског учења", "machine_learning_smart_search": "Паметна претрага", - "machine_learning_smart_search_description": "Потражите слике семантички користец́и уграђени ЦЛИП", - "machine_learning_smart_search_enabled": "Омогуц́ите паметну претрагу", - "machine_learning_smart_search_enabled_description": "Ако је oneмогуц́ено, слике нец́е бити кодиране за паметну претрагу.", - "machine_learning_url_description": "URL сервера за машинско учење. Ако је наведено више URL адреса, сваки сервер ц́е бити покушаван појединачно док не одговори успешно, редом од првог до последњег. Сервери који не одговоре биц́е привремено игнорисани док се поново не повежу са мрежом.", - "manage_concurrency": "Управљање паралелношц́у", + "machine_learning_smart_search_description": "Потраживање слика семантички користећи уграђени CLIP", + "machine_learning_smart_search_enabled": "Омогући паметну претрагу", + "machine_learning_smart_search_enabled_description": "Ако је онемогућено, слике неће бити кодиране за паметну претрагу.", + "machine_learning_url_description": "URL сервера за машинско учење. Ако је наведено више URL адреса, сваки сервер ће бити покушаван појединачно док не одговори успешно, редом од првог до последњег. Сервери који не одговоре биће привремено игнорисани док се поново не повежу са мрежом.", + "manage_concurrency": "Управљање паралелношћу", "manage_log_settings": "Управљајте подешавањима евиденције", "map_dark_style": "Тамни стил", - "map_enable_description": "Омогуц́ите карактеристике мапе", + "map_enable_description": "Омогући карактеристике мапе", "map_gps_settings": "Мап & ГПС подешавања", "map_gps_settings_description": "Управљајте поставкама мапе и ГПС-а (обрнуто геокодирање)", "map_implications": "Функција мапе се ослања на екстерну услугу плочица (тилес.иммицх.цлоуд)", "map_light_style": "Светли стил", "map_manage_reverse_geocoding_settings": "Управљајте подешавањима Обрнуто геокодирање", "map_reverse_geocoding": "Обрнуто геокодирање", - "map_reverse_geocoding_enable_description": "Омогуц́ите обрнуто геокодирање", + "map_reverse_geocoding_enable_description": "Омогући обрнуто геокодирање", "map_reverse_geocoding_settings": "Подешавања обрнутог геокодирања", "map_settings": "Подешавање мапе", "map_settings_description": "Управљајте подешавањима мапе", "map_style_description": "URL до стyле.јсон мапе тема изгледа", - "memory_cleanup_job": "Чишц́ење меморије", + "memory_cleanup_job": "Чишћење меморије", "memory_generate_job": "Генерација меморије", "metadata_extraction_job": "Извод метаподатака", "metadata_extraction_job_description": "Извуците информације о метаподацима из сваке датотеке, као што су ГПС, лица и резолуција", @@ -164,36 +180,47 @@ "metadata_settings_description": "Управљајте подешавањима метаподатака", "migration_job": "Миграције", "migration_job_description": "Пренесите сличице датотека и лица у најновију структуру директоријума", + "nightly_tasks_cluster_new_faces_setting": "Групиши нова лица", + "nightly_tasks_database_cleanup_setting": "Задаци чишћења базе података", + "nightly_tasks_generate_memories_setting": "Прављење успомена", + "nightly_tasks_generate_memories_setting_description": "Направи нове успомене из датотека", + "nightly_tasks_missing_thumbnails_setting": "Прављењњ недостајућих сличица", + "nightly_tasks_missing_thumbnails_setting_description": "Стави датотеке без сличица у ред за прављење сличица", + "nightly_tasks_settings": "Подешавања ноћних задатака", + "nightly_tasks_settings_description": "Управљај ноћним задацима", + "nightly_tasks_start_time_setting": "Време почетка", "no_paths_added": "Нема додатих путања", "no_pattern_added": "Није додат образац", "note_apply_storage_label_previous_assets": "Напомена: Да бисте применили ознаку за складиштење на претходно отпремљена средства, покрените", "note_cannot_be_changed_later": "НАПОМЕНА: Ово се касније не може променити!", "notification_email_from_address": "Са адресе", - "notification_email_from_address_description": "Адреса е-поште пошиљаоца, на пример: \"Immich фото сервер <нореплy@еxампле.цом>\"", + "notification_email_from_address_description": "Адреса е-поште пошиљаоца, на пример: \"Immich фото сервер \". Користи адресу са које смеш слати поруке.", "notification_email_host_description": "Хост сервера е-поште (нпр. смтп.иммицх.апп)", "notification_email_ignore_certificate_errors": "Занемарите грешке сертификата", "notification_email_ignore_certificate_errors_description": "Игноришите грешке у валидацији ТЛС сертификата (не препоручује се)", "notification_email_password_description": "Лозинка за употребу при аутентификацији са сервером е-поште", "notification_email_port_description": "Порт сервера е-поште (нпр. 25, 465 или 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "Користи SMTPS (SMTP преко TLS)", "notification_email_sent_test_email_button": "Пошаљите пробну е-пошту и сачувајте", "notification_email_setting_description": "Подешавања за слање обавештења путем е-поште", "notification_email_test_email": "Пошаљите пробну е-пошту", "notification_email_test_email_failed": "Слање пробне е-поште није успело, проверите вредности", "notification_email_test_email_sent": "Пробна е-пошта је послата на {email}. Проверите своје пријемно сандуче.", "notification_email_username_description": "Корисничко име које се користи приликом аутентификације на серверу е-поште", - "notification_enable_email_notifications": "Омогуц́ите обавештења путем е-поште", + "notification_enable_email_notifications": "Омогући обавештења путем е-поште", "notification_settings": "Подешавања обавештења", - "notification_settings_description": "Управљајте подешавањима обавештења, укључујуц́и е-пошту", + "notification_settings_description": "Управљајте подешавањима обавештења, укључујући е-пошту", "oauth_auto_launch": "Аутоматско покретање", "oauth_auto_launch_description": "Покрените OAuth ток пријављивања аутоматски након навигације на страницу за пријаву", "oauth_auto_register": "Аутоматска регистрација", - "oauth_auto_register_description": "Аутоматски региструјте нове кориснике након што се пријавите помоц́у OAuth-а", + "oauth_auto_register_description": "Аутоматски региструј нове кориснике након што се пријаве помоћу OAuth-а", "oauth_button_text": "Текст дугмета", "oauth_client_secret_description": "Потребно ако OAuth провајдер не подржава ПКЦЕ (Прооф Кеy фор Цоде Еxцханге)", - "oauth_enable_description": "Пријавите се помоц́у OAuth-а", + "oauth_enable_description": "Пријава помоћу OAuth", "oauth_mobile_redirect_uri": "УРИ за преусмеравање мобилних уређаја", "oauth_mobile_redirect_uri_override": "Замена УРИ-ја мобилног преусмеравања", - "oauth_mobile_redirect_uri_override_description": "Омогуц́и када OAuth добављач (провидер) не дозвољава мобилни УРИ, као што је '{цаллбацк}'", + "oauth_mobile_redirect_uri_override_description": "Омогући када OAuth добављач (провајдер) не дозвољава мобилни URI, као што је \"{callback}\"", "oauth_settings": "ОАуторизација", "oauth_settings_description": "Управљајте подешавањима за пријаву са ОАуторизацијом", "oauth_settings_more_details": "За више детаља о овој функцији погледајте документе.", @@ -202,18 +229,18 @@ "oauth_storage_quota_claim": "Захтев за квоту складиштења", "oauth_storage_quota_claim_description": "Аутоматски подесите квоту меморијског простора корисника на вредност овог захтева.", "oauth_storage_quota_default": "Подразумевана квота за складиштење (ГиБ)", - "oauth_storage_quota_default_description": "Квота у ГиБ која се користи када нема потраживања (унесите 0 за неограничену квоту).", + "oauth_storage_quota_default_description": "Квота у GiB која се користи када није задата преко OAuth.", "oauth_timeout": "Временско ограничење захтева", "oauth_timeout_description": "Временско ограничење за захтеве у милисекундама", - "password_enable_description": "Пријавите се помоц́у е-поште и лозинке", + "password_enable_description": "Пријава помоћу е-поште и лозинке", "password_settings": "Лозинка за пријаву", "password_settings_description": "Управљајте подешавањима за пријаву лозинком", "paths_validated_successfully": "Све путање су успешно потврђене", - "person_cleanup_job": "Чишц́ење особа", + "person_cleanup_job": "Чишћење особа", "quota_size_gib": "Величина квоте (ГиБ)", "refreshing_all_libraries": "Освежавање свих библиотека", "registration": "Регистрација администратора", - "registration_description": "Пошто сте први корисник на систему, биц́ете додељени као Админ и одговорни сте за административне задатке, а додатне кориснике ц́ете креирати ви.", + "registration_description": "Пошто сте први корисник на систему, бићете додељени као Админ и одговорни сте за административне задатке, а додатне кориснике ћете креирати ви.", "require_password_change_on_login": "Захтевати од корисника да промени лозинку при првом пријављивању", "reset_settings_to_default": "Ресетујте подешавања на подразумеване вредности", "reset_settings_to_recent_saved": "Ресетујте подешавања на недавно сачувана подешавања", @@ -221,9 +248,9 @@ "search_jobs": "Тражи послове…", "send_welcome_email": "Пошаљите е-пошту добродошлице", "server_external_domain_settings": "Екстерни домаин", - "server_external_domain_settings_description": "Домаин за јавне дељене везе, укључујуц́и хттп(с)://", + "server_external_domain_settings_description": "Домен за јавне дељене везе, укључујући https(s)://", "server_public_users": "Јавни корисници", - "server_public_users_description": "Сви корисници (име и адреса е-поште) су наведени приликом додавања корисника у дељене албуме. Када је oneмогуц́ена, листа корисника ц́е бити доступна само администраторима.", + "server_public_users_description": "Сви корисници (име и адреса е-поште) су наведени приликом додавања корисника у дељене албуме. Када је онемогућен, списак корисника ће бити доступан само администраторима.", "server_settings": "Подешавања сервера", "server_settings_description": "Управљајте подешавањима сервера", "server_welcome_message": "Порука добродошлице", @@ -234,12 +261,12 @@ "smart_search_job_description": "Покрените машинско учење на датотекама да бисте подржали паметну претрагу", "storage_template_date_time_description": "Временска ознака креирања датотеке се користи за информације о датуму и времену", "storage_template_date_time_sample": "Пример времена {date}", - "storage_template_enable_description": "Омогуц́и механизам за шаблone за складиштење", + "storage_template_enable_description": "Омогући механизам за обрасце за складиштење", "storage_template_hash_verification_enabled": "Хеш верификација омогућена", - "storage_template_hash_verification_enabled_description": "Омогуц́ава хеш верификацију, не oneмогуц́авајте ово осим ако нисте сигурни у последице", + "storage_template_hash_verification_enabled_description": "Омогућава хеш верификацију, не онемогућавајте ово осим ако нисте сигурни у последице", "storage_template_migration": "Миграција шаблона за складиштење", "storage_template_migration_description": "Примените тренутни {template} на претходно отпремљене елементе", - "storage_template_migration_info": "Промене шаблона ц́е се применити само на нове датотеке. Да бисте ретроактивно применили шаблон на претходно отпремљене датотеке, покрените {job}.", + "storage_template_migration_info": "Промене шаблона ће се применити само на нове датотеке. Да бисте ретроактивно применили шаблон на претходно отпремљене датотеке, покрените {job}.", "storage_template_migration_job": "Посао миграције складишта", "storage_template_more_details": "За више детаља о овој функцији погледајте Шаблон за складиште и његове импликације", "storage_template_path_length": "Приближно ограничење дужине путање: {length, number}/{limit, number}", @@ -247,9 +274,9 @@ "storage_template_settings_description": "Управљајте структуром директоријума и именом датотеке средства за отпремање", "storage_template_user_label": "{label} је ознака за складиштење корисника", "system_settings": "Подешавања система", - "tag_cleanup_job": "Чишц́ење ознака (tags)", - "template_email_available_tags": "Можете да користите следец́е променљиве у свом шаблону: {tags}", - "template_email_if_empty": "Ако је шаблон празан, користиц́е се подразумевана адреса е-поште.", + "tag_cleanup_job": "Чишћење ознака", + "template_email_available_tags": "Можете да користите следеће променљиве у свом шаблону: {tags}", + "template_email_if_empty": "Ако је шаблон празан, користиће се подразумевана адреса е-поште.", "template_email_invite_album": "Шаблон за позив у албум", "template_email_preview": "Преглед", "template_email_settings": "Шаблони е-поште", @@ -258,95 +285,95 @@ "template_settings": "Шаблони обавештења", "template_settings_description": "Управљајте прилагођеним шаблонима за обавештења", "theme_custom_css_settings": "Прилагођени ЦСС", - "theme_custom_css_settings_description": "Каскадни листови стилова (ЦСС) омогуц́авају прилагођавање дизајна Immich-a.", + "theme_custom_css_settings_description": "Каскадни листови стилова (ЦСС) омогућавају прилагођавање дизајна Immich-a.", "theme_settings": "Подешавање тема", "theme_settings_description": "Управљајте прилагођавањем Immich wеб интерфејса", "thumbnail_generation_job": "Генеришите сличице", - "thumbnail_generation_job_description": "Генеришите велике, мале и замуц́ене сличице за свако средство, као и сличице за сваку особу", + "thumbnail_generation_job_description": "Генеришите велике, мале и замућене сличице за свако средство, као и сличице за сваку особу", "transcoding_acceleration_api": "АПИ за убрзање", - "transcoding_acceleration_api_description": "АПИ који ц́е комуницирати са вашим уређајем да би убрзао транскодирање. Ово подешавање је 'најбољи напор': врац́а се на софтверско транскодирање у случају неуспеха. VP9 може или не мора да ради у зависности од вашег хардвера.", + "transcoding_acceleration_api_description": "АПИ који ће комуницирати са вашим уређајем да би убрзао транскодирање. Ово подешавање је 'најбољи напор': враћа се на софтверско транскодирање у случају неуспеха. VP9 може или не мора да ради у зависности од вашег хардвера.", "transcoding_acceleration_nvenc": "НВЕНЦ (захтева НВИДИА ГПУ)", "transcoding_acceleration_qsv": "Qуицк Сyнц (захтева Интел CPU 7. генерације или новији)", "transcoding_acceleration_rkmpp": "РКМПП (само на Роцкцхип СОЦ-овима)", "transcoding_acceleration_vaapi": "Видео акцелерација АПИ (ВААПИ)", - "transcoding_accepted_audio_codecs": "Прихвац́ени аудио кодеци", + "transcoding_accepted_audio_codecs": "Прихваћени аудио кодеци", "transcoding_accepted_audio_codecs_description": "Изаберите које аудио кодеке не треба транскодирати. Користи се само за одређене политике транскодирања.", - "transcoding_accepted_containers": "Прихвац́ени контејнери", + "transcoding_accepted_containers": "Прихваћени контејнери", "transcoding_accepted_containers_description": "Изаберите који формати контејнера не морају да се ремуксују у МП4. Користи се само за одређене услове транскодирања.", - "transcoding_accepted_video_codecs": "Прихвац́ени видео кодеци", + "transcoding_accepted_video_codecs": "Прихваћени видео кодеци", "transcoding_accepted_video_codecs_description": "Изаберите које видео кодеке није потребно транскодирати. Користи се само за одређене политике транскодирања.", - "transcoding_advanced_options_description": "Опције које вец́ина корисника не би требало да мењају", + "transcoding_advanced_options_description": "Опције које већина корисника не би требало да мењају", "transcoding_audio_codec": "Аудио кодек", "transcoding_audio_codec_description": "Опус је опција највишег квалитета, али има лошију компатибилност са старим уређајима или софтвером.", - "transcoding_bitrate_description": "Видео снимци вец́и од максималне брзине преноса или нису у прихвац́еном формату", + "transcoding_bitrate_description": "Видео снимци већи од максималне брзине преноса или нису у прихваћеном формату", "transcoding_codecs_learn_more": "Да бисте сазнали више о терминологији која се овде користи, погледајте ФФмпег документацију за H.264 кодек, HEVC кодек и VP9 кодек.", "transcoding_constant_quality_mode": "Режим константног квалитета", - "transcoding_constant_quality_mode_description": "ИЦQ је бољи од ЦQП-а, али неки уређаји за хардверско убрзање не подржавају овај режим. Подешавање ове опције ц́е преферирати наведени режим када се користи кодирање засновано на квалитету. НВЕНЦ игнорише јер не подржава ИЦQ.", + "transcoding_constant_quality_mode_description": "ИЦQ је бољи од ЦQП-а, али неки уређаји за хардверско убрзање не подржавају овај режим. Подешавање ове опције ће преферирати наведени режим када се користи кодирање засновано на квалитету. НВЕНЦ игнорише јер не подржава ИЦQ.", "transcoding_constant_rate_factor": "Фактор константне стопе (-црф)", - "transcoding_constant_rate_factor_description": "Ниво квалитета видеа. Типичне вредности су 23 за H.264, 28 за HEVC, 31 за VP9 и 35 за АВ1. Ниже је боље, али производи вец́е датотеке.", + "transcoding_constant_rate_factor_description": "Ниво квалитета видеа. Типичне вредности су 23 за H.264, 28 за HEVC, 31 за VP9 и 35 за АВ1. Ниже је боље, али производи веће датотеке.", "transcoding_disabled_description": "Немојте транскодирати ниједан видео, може прекинути репродукцију на неким клијентима", "transcoding_encoding_options": "Опције Кодирања", "transcoding_encoding_options_description": "Подесите кодеке, резолуцију, квалитет и друге опције за кодиране видео записе", "transcoding_hardware_acceleration": "Хардверско убрзање", - "transcoding_hardware_acceleration_description": "Екпериментално; много брже, али ц́е имати нижи квалитет при истој брзини преноса", + "transcoding_hardware_acceleration_description": "Експериментално: брже транскодирање али може смањити квалитет при истом протоку", "transcoding_hardware_decoding": "Хардверско декодирање", - "transcoding_hardware_decoding_setting_description": "Омогуц́ава убрзање од краја до краја уместо да само убрзава кодирање. Можда нец́е радити на свим видео снимцима.", + "transcoding_hardware_decoding_setting_description": "Омогућава убрзање од краја до краја уместо да само убрзава кодирање. Можда неће радити на свим видео снимцима.", "transcoding_max_b_frames": "Максимални Б-кадри", - "transcoding_max_b_frames_description": "Више вредности побољшавају ефикасност компресије, али успоравају кодирање. Можда није компатибилно са хардверским убрзањем на старијим уређајима. 0 oneмогуц́ава Б-кадре, док -1 аутоматски поставља ову вредност.", + "transcoding_max_b_frames_description": "Више вредности побољшавају ефикасност компресије, али успоравају кодирање. Можда није компатибилно са хардверским убрзањем на старијим уређајима. 0 онемогућава Б-кадре, док -1 аутоматски поставља ову вредност.", "transcoding_max_bitrate": "Максимални битрате", - "transcoding_max_bitrate_description": "Подешавање максималног битрате-а може учинити величине датотека предвидљивијим уз мању цену квалитета. При 720п, типичне вредности су 2600к за VP9 или HEVC, или 4500к за H.264. Онемогуц́ено ако је постављено на 0.", + "transcoding_max_bitrate_description": "Подешавање највећег протока може учинити величине датотека предвидљивијим по цену незнатно нижег квалитета. При 720p, типичне вредности су 2600 kbit/s за VP9 или HEVC, или 4500 kbit/s за H.264. Онемогућено ако је постављено на 0. Када није задата јединица мере, k (за kbit/s) се подразумева; тако да 5000, 5000k и 5M представљају исту вредност.", "transcoding_max_keyframe_interval": "Максимални интервал кеyфраме-а", "transcoding_max_keyframe_interval_description": "Поставља максималну удаљеност кадрова између кључних кадрова. Ниже вредности погоршавају ефикасност компресије, али побољшавају време тражења и могу побољшати квалитет сцена са брзим кретањем. 0 аутоматски поставља ову вредност.", - "transcoding_optimal_description": "Видео снимци вец́и од циљне резолуције или нису у прихвац́еном формату", + "transcoding_optimal_description": "Видео снимци већи од циљне резолуције или нису у прихваћеном формату", "transcoding_policy": "Услови Транскодирања", "transcoding_policy_description": "Одреди кад да се транскодира видео", "transcoding_preferred_hardware_device": "Жељени хардверски уређај", "transcoding_preferred_hardware_device_description": "Односи се само на ВААПИ и QСВ. Поставља дри ноде који се користи за хардверско транскодирање.", "transcoding_preset_preset": "Унапред подешена подешавања (-пресет)", - "transcoding_preset_preset_description": "Брзина компресије. Спорије унапред подешене вредности производе мање датотеке и повец́авају квалитет када циљате одређену брзину преноса. VP9 игнорише брзине изнад 'брже'.", + "transcoding_preset_preset_description": "Брзина компресије. Спорије унапред подешене вредности производе мање датотеке и повећавају квалитет када циљате одређену брзину преноса. VP9 игнорише брзине изнад 'брже'.", "transcoding_reference_frames": "Референтни оквири (фрамес)", "transcoding_reference_frames_description": "Број оквира (фрамес) за референцу приликом компресије датог оквира. Више вредности побољшавају ефикасност компресије, али успоравају кодирање. 0 аутоматски поставља ову вредност.", - "transcoding_required_description": "Само видео снимци који нису у прихвац́еном формату", + "transcoding_required_description": "Само видео снимци који нису у прихваћеном формату", "transcoding_settings": "Подешавања видео транскодирања", "transcoding_settings_description": "Управљајте резолуцијом и информацијама о кодирању видео датотека", "transcoding_target_resolution": "Циљана резолуција", - "transcoding_target_resolution_description": "Вец́е резолуције могу да сачувају више детаља, али им је потребно више времена за кодирање, имају вец́е величине датотека и могу да смање брзину апликације.", + "transcoding_target_resolution_description": "Веће резолуције могу да сачувају више детаља, али им је потребно више времена за кодирање, имају веће величине датотека и могу да смање брзину апликације.", "transcoding_temporal_aq": "Временски (Темпорал) АQ", - "transcoding_temporal_aq_description": "Односи се само на НВЕНЦ. Повец́ава квалитет сцена са високим детаљима и ниским покретима. Можда није компатибилан са старијим уређајима.", + "transcoding_temporal_aq_description": "Односи се само на NVENC. Временска адаптивна квантизација (Temporal Adaptive Quantization) повећава квалитет сцена са високим детаљима и спорим покретима. Можда није компатибилан са старијим уређајима.", "transcoding_threads": "Нити (тхреадс)", - "transcoding_threads_description": "Више вредности доводе до бржег кодирања, али остављају мање простора серверу за обраду других задатака док је активан. Ова вредност не би требало да буде вец́а од броја CPU језгара. Максимизира искоришц́еност ако је подешено на 0.", + "transcoding_threads_description": "Више вредности доводе до бржег кодирања, али остављају мање простора серверу за обраду других задатака док је активан. Ова вредност не би требало да буде већа од броја CPU језгара. Максимизира искоришћеност ако је подешено на 0.", "transcoding_tone_mapping": "Мапирање (тone-маппинг)", "transcoding_tone_mapping_description": "Покушава да се сачува изглед ХДР видео записа када се конвертују у СДР. Сваки алгоритам прави различите компромисе за боју, детаље и осветљеност. Хабле чува детаље, Мобиус чува боју, а Раеинхард светлину.", "transcoding_transcode_policy": "Услови транскодирања", - "transcoding_transcode_policy_description": "Услови о томе када видео треба транскодирати. ХДР видео снимци ц́е увек бити транскодирани (осим ако је транскодирање oneмогуц́ено).", + "transcoding_transcode_policy_description": "Услови о томе када видео треба транскодирати. ХДР видео снимци ће увек бити транскодирани (осим ако је транскодирање онемогућено).", "transcoding_two_pass_encoding": "Двопролазно кодирање", - "transcoding_two_pass_encoding_setting_description": "Транскодирајте у два пролаза да бисте произвели боље кодиране видео записе. Када је максимална брзина у битовима омогуц́ена (потребна за рад са H.264 и HEVC), овај режим користи опсег брзине у битовима заснован на максималној брзини (маx битрате) и игнорише ЦРФ. За VP9, ЦРФ се може користити ако је максимална брзина преноса oneмогуц́ена.", + "transcoding_two_pass_encoding_setting_description": "Транскодирајте у два пролаза да бисте произвели боље кодиране видео записе. Када је максимална брзина у битовима омогућена (потребна за рад са H.264 и HEVC), овај режим користи опсег брзине у битовима заснован на максималној брзини (маx битрате) и игнорише ЦРФ. За VP9, ЦРФ се може користити ако је максимална брзина преноса онемогућена.", "transcoding_video_codec": "Видео кодек", - "transcoding_video_codec_description": "VP9 има високу ефикасност и wеб компатибилност, али му је потребно више времена за транскодирање. HEVC ради слично, али има нижу wеб компатибилност. H.264 је широко компатибилан и брзо се транскодира, али производи много вец́е датотеке. АВ1 је најефикаснији кодек, али му недостаје подршка на старијим уређајима.", - "trash_enabled_description": "Омогуц́ите функције Отпада", + "transcoding_video_codec_description": "VP9 има високу ефикасност и веб компатибилност, али му је потребно више времена за транскодирање. HEVC ради слично, али има нижу wеб компатибилност. H.264 је широко компатибилан и брзо се транскодира, али производи много веће датотеке. AV1 је најефикаснији кодек, али недостаје подршка за њега на старијим уређајима.", + "trash_enabled_description": "Омогућите функције Отпада", "trash_number_of_days": "Број дана", "trash_number_of_days_description": "Број дана за држање датотека у отпаду пре него што их трајно уклоните", - "trash_settings": "Подешавања смец́а", - "trash_settings_description": "Управљајте подешавањима смец́а", - "user_cleanup_job": "Чишц́ење корисника", - "user_delete_delay": "Налог и датотеке {user} биц́е заказани за трајно брисање за {delay, plural, one {# дан} other {# дана}}.", + "trash_settings": "Подешавања Отпада", + "trash_settings_description": "Управљајте подешавањима Отпада", + "user_cleanup_job": "Чишћење корисника", + "user_delete_delay": "Налог и датотеке {user} биће заказани за трајно брисање за {delay, plural, one {# дан} other {# дана}}.", "user_delete_delay_settings": "Избриши уз кашњење", - "user_delete_delay_settings_description": "Број дана након уклањања за трајно брисање корисничког налога и датотека. Посао брисања корисника се покрец́е у поноц́ да би се проверили корисници који су спремни за брисање. Промене ове поставке ц́е бити процењене при следец́ем извршењу.", - "user_delete_immediately": "Налог и датотеке {user} ц́е бити стављени на чекање за трајно брисање одмах.", + "user_delete_delay_settings_description": "Број дана након уклањања за трајно брисање корисничког налога и датотека. Посао брисања корисника се покреће у поноћ да би се проверили корисници који су спремни за брисање. Промене ове поставке ће бити процењене при следећем извршењу.", + "user_delete_immediately": "Налог и датотеке {user} ће одмах бити стављени на чекање за трајно брисање.", "user_delete_immediately_checkbox": "Ставите корисника и датотеке у ред за тренутно брисање", "user_details": "Детаљи корисника", "user_management": "Управљање корисницима", "user_password_has_been_reset": "Лозинка корисника је ресетована:", - "user_password_reset_description": "Молимо да доставите привремену лозинку кориснику и обавестите га да ц́е морати да промени лозинку приликом следец́ег пријављивања.", - "user_restore_description": "Налог {user} ц́е бити врац́ен.", + "user_password_reset_description": "Молимо да доставите привремену лозинку кориснику и обавестите га да ће морати да промени лозинку приликом следећег пријављивања.", + "user_restore_description": "Налог {user} ће бити враћен.", "user_restore_scheduled_removal": "Врати корисника - заказано уклањање за {date, date, лонг}", "user_settings": "Подешавања корисника", "user_settings_description": "Управљајте корисничким подешавањима", "user_successfully_removed": "Корисник {email} је успешно уклоњен.", - "version_check_enabled_description": "Омогуц́ите проверу нових издања", + "version_check_enabled_description": "Омогући проверу нових издања", "version_check_implications": "Функција провере верзије се ослања на периодичну комуникацију са гитхуб.цом", "version_check_settings": "Провера верзије", - "version_check_settings_description": "Омогуц́ите/oneмогуц́ите обавештење о новој верзији", + "version_check_settings_description": "Омогући/онемогући обавештење о новој верзији", "video_conversion_job": "Транскодирање видео записа", "video_conversion_job_description": "Транскодирајте видео записе за ширу компатибилност са прегледачима и уређајима" }, @@ -357,16 +384,16 @@ "advanced_settings_enable_alternate_media_filter_subtitle": "Користите ову опцију за филтрирање медија током синхронизације на основу алтернативних критеријума. Покушајте ово само ако имате проблема са апликацијом да открије све албуме.", "advanced_settings_enable_alternate_media_filter_title": "[ЕКСПЕРИМЕНТАЛНО] Користите филтер за синхронизацију албума на алтернативном уређају", "advanced_settings_log_level_title": "Ниво евиденције (лог): {level}", - "advanced_settings_prefer_remote_subtitle": "Неки уређаји веома споро учитавају сличице са средстава на уређају. Активирајте ово подешавање да бисте уместо тога учитали удаљене слике.", + "advanced_settings_prefer_remote_subtitle": "Неки уређаји веома споро учитавају сличице локалних датотека. Укључи ово подешавање да би се уместо њих користиле слике са сервера.", "advanced_settings_prefer_remote_title": "Преферирајте удаљене слике", "advanced_settings_proxy_headers_subtitle": "Дефинишите прокси заглавља које Immich треба да пошаље са сваким мрежним захтевом", - "advanced_settings_proxy_headers_title": "Прокси Хеадери (хеадерс)", + "advanced_settings_proxy_headers_title": "Посебна прокси заглавља (headers) [ЕКСПЕРИМЕНТАЛНО]", "advanced_settings_self_signed_ssl_subtitle": "Прескаче верификацију SSL сертификата за крајњу тачку сервера. Обавезно за самопотписане сертификате.", - "advanced_settings_self_signed_ssl_title": "Дозволи самопотписане SSL сертификате", + "advanced_settings_self_signed_ssl_title": "Дозволи самопотписане SSL сертификате [ЕКСПЕРИМЕНТАЛНО]", "advanced_settings_sync_remote_deletions_subtitle": "Аутоматски избришите или вратите средство на овом уређају када се та радња предузме на вебу", "advanced_settings_sync_remote_deletions_title": "Синхронизујте удаљена брисања [ЕКСПЕРИМЕНТАЛНО]", "advanced_settings_tile_subtitle": "Напредна корисничка подешавања", - "advanced_settings_troubleshooting_subtitle": "Омогуц́ите додатне функције за решавање проблема", + "advanced_settings_troubleshooting_subtitle": "Омогући додатне функције за решавање проблема", "advanced_settings_troubleshooting_title": "Решавање проблема", "age_months": "Старост{months, plural, one {# месец} other {# месеци}}", "age_year_months": "Старост 1 година, {months, plural, one {# месец} other {# месец(а/и)}}", @@ -375,7 +402,8 @@ "album_added_notification_setting_description": "Прими обавештење е-поштом кад будеш додан у дељен албум", "album_cover_updated": "Омот албума ажуриран", "album_delete_confirmation": "Да ли стварно желите да избришете албум {album}?", - "album_delete_confirmation_description": "Ако се овај албум дели, други корисници више нец́е моц́и да му приступе.", + "album_delete_confirmation_description": "Ако се овај албум дели, други корисници више неће моћи да му приступе.", + "album_deleted": "Албум обрисан", "album_info_card_backup_album_excluded": "ИСКЛЈУЧЕНО", "album_info_card_backup_album_included": "УКЛЈУЧЕНО", "album_info_updated": "Информација албума ажурирана", @@ -386,6 +414,7 @@ "album_remove_user": "Уклонити корисника?", "album_remove_user_confirmation": "Да ли сте сигурни да желите да уклоните {user}?", "album_share_no_users": "Изгледа да сте поделили овај албум са свим корисницима или да немате ниједног корисника са којим бисте делили.", + "album_summary": "Сажетак албума", "album_updated": "Албум ажуриран", "album_updated_setting_description": "Примите обавештење е-поштом када дељени албум има нова својства", "album_user_left": "Напустио/ла {album}", @@ -401,6 +430,7 @@ "album_with_link_access": "Нека свако ко има везу види фотографије и људе у овом албуму.", "albums": "Албуми", "albums_count": "{count, plural, one {{count, number} Албум} few {{count, number} Албуми} other {{count, number} Албуми}}", + "albums_default_sort_order": "Подразумевани начин ређања албума", "all": "Све", "all_albums": "Сви албуми", "all_people": "Све особе", @@ -412,15 +442,20 @@ "alt_text_qr_code": "Слика QР кода", "anti_clockwise": "У смеру супротном од казаљке на сату", "api_key": "АПИ кључ (кеy)", - "api_key_description": "Ова вредност ц́е бити приказана само једном. Обавезно копирајте пре него што затворите прозор.", + "api_key_description": "Ова вредност ће бити приказана само једном. Обавезно копирајте пре него што затворите прозор.", "api_key_empty": "Име вашег АПИ кључа не би требало да буде празно", "api_keys": "АПИ кључеви (кеyс)", + "app_architecture_variant": "Варијанта (архитектура)", "app_bar_signout_dialog_content": "Да ли сте сигурни да желите да се одјавите?", "app_bar_signout_dialog_ok": "Да", "app_bar_signout_dialog_title": "Одјавите се", "app_settings": "Подешавања апликације", + "app_stores": "Продавнице апликација", + "app_update_available": "Доступно је ажурирање апликације", "appears_in": "Појављује се у", + "apply_count": "Примени ({count, number})", "archive": "Архива", + "archive_action_prompt": "{count} додато у архиву", "archive_or_unarchive_photo": "Архивирајте или поништите архивирање фотографије", "archive_page_no_archived_assets": "Нису пронађена архивирана средства", "archive_page_title": "Архива ({count})", @@ -431,7 +466,7 @@ "are_these_the_same_person": "Да ли су ово иста особа?", "are_you_sure_to_do_this": "Јесте ли сигурни да желите ово да урадите?", "asset_action_delete_err_read_only": "Не могу да обришем елемент(е) само за читање, прескачем", - "asset_action_share_err_offline": "Није могуц́е преузети офлајн ресурс(е), прескачем", + "asset_action_share_err_offline": "Није могуће преузети офлајн датотеку(е), прескачем", "asset_added_to_album": "Додато у албум", "asset_adding_to_album": "Додаје се у албум…", "asset_description_updated": "Опис датотеке је ажуриран", @@ -447,10 +482,11 @@ "asset_list_settings_subtitle": "Опције за мрежни приказ фотографија", "asset_list_settings_title": "Мрежни приказ фотографија", "asset_offline": "Датотека одсутна", - "asset_offline_description": "Ова вањска датотека се више не налази на диску. Молимо контактирајте свог Immich администратора за помоц́.", - "asset_restored_successfully": "Имовина је успешно врац́ена", + "asset_offline_description": "Ова спољна датотека се више не налази на диску. Молимо контактирајте свог Immich администратора за помоћ.", + "asset_restored_successfully": "Датотека је успешно враћена", "asset_skipped": "Прескочено", "asset_skipped_in_trash": "У отпад", + "asset_trashed": "Датотека бачена у отпад", "asset_uploaded": "Отпремљено (Уплоадед)", "asset_uploading": "Отпремање…", "asset_viewer_settings_subtitle": "Управљајте подешавањима прегледача галерије", @@ -466,19 +502,20 @@ "assets_removed_count": "Уклоњено {count, plural, one {# датотека} few {# датотеке} other {# датотека}}", "assets_removed_permanently_from_device": "{count} елемената трајно уклоњено са вашег уређаја", "assets_restore_confirmation": "Да ли сте сигурни да желите да вратите све своје датотеке које су у отпаду? Не можете поништити ову радњу! Имајте на уму да се ванмрежна средства не могу вратити на овај начин.", - "assets_restored_count": "Врац́ено {count, plural, one {# датотека} few {# датотеке} other {# датотека}}", - "assets_restored_successfully": "{count} елемената успешно врац́ено", + "assets_restored_count": "Враћено {count, plural, one {# датотека} few {# датотеке} other {# датотека}}", + "assets_restored_successfully": "{count} датотека успешно враћено", "assets_trashed": "{count} елемената је пребачено у отпад", "assets_trashed_count": "Бачено у отпад {count, plural, one {# датотека} few{# датотеке} other {# датотека}}", "assets_trashed_from_server": "{count} ресурс(а) обрисаних са Immich сервера", - "assets_were_part_of_album_count": "{count, plural, one {Датотека је} other {Датотеке су}} вец́ део албума", - "authorized_devices": "Овлашц́ени уређаји", + "assets_were_part_of_album_count": "{count, plural, one {Датотека је} other {Датотеке су}} већ део албума", + "authorized_devices": "Овлашћени уређаји", "automatic_endpoint_switching_subtitle": "Повежите се локално преко одређеног Wi-Fi-ја када је доступан и користите алтернативне везе на другим местима", "automatic_endpoint_switching_title": "Аутоматска промена URL-ова", "back": "Назад", "back_close_deselect": "Назад, затворите или опозовите избор", "background_location_permission": "Дозвола за локацију у позадини", "background_location_permission_content": "Да би се мењале мреже док се ради у позадини, Имих мора *увек* имати прецизан приступ локацији како би апликација могла да прочита име Wi-Fi мреже", + "background_options": "Позадинске опције", "backup": "Направи резервну копију", "backup_album_selection_page_albums_device": "Албума на уређају ({count})", "backup_album_selection_page_albums_tap": "Додирни да укључиш, додирни двапут да искључиш", @@ -488,6 +525,7 @@ "backup_album_selection_page_total_assets": "Укупно јединствених ***", "backup_all": "Све", "backup_background_service_backup_failed_message": "Прављење резервне копије елемената није успело. Покушава се поново…", + "backup_background_service_complete_notification": "Завршено прављење резервне копије", "backup_background_service_connection_failed_message": "Повезивање са сервером није успело. Покушавам поново…", "backup_background_service_current_upload_notification": "Отпремање {filename}", "backup_background_service_default_notification": "Проверавање нових записа…", @@ -522,8 +560,8 @@ "backup_controller_page_id": "ИД:{id}", "backup_controller_page_info": "Информације", "backup_controller_page_none_selected": "Ништа одабрано", - "backup_controller_page_remainder": "Подсетник", - "backup_controller_page_remainder_sub": "Остало фотографија и видеа да се отпреми од селекције", + "backup_controller_page_remainder": "Преостало", + "backup_controller_page_remainder_sub": "Остало фотографија и видеа да се сачува од изабраних", "backup_controller_page_server_storage": "Простор на серверу", "backup_controller_page_start_backup": "Покрени прављење резервне копије", "backup_controller_page_status_off": "Аутоматско прављење резервних копија у првом плану је искључено", @@ -537,26 +575,27 @@ "backup_err_only_album": "Немогуће брисање јединог албума", "backup_info_card_assets": "записи", "backup_manual_cancelled": "Отказано", - "backup_manual_in_progress": "Отпремање је вец́ у току. Покушајте касније", + "backup_manual_in_progress": "Отпремање је већ у току. Покушајте касније", "backup_manual_success": "Успех", "backup_manual_title": "Уплоад статус", + "backup_options": "Опције резервне копије", "backup_options_page_title": "Бацкуп оптионс", "backup_setting_subtitle": "Управљајте подешавањима отпремања у позадини и предњем плану", "backward": "Уназад", "birthdate_saved": "Датум рођења успешно сачуван", "birthdate_set_description": "Датум рођења се користи да би се израчунале године ове особе у добу одређене фотографије.", - "blurred_background": "Замуц́ена позадина", + "blurred_background": "Замућена позадина", "bugs_and_feature_requests": "Грешке (бугс) и захтеви за функције", "build": "Под-верзија (Буилд)", "build_image": "Сагради (Буилд) image", - "bulk_delete_duplicates_confirmation": "Да ли сте сигурни да желите групно да избришете {count, plural, one {# дуплиран елеменат} few {# дуплирана елемента} other {# дуплираних елемената}}? Ово ц́е задржати највец́е средство сваке групе и трајно избрисати све друге дупликате. Не можете поништити ову радњу!", - "bulk_keep_duplicates_confirmation": "Да ли сте сигурни да желите да задржите {count, plural, one {1 дуплирану датотеку} few {# дуплиране датотеке} other {# дуплираних датотека}}? Ово ц́е решити све дуплиране групе без брисања било чега.", - "bulk_trash_duplicates_confirmation": "Да ли сте сигурни да желите групно да одбаците {count, plural, one {1 дуплирану датотеку} few {# дуплиране датотеке} other {# дуплираних датотека}}? Ово ц́е задржати највец́у датотеку сваке групе и одбацити све остале дупликате.", + "bulk_delete_duplicates_confirmation": "Да ли сте сигурни да желите групно да избришете {count, plural, one {# дуплиран елеменат} few {# дуплирана елемента} other {# дуплираних елемената}}? Ово ће задржати највећу датотеку сваке групе и трајно избрисати све друге дупликате. Не можете поништити ову радњу!", + "bulk_keep_duplicates_confirmation": "Да ли сте сигурни да желите да задржите {count, plural, one {1 дуплирану датотеку} few {# дуплиране датотеке} other {# дуплираних датотека}}? Ово ће решити све дуплиране групе без брисања било чега.", + "bulk_trash_duplicates_confirmation": "Да ли сте сигурни да желите групно да одбаците {count, plural, one {1 дуплирану датотеку} few {# дуплиране датотеке} other {# дуплираних датотека}}? Ово ће задржати највећу датотеку сваке групе и одбацити све остале дупликате.", "buy": "Купите лиценцу Immich-a", "cache_settings_clear_cache_button": "Обриши кеш меморију", "cache_settings_clear_cache_button_title": "Ова опција брише кеш меморију апликације. Ово ће битно утицати на перформансе апликације док се кеш меморија не учита поново.", "cache_settings_duplicated_assets_clear_button": "ЦЛЕАР", - "cache_settings_duplicated_assets_subtitle": "Фотографије и видео снимци које је апликација ставила на црну листу", + "cache_settings_duplicated_assets_subtitle": "Фотографије и видео снимци које апликација игнорише", "cache_settings_duplicated_assets_title": "Дуплирани елементи ({count})", "cache_settings_statistics_album": "Минијатуре библиотека", "cache_settings_statistics_full": "Пуне слике", @@ -573,15 +612,18 @@ "cancel": "Одустани", "cancel_search": "Откажи претрагу", "canceled": "Отказано", + "canceling": "Отказујем", "cannot_merge_people": "Не може спојити особе", "cannot_undo_this_action": "Не можете поништити ову радњу!", "cannot_update_the_description": "Не може ажурирати опис", + "cast": "Шаљи на уређај (каст)", "change_date": "Промени датум", + "change_description": "Промени опис", "change_display_order": "Промени редослед приказа", "change_expiration_time": "Промени време истека", "change_location": "Промени место", "change_name": "Промени име", - "change_name_successfully": "Промени име успешно", + "change_name_successfully": "Име успешно промењено", "change_password": "Промени Лозинку", "change_password_description": "Ово је или први пут да се пријављујете на систем или је поднет захтев за промену лозинке. Унесите нову лозинку испод.", "change_password_form_confirm_password": "Поново унесите шифру", @@ -592,11 +634,12 @@ "change_pin_code": "Промена ПИН кода", "change_your_password": "Промени своју шифру", "changed_visibility_successfully": "Видљивост је успешно промењена", - "check_corrupt_asset_backup": "Проверите да ли постоје оштец́ене резервне копије имовине", + "charging": "Пуњење", + "check_corrupt_asset_backup": "Провери да ли постоје оштећене резервне копије датотека", "check_corrupt_asset_backup_button": "Извршите проверу", "check_corrupt_asset_backup_description": "Покрените ову проверу само преко Wi-Fi мреже и након што се направи резервна копија свих података. Поступак може потрајати неколико минута.", "check_logs": "Проверите дневнике (логс)", - "choose_matching_people_to_merge": "Изаберите одговарајуц́е особе за спајање", + "choose_matching_people_to_merge": "Изаберите одговарајуће особе за спајање", "city": "Град", "clear": "Јасно", "clear_all": "Избриши све", @@ -607,10 +650,10 @@ "client_cert_enter_password": "Ентер Password", "client_cert_import": "Импорт", "client_cert_import_success_msg": "Сертификат клијента је увезен", - "client_cert_invalid_msg": "Неважец́а датотека сертификата или погрешна лозинка", + "client_cert_invalid_msg": "Неважећа датотека сертификата или погрешна лозинка", "client_cert_remove_msg": "Сертификат клијента је уклоњен", - "client_cert_subtitle": "Подржава само ПКЦС12 (.п12, .пфx) формат. Увоз/уклањање сертификата је доступно само пре пријаве", - "client_cert_title": "SSL клијентски сертификат", + "client_cert_subtitle": "Подржава само PKCS12 (.p12, .pfx) формат. Увоз/уклањање сертификата је доступно само пре пријаве", + "client_cert_title": "Клијентски SSL сертификат [ЕКСПЕРИМЕНТАЛНО]", "clockwise": "У смеру казаљке", "close": "Затвори", "collapse": "Скупи", @@ -620,17 +663,17 @@ "comment_deleted": "Коментар обрисан", "comment_options": "Опције коментара", "comments_and_likes": "Коментари и лајкови", - "comments_are_disabled": "Коментари су oneмогуц́ени", + "comments_are_disabled": "Коментари су онемогућени", "common_create_new_album": "Креирај нови албум", - "common_server_error": "Молимо вас да проверите мрежну везу, уверите се да је сервер доступан и да су верзије апликација/сервера компатибилне.", "completed": "Завршено", "confirm": "Потврди", "confirm_admin_password": "Потврди Административну Лозинку", "confirm_delete_face": "Да ли сте сигурни да желите да избришете особу {name} из дела?", "confirm_delete_shared_link": "Да ли сте сигурни да желите да избришете овај дељени link?", - "confirm_keep_this_delete_others": "Све остале датотеке у групи ц́е бити избрисане осим ове датотеке. Да ли сте сигурни да желите да наставите?", + "confirm_keep_this_delete_others": "Све остале датотеке у групи ће бити избрисане осим ове датотеке. Да ли сте сигурни да желите да наставите?", "confirm_new_pin_code": "Потврдите нови ПИН код", "confirm_password": "Поново унеси шифру", + "connected_to": "Повезан са", "contain": "Обухвати", "context": "Контекст", "continue": "Настави", @@ -668,7 +711,7 @@ "create_shared_album_page_share_add_assets": "ДОДАЈ СРЕДСТВА", "create_shared_album_page_share_select_photos": "Одабери фотографије", "create_tag": "Креирајте ознаку (tag)", - "create_tag_description": "Направите нову ознаку (tag). За угнежђене ознаке, унесите пуну путању ознаке укључујуц́и косе црте.", + "create_tag_description": "Направите нову ознаку (tag). За угнежђене ознаке, унесите пуну путању ознаке укључујћи косе црте.", "create_user": "Направи корисника", "created": "Направљен", "created_at": "Креирано", @@ -689,6 +732,7 @@ "date_of_birth_saved": "Датум рођења успешно сачуван", "date_range": "Распон датума", "day": "Дан", + "days": "Дани", "deduplicate_all": "Де-дуплицирај све", "deduplication_criteria_1": "Величина слике у бајтовима", "deduplication_criteria_2": "Број EXIF података", @@ -700,9 +744,9 @@ "delete_album": "Обриши албум", "delete_api_key_prompt": "Да ли сте сигурни да желите да избришете овај АПИ кључ (кеy)?", "delete_dialog_alert": "Ове ствари ће перманентно бити обрисане са Immich-a и Вашег уређаја", - "delete_dialog_alert_local": "Ове ставке ц́е бити трајно уклоњене са вашег уређаја, али ц́е и даље бити доступне на Immich серверу", - "delete_dialog_alert_local_non_backed_up": "Неке ставке нису резервно копиране на Immich-u и биц́е трајно уклоњене са вашег уређаја", - "delete_dialog_alert_remote": "Ове ставке ц́е бити трајно избрисане са Immich сервера", + "delete_dialog_alert_local": "Ове ставке ће бити трајно уклоњене са вашег уређаја, али ће и даље бити доступне на Immich серверу", + "delete_dialog_alert_local_non_backed_up": "Неке ставке нису резервно копиране на Immich-u и биће трајно уклоњене са вашег уређаја", + "delete_dialog_alert_remote": "Ове ставке ће бити трајно избрисане са Immich сервера", "delete_dialog_ok_force": "Ипак обриши", "delete_dialog_title": "Обриши перманентно", "delete_duplicates_confirmation": "Да ли сте сигурни да желите да трајно избришете ове дупликате?", @@ -725,7 +769,7 @@ "description_input_submit_error": "Грешка при ажурирању описа, проверите дневник за више детаља", "details": "Детаљи", "direction": "Смер", - "disabled": "Онемогуц́ено", + "disabled": "Онемогућено", "disallow_edits": "Забрани измене", "discord": "Дискорд", "discover": "Откријте", @@ -760,13 +804,14 @@ "downloading_media": "Преузимање медија", "drop_files_to_upload": "Убаците датотеке било где да их отпремите (уплоад-ујете)", "duplicates": "Дупликати", - "duplicates_description": "Разрешите сваку групу тако што ц́ете навести дупликате, ако их има", + "duplicates_description": "Разрешите сваку групу тако што ћете навести дупликате, ако их има", "duration": "Трајање", "edit": "Уреди", "edit_album": "Уреди албум", "edit_avatar": "Уреди аватар", "edit_date": "Уреди датум", "edit_date_and_time": "Уреди датум и време", + "edit_description": "Измени опис", "edit_exclusion_pattern": "Измените образац изузимања", "edit_faces": "Уреди лица", "edit_import_path": "Уреди путању за преузимање", @@ -780,19 +825,18 @@ "edit_tag": "Уреди ознаку (tag)", "edit_title": "Уреди титулу", "edit_user": "Уреди корисника", - "edited": "Уређено", "editor": "Уредник", - "editor_close_without_save_prompt": "Промене нец́е бити сачуване", + "editor_close_without_save_prompt": "Промене неће бити сачуване", "editor_close_without_save_title": "Затворити уређивач?", "editor_crop_tool_h2_aspect_ratios": "Пропорције (аспецт ратиос)", "editor_crop_tool_h2_rotation": "Ротација", "email": "Е-пошта", "email_notifications": "Обавештења е-поштом", "empty_folder": "Ова мапа је празна", - "empty_trash": "Испразните смец́е", - "empty_trash_confirmation": "Да ли сте сигурни да желите да испразните смец́е? Ово ц́е трајно уклонити све датотеке у смец́у из Immich-a.\nНе можете поништити ову радњу!", - "enable": "Омогуц́и (Енабле)", - "enabled": "Омогуц́ено (Енаблед)", + "empty_trash": "Испразните отпад", + "empty_trash_confirmation": "Да ли сте сигурни да желите да испразните смеће? Ово ће трајно уклонити све датотеке у отпаду из Immich-a.\nНе можете поништити ову радњу!", + "enable": "Омогући", + "enabled": "Омогућено", "end_date": "Крајњи датум", "enqueued": "Стављено у ред", "enter_wifi_name": "Унесите назив Wi-Fi мреже", @@ -803,12 +847,12 @@ "error_saving_image": "Грешка: {error}", "error_title": "Грешка – Нешто је пошло наопако", "errors": { - "cannot_navigate_next_asset": "Није могуц́е доц́и до следец́е датотеке", - "cannot_navigate_previous_asset": "Није могуц́е доц́и до претходне датотеке", - "cant_apply_changes": "Није могуц́е применити промене", - "cant_change_activity": "Није могуц́е {enabled, select, true {oneмогуц́ити} other {омогуц́ити}} активности", - "cant_change_asset_favorite": "Није могуц́е променити фаворит за датотеку", - "cant_change_metadata_assets_count": "Није могуц́е променити метаподатке за {count, plural, one {# датотеку} other {# датотеке}}", + "cannot_navigate_next_asset": "Није могуће доћи до следеће датотеке", + "cannot_navigate_previous_asset": "Није могуће доћи до претходне датотеке", + "cant_apply_changes": "Није могуће применити промене", + "cant_change_activity": "Није могуће {enabled, select, true {онемогућити} other {омогућити}} активност", + "cant_change_asset_favorite": "Није могуће променити омиљено за датотеку", + "cant_change_metadata_assets_count": "Није могуће променити метаподатке за {count, plural, one {# датотеку} other {# датотеке}}", "cant_get_faces": "Не могу да нађем лица", "cant_get_number_of_comments": "Не могу добити број коментара", "cant_search_people": "Не могу претраживати особе", @@ -820,7 +864,7 @@ "error_hiding_buy_button": "Грешка при скривању дугмета за куповину", "error_removing_assets_from_album": "Грешка при уклањању датотеке из албума, проверите конзолу за више детаља", "error_selecting_all_assets": "Грешка при избору свих датотека", - "exclusion_pattern_already_exists": "Овај образац искључења вец́ постоји.", + "exclusion_pattern_already_exists": "Овај образац искључења већ постоји.", "failed_to_create_album": "Није могуће креирати албум", "failed_to_create_shared_link": "Прављење дељеног linkа није успело", "failed_to_edit_shared_link": "Уређивање дељеног linkа није успело", @@ -832,94 +876,94 @@ "failed_to_load_people": "Учитавање особа није успело", "failed_to_remove_product_key": "Уклањање кључа производа није успело", "failed_to_stack_assets": "Слагање датотека није успело", - "failed_to_unstack_assets": "Расклапање датотека није успело", + "failed_to_unstack_assets": "Разгруписање датотека није успело", "failed_to_update_notification_status": "Ажурирање статуса обавештења није успело", - "import_path_already_exists": "Ова путања увоза вец́ постоји.", + "import_path_already_exists": "Ова путања увоза већ постоји.", "incorrect_email_or_password": "Неисправан e-mail или лозинка", "paths_validation_failed": "{paths, plural, one {# путања није прошла} other {# путањe нису прошле}} проверу ваљаности", - "profile_picture_transparent_pixels": "Слике профила не могу имати прозирне пикселе. Молимо увец́ајте и/или померите слику.", - "quota_higher_than_disk_size": "Поставили сте квоту вец́у од величине диска", - "unable_to_add_album_users": "Није могуц́е додати кориснике у албум", - "unable_to_add_assets_to_shared_link": "Није могуц́е додати елементе дељеној вези", - "unable_to_add_comment": "Није могуц́е додати коментар", - "unable_to_add_exclusion_pattern": "Није могуц́е додати образац изузимања", - "unable_to_add_import_path": "Није могуц́е додати путању за увоз", - "unable_to_add_partners": "Није могуц́е додати партнере", + "profile_picture_transparent_pixels": "Слике профила не могу имати прозирне пикселе. Молимо увећајте и/или померите слику.", + "quota_higher_than_disk_size": "Поставили сте квоту већу од величине диска", + "unable_to_add_album_users": "Није могуће додати кориснике у албум", + "unable_to_add_assets_to_shared_link": "Није могуће додати датотеке дељеној вези", + "unable_to_add_comment": "Није могуће додати коментар", + "unable_to_add_exclusion_pattern": "Није могуће додати образац изузимања", + "unable_to_add_import_path": "Није могуће додати путању за увоз", + "unable_to_add_partners": "Није могуће додати партнере", "unable_to_add_remove_archive": "Није могуће {archived, select, true {уклонити датотеке из} other {додати датотеке у}} архиву", "unable_to_add_remove_favorites": "Није могуће {favorite, select, true {додати датотеке у} other {уклонити датотеке из}} фаворите", "unable_to_archive_unarchive": "Није могуће {archived, select, true {архивирати} other {де-архивирати}}", - "unable_to_change_album_user_role": "Није могуц́е променити улогу корисника албума", - "unable_to_change_date": "Није могуц́е променити датум", - "unable_to_change_favorite": "Није могуц́е променити фаворит за датотеку/е", - "unable_to_change_location": "Није могуц́е променити локацију", - "unable_to_change_password": "Није могуц́е променити лозинку", - "unable_to_change_visibility": "Није могуц́е променити видљивост за {count, plural, one {# особу} other {# особе}}", - "unable_to_complete_oauth_login": "Није могуц́е довршити OAuth пријаву", - "unable_to_connect": "Није могуц́е повезати се", - "unable_to_copy_to_clipboard": "Није могуц́е копирати у међуспремник (цлипбоард), проверите да ли приступате страници преко хттпс-а", - "unable_to_create_admin_account": "Није могуц́е направити администраторски налог", - "unable_to_create_api_key": "Није могуц́е направити нови АПИ кључ (кеy)", - "unable_to_create_library": "Није могуц́е направити библиотеку", - "unable_to_create_user": "Није могуц́е креирати корисника", - "unable_to_delete_album": "Није могуц́е избрисати албум", - "unable_to_delete_asset": "Није могуц́е избрисати датотеке", + "unable_to_change_album_user_role": "Није могуће променити улогу корисника албума", + "unable_to_change_date": "Није могуће променити датум", + "unable_to_change_favorite": "Није могуће променити омиљено за датотеку", + "unable_to_change_location": "Није могуће променити локацију", + "unable_to_change_password": "Није могуће променити лозинку", + "unable_to_change_visibility": "Није могуће променити видљивост за {count, plural, one {# особу} other {# особе}}", + "unable_to_complete_oauth_login": "Није могуће довршити OAuth пријаву", + "unable_to_connect": "Није могуће повезати се", + "unable_to_copy_to_clipboard": "Није могуће копирати у међуспремник (цлипбоард), проверите да ли приступате страници преко хттпс-а", + "unable_to_create_admin_account": "Није могуће направити администраторски налог", + "unable_to_create_api_key": "Није могуће направити нови АПИ кључ (кеy)", + "unable_to_create_library": "Није могуће направити библиотеку", + "unable_to_create_user": "Није могуће креирати корисника", + "unable_to_delete_album": "Није могуће избрисати албум", + "unable_to_delete_asset": "Није могуће избрисати датотеке", "unable_to_delete_assets": "Грешка при брисању датотека", - "unable_to_delete_exclusion_pattern": "Није могуц́е избрисати образац изузимања", - "unable_to_delete_import_path": "Није могуц́е избрисати путању за увоз", - "unable_to_delete_shared_link": "Није могуц́е избрисати дељени link", - "unable_to_delete_user": "Није могуц́е избрисати корисника", - "unable_to_download_files": "Није могуц́е преузети датотеке", - "unable_to_edit_exclusion_pattern": "Није могуц́е изменити образац изузимања", - "unable_to_edit_import_path": "Није могуц́е изменити путању увоза", - "unable_to_empty_trash": "Није могуц́е испразнити отпад", - "unable_to_enter_fullscreen": "Није могуц́е отворити преко целог екрана", - "unable_to_exit_fullscreen": "Није могуц́е изац́и из целог екрана", - "unable_to_get_comments_number": "Није могуц́е добити број коментара", + "unable_to_delete_exclusion_pattern": "Није могуће избрисати образац изузимања", + "unable_to_delete_import_path": "Није могуће избрисати путању за увоз", + "unable_to_delete_shared_link": "Није могуће избрисати дељени link", + "unable_to_delete_user": "Није могуће избрисати корисника", + "unable_to_download_files": "Није могуће преузети датотеке", + "unable_to_edit_exclusion_pattern": "Није могуће изменити образац изузимања", + "unable_to_edit_import_path": "Није могуће изменити путању увоза", + "unable_to_empty_trash": "Није могуће испразнити отпад", + "unable_to_enter_fullscreen": "Није могуће отворити преко целог екрана", + "unable_to_exit_fullscreen": "Није могуће изаћи из целог екрана", + "unable_to_get_comments_number": "Није могуће добити број коментара", "unable_to_get_shared_link": "Преузимање дељене везе није успело", "unable_to_hide_person": "Није могуће сакрити особу", "unable_to_link_motion_video": "Није могуће повезати видео са сликом", - "unable_to_link_oauth_account": "Није могуц́е повезати OAuth налог", - "unable_to_log_out_all_devices": "Није могуц́е одјавити све уређаје", - "unable_to_log_out_device": "Није могуц́е одјавити уређај", - "unable_to_login_with_oauth": "Није могуц́е пријавити се помоц́у OAuth-а", + "unable_to_link_oauth_account": "Није могуће повезати OAuth налог", + "unable_to_log_out_all_devices": "Није могуће одјавити све уређаје", + "unable_to_log_out_device": "Није могуће одјавити уређај", + "unable_to_login_with_oauth": "Није могуће пријавити се помоћу OAuth-а", "unable_to_play_video": "Није могуће пустити видео", - "unable_to_reassign_assets_existing_person": "Није могуц́е прерасподелити датотеке на {name, select, null {постојец́у особу} other {{name}}}", - "unable_to_reassign_assets_new_person": "Није могуц́е пренети средства новој особи", + "unable_to_reassign_assets_existing_person": "Није могуће прерасподелити датотеке на {name, select, null {постојећу особу} other {{name}}}", + "unable_to_reassign_assets_new_person": "Није могуће пренети средства новој особи", "unable_to_refresh_user": "Није могуће освежити корисника", "unable_to_remove_album_users": "Није могуће уклонити кориснике из албума", "unable_to_remove_api_key": "Није могуће уклонити АПИ кључ (кеy)", - "unable_to_remove_assets_from_shared_link": "Није могуц́е уклонити елементе са дељеног linkа", + "unable_to_remove_assets_from_shared_link": "Није могуће уклонити елементе са дељеног linkа", "unable_to_remove_library": "Није могуће уклонити библиотеку", "unable_to_remove_partner": "Није могуће уклонити партнера", "unable_to_remove_reaction": "Није могуће уклонити реакцију", "unable_to_reset_password": "Није могуће ресетовати лозинку", - "unable_to_reset_pin_code": "Није могуц́е ресетовати ПИН код", + "unable_to_reset_pin_code": "Није могуће ресетовати ПИН код", "unable_to_resolve_duplicate": "Није могуће разрешити дупликат", - "unable_to_restore_assets": "Није могуц́е вратити датотеке", + "unable_to_restore_assets": "Није могуће вратити датотеке", "unable_to_restore_trash": "Није могуће повратити отпад", "unable_to_restore_user": "Није могуће повратити корисника", "unable_to_save_album": "Није могуће сачувати албум", "unable_to_save_api_key": "Није могуће сачувати АПИ кључ (кеy)", - "unable_to_save_date_of_birth": "Није могуц́е сачувати датум рођења", + "unable_to_save_date_of_birth": "Није могуће сачувати датум рођења", "unable_to_save_name": "Није могуће сачувати име", "unable_to_save_profile": "Није могуће сачувати профил", "unable_to_save_settings": "Није могуће сачувати подешавања", "unable_to_scan_libraries": "Није могуће скенирати библиотеке", "unable_to_scan_library": "Није могуће скенирати библиотеку", - "unable_to_set_feature_photo": "Није могуц́е поставити истакнуту фотографију", + "unable_to_set_feature_photo": "Није могуће поставити истакнуту фотографију", "unable_to_set_profile_picture": "Није могуће поставити профилну слику", "unable_to_submit_job": "Није могуће предати задатак", - "unable_to_trash_asset": "Није могуц́е избацити материјал у отпад", + "unable_to_trash_asset": "Није могуће избацити материјал у отпад", "unable_to_unlink_account": "Није могуће раскинути профил", "unable_to_unlink_motion_video": "Није могуће одвезати видео од слике", - "unable_to_update_album_cover": "Није могуц́е ажурирати насловницу албума", - "unable_to_update_album_info": "Није могуц́е ажурирати информације о албуму", + "unable_to_update_album_cover": "Није могуће ажурирати насловницу албума", + "unable_to_update_album_info": "Није могуће ажурирати информације о албуму", "unable_to_update_library": "Није могуће ажурирати библиотеку", "unable_to_update_location": "Није могуће ажурирати локацију", "unable_to_update_settings": "Није могуће ажурирати подешавања", "unable_to_update_timeline_display_status": "Није могуће ажурирати статус приказа временске линије", "unable_to_update_user": "Није могуће ажурирати корисника", - "unable_to_upload_file": "Није могуц́е отпремити датотеку" + "unable_to_upload_file": "Није могуће отпремити датотеку" }, "exif": "Exif", "exif_bottom_sheet_description": "Додај опис...", @@ -944,7 +988,7 @@ "external": "Спољашњи", "external_libraries": "Спољашње Библиотеке", "external_network": "Спољна мрежа", - "external_network_sheet_info": "Када није на жељеној Wi-Fi мрежи, апликација ц́е се повезати са сервером преко прве од доле наведених URL адреса до којих може да дође, почевши од врха до дна", + "external_network_sheet_info": "Када није на жељеној Wi-Fi мрежи, апликација ће се повезати са сервером преко прве од доле наведених URL адреса до којих може да дође, почевши од врха до дна", "face_unassigned": "Нераспоређени", "failed": "Неуспешно", "failed_to_load_assets": "Датотеке нису успешно учитане", @@ -963,20 +1007,23 @@ "filter": "Филтер", "filter_people": "Филтрирање особа", "filter_places": "Филтрирајте места", - "find_them_fast": "Брзо их пронађите по имену помоц́у претраге", + "find_them_fast": "Брзо их пронађите по имену помоћу претраге", + "first": "Први", "fix_incorrect_match": "Исправите нетачно подударање", "folder": "Фасцикла", "folder_not_found": "Фасцикла није пронађена", "folders": "Фасцикле (Фолдерс)", "folders_feature_description": "Прегледавање приказа фасцикле за фотографије и видео записа у систему датотека", "forward": "Напред", + "gcast_enabled": "Google Cast", "general": "Генерално", - "get_help": "Нађи помоц́", - "get_wifiname_error": "Није могуц́е добити име Wi-Fi мреже. Уверите се да сте дали потребне дозволе и да сте повезани на Wi-Fi мрежу", + "get_help": "Нађи помоћ", + "get_wifiname_error": "Није могуће добити име Wi-Fi мреже. Уверите се да сте дали потребне дозволе и да сте повезани на Wi-Fi мрежу", "getting_started": "Почињем", "go_back": "Врати се", "go_to_folder": "Иди у фасциклу", "go_to_search": "Иди на претрагу", + "gps": "GPS", "grant_permission": "Дај дозволу", "group_albums_by": "Групни албуми по...", "group_country": "Група по држава", @@ -984,14 +1031,14 @@ "group_owner": "Групирајте по власнику", "group_places_by": "Групирајте места по...", "group_year": "Групирајте по години", - "haptic_feedback_switch": "Омогуц́и хаптичку повратну информацију", + "haptic_feedback_switch": "Омогући хаптичку повратну информацију", "haptic_feedback_title": "Хаптичке повратне информације", "has_quota": "Има квоту", + "hashing": "Хеширање", "header_settings_add_header_tip": "Додај заглавље", "header_settings_field_validator_msg": "Вредност не може бити празна", "header_settings_header_name_input": "Назив заглавља", "header_settings_header_value_input": "Вредност заглавља", - "headers_settings_tile_subtitle": "Дефинишите прокси заглавља која апликација треба да шаље са сваким мрежним захтевом", "headers_settings_tile_title": "Прилагођени прокси заглавци", "hi_user": "Здраво {name} ({email})", "hide_all_people": "Сакриј све особе", @@ -1003,22 +1050,24 @@ "home_page_add_to_album_conflicts": "Додат {added} запис у албум {album}. {failed} записи су већ у албуму.", "home_page_add_to_album_err_local": "Тренутно немогуће додати локалне записе у албуме, прескацу се", "home_page_add_to_album_success": "Доdate {added} ставке у албум {album}.", - "home_page_album_err_partner": "Још увек није могуц́е додати партнерска средства у албум, прескачем", - "home_page_archive_err_local": "Још увек није могуц́е архивирати локалне ресурсе, прескачем", + "home_page_album_err_partner": "Још увек није могуће додати партнерска средства у албум, прескачем", + "home_page_archive_err_local": "Још увек није могуће архивирати локалне ресурсе, прескачем", "home_page_archive_err_partner": "Не могу да архивирам партнерску имовину, прескачем", "home_page_building_timeline": "Креирање хронолошке линије", "home_page_delete_err_partner": "Не могу да обришем партнерску имовину, прескачем", "home_page_delete_remote_err_local": "Локална средства у обрисавању удаљеног избора, прескакање", "home_page_favorite_err_local": "Тренутно није могуце додати локалне записе у фаворите, прескацу се", - "home_page_favorite_err_partner": "Још увек није могуц́е означити партнерске ресурсе као омиљене, прескачем", + "home_page_favorite_err_partner": "Још увек није могуће означити партнерске ресурсе као омиљене, прескачем", "home_page_first_time_notice": "Ако је ово први пут да користите апликацију, молимо Вас да одаберете албуме које желите да сачувате", "home_page_share_err_local": "Не могу да делим локалне ресурсе преко linkа, прескачем", - "home_page_upload_err_limit": "Можете отпремити највише 30 елемената истовремено, прескачуц́и", - "host": "Домац́ин (Хост)", + "home_page_upload_err_limit": "Можете отпремити највише 30 елемената истовремено, прескачући", + "host": "Домаћин (Хост)", "hour": "Сат", + "hours": "Сати", "id": "ИД", + "idle": "Неактивно", "ignore_icloud_photos": "Игноришите иЦлоуд фотографије", - "ignore_icloud_photos_description": "Фотографије које су сачуване на иЦлоуд-у нец́е бити отпремљене на Immich сервер", + "ignore_icloud_photos_description": "Фотографије које су сачуване на иЦлоуд-у неће бити отпремљене на Immich сервер", "image": "Фотографија", "image_alt_text_date": "{isVideo, select, true {Видео} other {Image}} снимљено {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Видео} other {Image}} снимљено са {person1} {date}", @@ -1052,10 +1101,11 @@ "night_at_midnight": "Свака ноћ у поноћ", "night_at_twoam": "Свака ноћ у 2ам" }, - "invalid_date": "Неважец́и датум", - "invalid_date_format": "Неважец́и формат датума", + "invalid_date": "Неважећи датум", + "invalid_date_format": "Неважећи формат датума", "invite_people": "Позовите људе", "invite_to_album": "Позови на албум", + "ios_debug_info_fetch_ran_at": "Дохватање покренуто {dateTime}", "items_count": "{count, plural, one {# датотека} other {# датотека}}", "jobs": "Послови", "keep": "Задржи", @@ -1065,6 +1115,7 @@ "keyboard_shortcuts": "Пречице на тастатури", "language": "Језик", "language_setting_description": "Изаберите жељени језик", + "last": "Последњи", "last_seen": "Последњи пут виђен", "latest_version": "Најновија верзија", "latitude": "Географска ширина", @@ -1080,7 +1131,9 @@ "library_page_sort_created": "Најновије креирано", "library_page_sort_last_modified": "Последња измена", "library_page_sort_title": "Назив албума", + "licenses": "Лиценце", "light": "Светло", + "like": "Свиђа ми се", "like_deleted": "Лајкуј избрисано", "link_motion_video": "Направи везу за видео запис", "link_to_oauth": "Веза до OAuth-а", @@ -1088,21 +1141,24 @@ "list": "Излистај", "loading": "Учитавање", "loading_search_results_failed": "Учитавање резултата претраге није успело", + "local": "Локално", "local_network": "Лоцал нетwорк", - "local_network_sheet_info": "Апликација ц́е се повезати са сервером преко ове URL адресе када користи наведену Ви-Фи мрежу", + "local_network_sheet_info": "Апликација ће се повезати са сервером преко ове URL адресе када користи наведену Ви-Фи мрежу", "location_permission": "Дозвола за локацију", "location_permission_content": "Да би користио функцију аутоматског пребацивања, Immich-u је потребна прецизна дозвола за локацију како би могао да прочита назив тренутне Wi-Fi мреже", "location_picker_choose_on_map": "Изаберите на мапи", - "location_picker_latitude_error": "Унесите важец́у географску ширину", + "location_picker_latitude_error": "Унесите важећу географску ширину", "location_picker_latitude_hint": "Унесите своју географску ширину овде", - "location_picker_longitude_error": "Унесите важец́у географску дужину", + "location_picker_longitude_error": "Унесите важећу географску дужину", "location_picker_longitude_hint": "Унесите своју географску дужину овде", + "lock": "Закључај", + "locked_folder": "Закључана фасцикла", "log_out": "Одјави се", "log_out_all_devices": "Одјавите се са свих уређаја", "logged_out_all_devices": "Одјављени су сви уређаји", "logged_out_device": "Одјављен уређај", "login": "Пријава", - "login_disabled": "Пријава је oneмогуц́ена", + "login_disabled": "Пријава је онемогућена", "login_form_api_exception": "Изузетак АПИ-ја. Молимо вас да проверите URL адресу сервера и покушате поново.", "login_form_back_button_text": "Назад", "login_form_email_hint": "вашemail@email.цом", @@ -1116,21 +1172,22 @@ "login_form_failed_get_oauth_server_config": "Евиденција грешака користећи OAuth, проверити серверски link (URL)", "login_form_failed_get_oauth_server_disable": "OAuth опција није доступна на овом серверу", "login_form_failed_login": "Неуспешна пријава, провери URL сервера, email и шифру", - "login_form_handshake_exception": "Дошло је до изузетка рукостискања са сервером. Омогуц́ите подршку за самопотписане сертификате у подешавањима ако користите самопотписани сертификат.", + "login_form_handshake_exception": "Дошло је до изузетка рукостискања са сервером. Омогућите подршку за самопотписане сертификате у подешавањима ако користите самопотписани сертификат.", "login_form_password_hint": "шифра", "login_form_save_login": "Остани пријављен", "login_form_server_empty": "Ентер а сервер URL.", - "login_form_server_error": "Није могуц́е повезати се са сервером.", - "login_has_been_disabled": "Пријава је oneмогуц́ена.", + "login_form_server_error": "Није могуће повезати се са сервером.", + "login_has_been_disabled": "Пријава је онемогућена.", "login_password_changed_error": "Дошло је до грешке приликом ажурирања лозинке", "login_password_changed_success": "Лозинка је успешно ажурирана", "logout_all_device_confirmation": "Да ли сте сигурни да желите да се одјавите са свих уређаја?", "logout_this_device_confirmation": "Да ли сте сигурни да желите да се одјавите са овог уређаја?", + "logs": "Евиденција", "longitude": "Географска дужина", "look": "Погледај", "loop_videos": "Понављајте видео записе", - "loop_videos_description": "Омогуц́ите за аутоматско понављање видео записа у прегледнику детаља.", - "main_branch_warning": "Употребљавате развојну верзију; строго препоручујемо употребу изdate верзије!", + "loop_videos_description": "Омогућите за аутоматско понављање видео записа у прегледнику детаља.", + "main_branch_warning": "Користиш развојну верзију; строго препоручујемо употребу издате верзије!", "main_menu": "Главни мени", "make": "Креирај", "manage_shared_links": "Управљајте дељеним везама", @@ -1141,12 +1198,12 @@ "manage_your_devices": "Управљајте својим пријављеним уређајима", "manage_your_oauth_connection": "Управљајте својом OAuth везом", "map": "Мапа", - "map_assets_in_bounds": "{count} фотографија", - "map_cannot_get_user_location": "Није могуц́е добити локацију корисника", + "map_assets_in_bounds": "{count, plural, =0 {Нема фотографија у овом подручју} one {# фотографија} few {# фотографије} other {# фотографија}}", + "map_cannot_get_user_location": "Није могуће добити локацију корисника", "map_location_dialog_yes": "Да", "map_location_picker_page_use_location": "Користите ову локацију", - "map_location_service_disabled_content": "Услуга локације мора бити омогуц́ена да би се приказивала средства са ваше тренутне локације. Да ли желите да је сада омогуц́ите?", - "map_location_service_disabled_title": "Услуга локације је oneмогуц́ена", + "map_location_service_disabled_content": "Услуга локације мора бити омогућена да би се приказивала средства са ваше тренутне локације. Да ли желите да је сада омогућите?", + "map_location_service_disabled_title": "Услуга локације је онемогућена", "map_marker_for_images": "Означивач на мапи за слике снимљене у {city}, {country}", "map_marker_with_image": "Маркер на мапи са сликом", "map_no_location_permission_content": "Потребна је дозвола за локацију да би се приказали ресурси са ваше тренутне локације. Да ли желите да је сада дозволите?", @@ -1168,14 +1225,14 @@ "marked_all_as_read": "Све је означено као прочитано", "matches": "Подударања", "media_type": "Врста медија", - "memories": "Сец́ања", - "memories_all_caught_up": "Све је ухвац́ено", + "memories": "Сећања", + "memories_all_caught_up": "Све је ухваћено", "memories_check_back_tomorrow": "Вратите се сутра за још успомена", - "memories_setting_description": "Управљајте оним што видите у својим сец́ањима", + "memories_setting_description": "Управљајте оним што видите у својим сећањима", "memories_start_over": "Почни испочетка", "memories_swipe_to_close": "Превуците нагоре да бисте затворили", "memory": "Меморија", - "memory_lane_title": "Трака сец́ања {title}", + "memory_lane_title": "Трака сећања {title}", "menu": "Мени", "merge": "Споји", "merge_people": "Споји особе", @@ -1185,20 +1242,23 @@ "merged_people_count": "Спојено {count, plural, one {# особа} other {# особе}}", "minimize": "Минимизирајте", "minute": "Минут", + "minutes": "Минути", "missing": "Недостаје", "model": "Модел", "month": "Месец", "monthly_title_text_date_format": "ММММ y", "more": "Више", + "move": "Премести", "moved_to_archive": "Премештено {count, plural, one {# датотека} other {# датотеке}} у архиву", "moved_to_library": "Премештено {count, plural, one {# датотека} other {# датотеке}} у библиотеку", - "moved_to_trash": "Премештено у смец́е", + "moved_to_trash": "Премештено у смеће", "multiselect_grid_edit_date_time_err_read_only": "Не можете да измените датум елемената само за читање, прескачем", "multiselect_grid_edit_gps_err_read_only": "Не могу да изменим локацију елемената само за читање, прескачем", - "mute_memories": "Пригуши сец́ања", + "mute_memories": "Пригуши сећања", "my_albums": "Моји албуми", "name": "Име", "name_or_nickname": "Име или надимак", + "navigate": "Иди", "networking_settings": "Умрежавање", "networking_subtitle": "Управљајте подешавањима крајње тачке сервера", "never": "Никада", @@ -1211,7 +1271,7 @@ "new_version_available": "ДОСТУПНА НОВА ВЕРЗИЈА", "newest_first": "Најновије прво", "next": "Следеће", - "next_memory": "Следец́е сец́ање", + "next_memory": "Следеће сећање", "no": "Не", "no_albums_message": "Направите албум да бисте организовали своје фотографије и видео записе", "no_albums_with_name_yet": "Изгледа да још увек немате ниједан албум са овим именом.", @@ -1226,29 +1286,32 @@ "no_libraries_message": "Направите спољну библиотеку да бисте видели своје фотографије и видео записе", "no_name": "Нема имена", "no_notifications": "Нема обавештења", - "no_people_found": "Нису пронађени одговарајуц́и људи", + "no_people_found": "Нису пронађени одговарајући људи", "no_places": "Нема места", "no_results": "Нема резултата", "no_results_description": "Покушајте са синонимом или општијом кључном речи", "no_shared_albums_message": "Направите албум да бисте делили фотографије и видео записе са људима у вашој мрежи", + "not_available": "Недоступно", "not_in_any_album": "Нема ни у једном албуму", "not_selected": "Није изабрано", "note_apply_storage_label_to_previously_uploaded assets": "Напомена: Да бисте применили ознаку за складиштење на претходно уплоадиране датотеке, покрените", "notes": "Напомене", "notification_permission_dialog_content": "Да би укљуцили нотификације, идите у Опције и одаберите Дозволи.", - "notification_permission_list_tile_content": "Дајте дозволу за омогуц́авање обавештења.", + "notification_permission_list_tile_content": "Дајте дозволу за омогућавање обавештења.", "notification_permission_list_tile_enable_button": "Укључи Нотификације", "notification_permission_list_tile_title": "Дозволе за нотификације", - "notification_toggle_setting_description": "Омогуц́ите обавештења путем е-поште", + "notification_toggle_setting_description": "Омогућите обавештења путем е-поште", "notifications": "Нотификације", "notifications_setting_description": "Управљајте обавештењима", + "oauth": "OAuth", "official_immich_resources": "Званични Immich ресурси", "offline": "Одсутан (Оффлине)", + "offset": "Помак", "ok": "Ок", "oldest_first": "Најстарије прво", "on_this_device": "На овом уређају", "onboarding": "Приступање (Онбоардинг)", - "onboarding_privacy_description": "Следец́е (опциone) функције се ослањају на спољне услуге и могу се oneмогуц́ити у било ком тренутку у подешавањима администрације.", + "onboarding_privacy_description": "Следеће (необавезне) функције се ослањају на спољне услуге и могу се онемогућити у било ком тренутку у подешавањима.", "onboarding_theme_description": "Изаберите тему боја за свој налог. Ово можете касније да промените у подешавањима.", "onboarding_welcome_user": "Добродошли, {user}", "online": "Доступан (Онлине)", @@ -1277,7 +1340,7 @@ "partner_page_partner_add_failed": "Додавање партнера није успело", "partner_page_select_partner": "Изаберите партнера", "partner_page_shared_to_title": "Дељено са", - "partner_page_stop_sharing_content": "{partner} више нец́е моц́и да приступи вашим фотографијама.", + "partner_page_stop_sharing_content": "{partner} више неће моћи да приступи вашим фотографијама.", "partner_sharing": "Партнерско дељење", "partners": "Партнери", "password": "Шифра", @@ -1292,7 +1355,7 @@ "path": "Путања", "pattern": "Шаблон", "pause": "Пауза", - "pause_memories": "Паузирајте сец́ања", + "pause_memories": "Паузирајте сећања", "paused": "Паузирано", "pending": "На чекању", "people": "Особе", @@ -1303,16 +1366,17 @@ "permanent_deletion_warning_setting_description": "Прикажи упозорење када трајно бришете датотеке", "permanently_delete": "Трајно избрисати", "permanently_delete_assets_count": "Трајно избриши {count, plural, one {датотеку} other {датотеке}}", - "permanently_delete_assets_prompt": "Да ли сте сигурни да желите да трајно избришете {count, plural, one {ову датотеку?} other {ове # датотеке?}}Ово ц́е их такође уклонити {count, plural, one {из њиховог} other {из њихових}} албума.", + "permanently_delete_assets_prompt": "Да ли сте сигурни да желите да трајно избришете {count, plural, one {ову датотеку?} other {ове # датотеке?}}Ово ће их такође уклонити {count, plural, one {из њиховог} other {из њихових}} албума.", "permanently_deleted_asset": "Трајно избрисана датотека", "permanently_deleted_assets_count": "Трајно избрисано {count, plural, one {# датотека} other {# датотеке}}", + "permission": "Дозвола", "permission_onboarding_back": "Назад", "permission_onboarding_continue_anyway": "Ипак настави", "permission_onboarding_get_started": "Започните", "permission_onboarding_go_to_settings": "Иди на подешавања", "permission_onboarding_permission_denied": "Дозвола одбијена. Да бисте користили Immich, доделите дозволе за фотографије и видео записе у Подешавањима.", "permission_onboarding_permission_granted": "Дозвола одобрена! Спремни сте.", - "permission_onboarding_permission_limited": "Дозвола ограничена. Да бисте омогуц́или Immich-u да прави резервне копије и управља целом вашом колекцијом галерије, доделите дозволе за фотографије и видео записе у Подешавањима.", + "permission_onboarding_permission_limited": "Дозвола ограничена. Да бисте омогућили Immich-u да прави резервне копије и управља целом вашом колекцијом галерије, доделите дозволе за фотографије и видео записе у Подешавањима.", "permission_onboarding_request": "Immich захтева дозволу да види ваше фотографије и видео записе.", "person": "Особа", "person_birthdate": "Рођен(а) {date}", @@ -1330,27 +1394,27 @@ "places": "Места", "places_count": "{count, plural, one {{count, number} Место} other {{count, number} Места}}", "play": "Покрени", - "play_memories": "Покрени сец́ања", + "play_memories": "Покрени сећања", "play_motion_photo": "Покрени покретну фотографију", "play_or_pause_video": "Покрени или паузирај видео запис", "port": "порт", "preferences_settings_subtitle": "Управљајте подешавањима апликације", "preferences_settings_title": "Подешавања", + "preparing": "Припрема", "preset": "Унапред подешено", "preview": "Преглед", "previous": "Прошло", - "previous_memory": "Претходно сец́ање", - "previous_or_next_photo": "Претходна или следец́а фотографија", + "previous_memory": "Претходно сећање", + "previous_or_next_day": "Дан напред/назад", + "previous_or_next_month": "Месец напред/назад", + "previous_or_next_photo": "Фотографија напред/назад", + "previous_or_next_year": "Година напред/назад", "primary": "Примарна (Примарy)", "privacy": "Приватност", "profile": "Профил", "profile_drawer_app_logs": "Евиденција", - "profile_drawer_client_out_of_date_major": "Мобилна апликација је застарела. Молимо вас да је ажурирате на најновију главну верзију.", - "profile_drawer_client_out_of_date_minor": "Мобилна апликација је застарела. Молимо вас да је ажурирате на најновију споредну верзију.", "profile_drawer_client_server_up_to_date": "Клијент и сервер су најновије верзије", "profile_drawer_github": "ГитХуб", - "profile_drawer_server_out_of_date_major": "Сервер је застарео. Молимо вас да ажурирате на најновију главну верзију.", - "profile_drawer_server_out_of_date_minor": "Сервер је застарео. Молимо вас да ажурирате на најновију споредну верзију.", "profile_image_of_user": "Слика профила од корисника {user}", "profile_picture_set": "Профилна слика постављена.", "public_album": "Јавни албум", @@ -1374,8 +1438,8 @@ "purchase_license_subtitle": "Купите Immich да бисте подржали континуирани развој услуге", "purchase_lifetime_description": "Доживотна лиценца", "purchase_option_title": "ОПЦИЈЕ КУПОВИНЕ", - "purchase_panel_info_1": "Изградња Immich-a захтева много времена и труда, а имамо инжењере који раде на томе са пуним радним временом како бисмо је учинили што је могуц́е бољом. Наша мисија је да софтвер отвореног кода и етичке пословне праксе постану одржив извор прихода за програмере и да створимо екосистем који поштује приватност са стварним алтернативама експлоатативним услугама у облаку.", - "purchase_panel_info_2": "Пошто смо се обавезали да нец́емо додавати платне зидове, ова куповина вам нец́е дати никакве додатне функције у Immich-u. Ослањамо се на кориснике попут вас да подрже Immich-ов стални развој.", + "purchase_panel_info_1": "Изградња Immich-a захтева много времена и труда, а имамо инжењере који раде на томе са пуним радним временом како бисмо је учинили што је могуће бољом. Наша мисија је да софтвер отвореног кода и етичке пословне праксе постану одржив извор прихода за програмере и да створимо екосистем који поштује приватност са стварним алтернативама експлоатативним услугама у облаку.", + "purchase_panel_info_2": "Пошто смо се обавезали да нећемо додавати платне зидове, ова куповина ти неће дати никакве додатне функције у Immich-у. Ослањамо се на кориснике попут тебе да подрже Immich-ов стални развој.", "purchase_panel_title": "Подржите пројекат", "purchase_per_server": "По серверу", "purchase_per_user": "По кориснику", @@ -1394,9 +1458,9 @@ "reaction_options": "Опције реакције", "read_changelog": "Прочитајте дневник промена", "reassign": "Поново додај", - "reassigned_assets_to_existing_person": "Поново додељено {count, plural, one {# датотека} other {# датотеке}} постојец́ој {name, select, null {особи} other {{name}}}", + "reassigned_assets_to_existing_person": "Поново додељено {count, plural, one {# датотека} other {# датотеке}} постојећој {name, select, null {особи} other {{name}}}", "reassigned_assets_to_new_person": "Поново додељено {count, plural, one {# датотека} other {# датотеке}} новој особи", - "reassing_hint": "Доделите изабрана средства постојец́ој особи", + "reassing_hint": "Доделите изабрана средства постојећој особи", "recent": "Скорашњи", "recent-albums": "Недавни албуми", "recent_searches": "Скорашње претраге", @@ -1410,11 +1474,12 @@ "refresh_metadata": "Освежите метаподатке", "refresh_thumbnails": "Освежите сличице", "refreshed": "Освежено", - "refreshes_every_file": "Поново чита све постојец́е и нове датотеке", + "refreshes_every_file": "Поново чита све постојеће и нове датотеке", "refreshing_encoded_video": "Освежавање кодираног (енcodeд) видеа", "refreshing_faces": "Освежавање лица", "refreshing_metadata": "Освежавање мета-података", "regenerating_thumbnails": "Обнављање сличица", + "remote": "Удаљено", "remove": "Уклони", "remove_assets_album_confirmation": "Да ли сте сигурни да желите да уклоните {count, plural, one {# датотеку} other {# датотеке}} из албума?", "remove_assets_shared_link_confirmation": "Да ли сте сигурни да желите да уклоните {count, plural, one {# датотеку} other {# датотеке}} са ове дељене везе?", @@ -1437,7 +1502,7 @@ "removed_tagged_assets": "Уклоњена ознака из {count, plural, one {# датотеке} other {# датотека}}", "rename": "Преименуј", "repair": "Поправи", - "repair_no_results_message": "Овде ц́е се појавити датотеке које нису прац́ене и недостају", + "repair_no_results_message": "Овде ће се појавити датотеке које нису праћене и недостају", "replace_with_upload": "Замените са уплоад-ом", "repository": "Репозиторијум (Репоситорy)", "require_password": "Потребна лозинка", @@ -1453,13 +1518,14 @@ "restore": "Поврати", "restore_all": "Поврати све", "restore_user": "Поврати корисника", - "restored_asset": "Поврац́ено средство", + "restored_asset": "Повраћено средство", "resume": "Поново покрени", "retry_upload": "Покушајте поново да уплоадујете", "review_duplicates": "Прегледајте дупликате", "role": "Улога", "role_editor": "Уредник", "role_viewer": "Гледалац", + "running": "У току", "save": "Сачувај", "save_to_gallery": "Сачувај у галерију", "saved_api_key": "Сачуван АПИ кључ (кеy)", @@ -1496,7 +1562,7 @@ "search_filter_media_type_title": "Изаберите тип медија", "search_filter_people_title": "Изаберите људе", "search_for": "Тражи", - "search_for_existing_person": "Потражите постојец́у особу", + "search_for_existing_person": "Потражите постојећу особу", "search_no_more_result": "Нема више резултата", "search_no_people": "Без особа", "search_no_people_named": "Нема особа са именом „{name}“", @@ -1519,7 +1585,7 @@ "search_result_page_new_search_hint": "Нова претрага", "search_settings": "Претрага подешавања", "search_state": "Тражи регион...", - "search_suggestion_list_smart_search_hint_1": "Паметна претрага је подразумевано омогуц́ена, за претрагу метаподатака користите синтаксу ", + "search_suggestion_list_smart_search_hint_1": "Паметна претрага је подразумевано омогућена, за претрагу метаподатака користите синтаксу ", "search_suggestion_list_smart_search_hint_2": "м:ваш-појам-за-претрагу", "search_tags": "Претражи ознаке (tags)...", "search_timezone": "Претражи временску зону...", @@ -1552,6 +1618,7 @@ "server_info_box_server_url": "Сервер URL", "server_offline": "Сервер ван мреже (offline)", "server_online": "Сервер на мрежи (online)", + "server_privacy": "Приватност сервера", "server_stats": "Статистика сервера", "server_version": "Верзија сервера", "set": "Постави", @@ -1561,7 +1628,8 @@ "set_date_of_birth": "Подесите датум рођења", "set_profile_picture": "Постави профилну слику", "set_slideshow_to_fullscreen": "Поставите пројекцију слајдова на цео екран", - "setting_image_viewer_help": "Прегледач детаља прво учитава малу сличицу, затим преглед средње величине (ако је омогуц́ен), и на крају оригинал (ако је омогуц́ен).", + "set_stack_primary_asset": "Постави као примарну датотеку групе", + "setting_image_viewer_help": "Прегледач детаља прво учитава малу сличицу, затим преглед средње величине (ако је омогућен), и на крају оригинал (ако је омогућен).", "setting_image_viewer_original_subtitle": "Активирај учитавање слика у пуној резолуцији (Велика!). Деактивацијом ове ставке можеш да смањиш потрошњу интернета и заузетог простора на уређају.", "setting_image_viewer_original_title": "Учитај оригиналну слику", "setting_image_viewer_preview_subtitle": "Активирај учитавање слика у средњој резолуцији. Деактивирај да се директно учитава оригинал, или да се само користи минијатура.", @@ -1591,8 +1659,9 @@ "share_add_photos": "Додај фотографије", "share_assets_selected": "Изабрано је {count}", "share_dialog_preparing": "Припремање...", + "share_link": "Подели везу", "shared": "Дељено", - "shared_album_activities_input_disable": "Коментар је oneмогуц́ен", + "shared_album_activities_input_disable": "Коментар је онемогућен", "shared_album_activity_remove_content": "Да ли желите да обришете ову активност?", "shared_album_activity_remove_title": "Обриши активност", "shared_album_section_people_action_error": "Грешка при напуштању/уклањању из албума", @@ -1609,6 +1678,7 @@ "shared_link_clipboard_text": "Линк: {link}\nЛозинка: {password}", "shared_link_create_error": "Грешка при креирању дељеног linkа", "shared_link_edit_description_hint": "Унесите опис дељења", + "shared_link_edit_expire_after_option_day": "1 дан", "shared_link_edit_expire_after_option_days": "{count} дана", "shared_link_edit_expire_after_option_hour": "1 сат", "shared_link_edit_expire_after_option_hours": "{count} сати", @@ -1633,7 +1703,7 @@ "shared_link_manage_links": "Управљајте дељеним linkовима", "shared_link_options": "Опције дељене везе", "shared_links": "Дељене везе", - "shared_links_description": "Делите фотографије и видео записе помоц́у linkа", + "shared_links_description": "Делите фотографије и видео записе помоћу linkа", "shared_photos_and_videos_count": "{assetCount, plural, other {# дељене фотографије и видео записе.}}", "shared_with_me": "Дељено са мном", "shared_with_partner": "Дели се са {partner}", @@ -1686,30 +1756,32 @@ "sort_recent": "Најновија фотографија", "sort_title": "Наслов", "source": "Извор", - "stack": "Слагање", - "stack_duplicates": "Дупликати гомиле", - "stack_select_one_photo": "Изаберите једну главну фотографију за гомилу", - "stack_selected_photos": "Сложите изабране фотографије", - "stacked_assets_count": "Наслагано {count, plural, one {# датотека} other {# датотеке}}", - "stacktrace": "Веза до гомиле", + "stack": "Групиши", + "stack_action_prompt": "{count} груписано", + "stack_duplicates": "Групиши дупликате", + "stack_select_one_photo": "Изабери једну главну фотографију за групу", + "stack_selected_photos": "Групиши изабране фотографије", + "stacked_assets_count": "Груписано {count, plural, one {# датотека} other {# датотеке}}", + "stacktrace": "Стектрејс", "start": "Почетак", "start_date": "Датум почетка", "state": "Стање", "status": "Статус", "stop_motion_photo": "Заустави покретну фотографију", "stop_photo_sharing": "Желите да зауставите дељење фотографија?", - "stop_photo_sharing_description": "{partner} више нец́е моц́и да приступи вашим фотографијама.", + "stop_photo_sharing_description": "{partner} више неће моћи да приступи вашим фотографијама.", "stop_sharing_photos_with_user": "Престаните да делите своје фотографије са овим корисником", "storage": "Складиште (Storage space)", "storage_label": "Ознака за складиштење", "storage_quota": "Квота складиштења", "storage_usage": "Користи се {used} од {available}", "submit": "Достави", + "success": "Успешно", "suggestions": "Сугестије", "sunrise_on_the_beach": "Излазак сунца на плажи", "support": "Подршка", "support_and_feedback": "Подршка и повратне информације", - "support_third_party_description": "Ваша иммицх инсталација је спакована од стране трец́е стране. Проблеми са којима се суочавате могу бити узроковани тим пакетом, па вас молимо да им прво поставите проблеме користец́и доње везе.", + "support_third_party_description": "Ваша иммицх инсталација је спакована од стране треће стране. Проблеми са којима се суочавате могу бити узроковани тим пакетом, па вас молимо да им прво поставите проблеме користећи доње везе.", "swap_merge_direction": "Замените правац спајања", "sync": "Синхронизација", "sync_albums": "Синхронизуј албуме", @@ -1741,9 +1813,9 @@ "theme_setting_theme_subtitle": "Одабери тему система", "theme_setting_three_stage_loading_subtitle": "Тростепено учитавање можда убрза учитавање, по цену потрошње података", "theme_setting_three_stage_loading_title": "Активирај тростепено учитавање", - "they_will_be_merged_together": "Они ц́е бити спојени заједно", - "third_party_resources": "Ресурси трец́их страна", - "time_based_memories": "Сец́ања заснована на времену", + "they_will_be_merged_together": "Они ће бити спојени заједно", + "third_party_resources": "Ресурси трећих страна", + "time_based_memories": "Сећања заснована на времену", "timeline": "Временска линија", "timezone": "Временска зона", "to_archive": "Архивирај", @@ -1751,7 +1823,7 @@ "to_favorite": "Постави као фаворит", "to_login": "Пријава", "to_parent": "Врати се назад", - "to_trash": "Смец́е", + "to_trash": "Смеће", "toggle_settings": "Nameсти подешавања", "total": "Укупно", "total_usage": "Укупна употреба", @@ -1759,21 +1831,23 @@ "trash_all": "Баци све у отпад", "trash_count": "Отпад {count, number}", "trash_delete_asset": "Отпад/Избриши датотеку", - "trash_emptied": "Испразнио смец́е", - "trash_no_results_message": "Слике и видео записи у отпаду ц́е се појавити овде.", + "trash_emptied": "Испразнио смеће", + "trash_no_results_message": "Слике и видео записи у отпаду ће се појавити овде.", "trash_page_delete_all": "Обриши све", - "trash_page_empty_trash_dialog_content": "Да ли желите да испразните своја премештена средства? Ови предмети ц́е бити трајно уклоњени из Immich-a", - "trash_page_info": "Ставке избачене из отпада биц́е трајно обрисане након {days} дана", + "trash_page_empty_trash_dialog_content": "Да ли желите да испразните своја премештена средства? Ови предмети ће бити трајно уклоњени из Immich-a", + "trash_page_info": "Ставке избачене из отпада биће трајно обрисане након {days} дана", "trash_page_no_assets": "Нема елемената у отпаду", "trash_page_restore_all": "Врати све", "trash_page_select_assets_btn": "Изаберите средства", "trash_page_title": "Отпад ({count})", - "trashed_items_will_be_permanently_deleted_after": "Датотеке у отпаду ц́е бити трајно избрисане након {days, plural, one {# дан} few {# дана} other {# дана}}.", + "trashed_items_will_be_permanently_deleted_after": "Датотеке у отпаду ће бити трајно избрисане након {days, plural, one {# дан} few {# дана} other {# дана}}.", + "troubleshoot": "Решавање проблема", "type": "Врста", - "unable_to_change_pin_code": "Није могуц́е променити ПИН код", - "unable_to_setup_pin_code": "Није могуц́е подесити ПИН код", + "unable_to_change_pin_code": "Није могуће променити ПИН код", + "unable_to_setup_pin_code": "Није могуће подесити ПИН код", "unarchive": "Врати из архиве", "unarchived_count": "{count, plural, other {Неархивирано#}}", + "undo": "Опозови", "unfavorite": "Избаци из омиљених (унфаворите)", "unhide_person": "Откриј особу", "unknown": "Непознат", @@ -1790,9 +1864,11 @@ "unsaved_change": "Несачувана промена", "unselect_all": "Поништи све", "unselect_all_duplicates": "Поништи избор свих дупликата", - "unstack": "Разгомилај (Ун-стацк)", - "unstacked_assets_count": "Несложено {count, plural, one {# датотека} other {# датотеке}}", - "up_next": "Следец́е", + "unstack": "Разгрупиши", + "unstack_action_prompt": "{count} разгруписано", + "unstacked_assets_count": "Разгруписано {count, plural, one {# датотека} other {# датотеке}}", + "untagged": "Неозначено", + "up_next": "Следеће", "updated_at": "Ажурирано", "updated_password": "Ажурирана лозинка", "upload": "Уплоадуј", @@ -1810,6 +1886,7 @@ "uploading": "Отпремање", "url": "URL", "usage": "Употреба", + "use_biometric": "Користи биометрију", "use_current_connection": "користи тренутну везу", "use_custom_date_range": "Уместо тога користите прилагођени период", "user": "Корисник", @@ -1817,17 +1894,18 @@ "user_liked": "{user} је лајковао {type, select, photo {ову фотографију} video {овај видео запис} asset {ову датотеку} other {ово}}", "user_pin_code_settings": "ПИН код", "user_pin_code_settings_description": "Управљајте својим ПИН кодом", + "user_privacy": "Приватност корисника", "user_purchase_settings": "Куповина", "user_purchase_settings_description": "Управљајте куповином", "user_role_set": "Постави {user} као {role}", - "user_usage_detail": "Детаљи коришц́ења корисника", - "user_usage_stats": "Статистика коришц́ења налога", - "user_usage_stats_description": "Погледајте статистику коришц́ења налога", + "user_usage_detail": "Детаљи коришћења корисника", + "user_usage_stats": "Статистика коришћења налога", + "user_usage_stats_description": "Погледајте статистику коришћења налога", "username": "Корисничко име", "users": "Корисници", "utilities": "Алати", "validate": "Провери", - "validate_endpoint_error": "Молимо вас да унесете важец́и URL", + "validate_endpoint_error": "Молимо вас да унесете важећи URL", "variables": "Променљиве (вариаблес)", "version": "Верзија", "version_announcement_closing": "Твој пријатељ, Алекс", @@ -1836,7 +1914,7 @@ "version_history_item": "Инсталирано {version} {date}", "video": "Видео запис", "video_hover_setting": "Пусти сличицу видеа када лебди", - "video_hover_setting_description": "Пусти сличицу видеа када миш пређе преко ставке. Чак и када је oneмогуц́ена, репродукција се може покренути преласком миша преко икone за репродукцију.", + "video_hover_setting_description": "Пусти сличицу видеа када миш пређе преко ставке. Чак и када је онемогућена, репродукција се може покренути преласком миша преко иконе за репродукцију.", "videos": "Видео записи", "videos_count": "{count, plural, one {# видео запис} few {# видео записа} other {# видео записа}}", "view": "Гледај (виеw)", @@ -1847,15 +1925,16 @@ "view_link": "Погледај везу", "view_links": "Прикажи везе", "view_name": "Погледати", - "view_next_asset": "Погледајте следец́у датотеку", + "view_next_asset": "Погледајте следећу датотеку", "view_previous_asset": "Погледај претходну датотеку", "view_qr_code": "Погледајте QР код", - "view_stack": "Прикажи гомилу", - "viewer_remove_from_stack": "Уклони из стека", - "viewer_stack_use_as_main_asset": "Користи као главни ресурс", - "viewer_unstack": "Ун-Стацк", + "view_stack": "Прикажи групу", + "view_user": "Прикажи корисника", + "viewer_remove_from_stack": "Уклони из групе", + "viewer_stack_use_as_main_asset": "Користи као главну датотеку", + "viewer_unstack": "Разгрупиши", "visibility_changed": "Видљивост је промењена за {count, plural, one {# особу} other {# особе}}", - "waiting": "Чекам", + "waiting": "На чекању", "warning": "Упозорење", "week": "Недеља", "welcome": "Добродошли", diff --git a/i18n/sr_Latn.json b/i18n/sr_Latn.json index 06a76f8f0a..aa895cd20c 100644 --- a/i18n/sr_Latn.json +++ b/i18n/sr_Latn.json @@ -123,6 +123,10 @@ "logging_enable_description": "Omogući evidentiranje", "logging_level_description": "Kada je omogućeno, koji nivo evidencije koristiti.", "logging_settings": "Evidentiranje", + "machine_learning_availability_checks": "Provere dostupnosti", + "machine_learning_availability_checks_enabled": "Omogući provere dostupnosti", + "machine_learning_availability_checks_interval": "Interval provere", + "machine_learning_availability_checks_interval_description": "Interval u milisekundama između provera dostupnosti", "machine_learning_clip_model": "Model CLIP", "machine_learning_clip_model_description": "Naziv CLIP modela je naveden ovde. Imajte na umu da morate ponovo da pokrenete posao „Pametno pretraživanje“ za sve slike nakon promene modela.", "machine_learning_duplicate_detection": "Detekcija duplikata", @@ -182,11 +186,13 @@ "nightly_tasks_database_cleanup_setting": "Zadaci čiščenja baze podataka", "nightly_tasks_database_cleanup_setting_description": "Očisti stare, istekle podatke iz baze podataka", "nightly_tasks_generate_memories_setting": "Generiši sjećanja", - "nightly_tasks_generate_memories_setting_description": "Kreiraj nova sjećanja", + "nightly_tasks_generate_memories_setting_description": "Stvorite nova sećanja iz imovine", "nightly_tasks_missing_thumbnails_setting": "Generiši nedostajuće sličice", "nightly_tasks_missing_thumbnails_setting_description": "Dodajte elemente bez sličica u red za generisanje sličica", "nightly_tasks_settings": "Podešavanja noćnih zadataka", "nightly_tasks_settings_description": "Upravljaj noćnim zadacima", + "nightly_tasks_start_time_setting": "Vreme početka", + "nightly_tasks_start_time_setting_description": "Vreme kada server započinje noćne zadatke", "nightly_tasks_sync_quota_usage_setting_description": "Ažurirajte kvotu memorijskog prostora korisnika na osnovu trenutne upotrebe", "no_paths_added": "Nema dodatih putanja", "no_pattern_added": "Nije dodat obrazac", @@ -638,7 +644,6 @@ "comments_and_likes": "Komentari i lajkovi", "comments_are_disabled": "Komentari su onemogućeni", "common_create_new_album": "Kreiraj novi album", - "common_server_error": "Molimo vas da proverite mrežnu vezu, uverite se da je server dostupan i da su verzije aplikacija/servera kompatibilne.", "completed": "Završeno", "confirm": "Potvrdi", "confirm_admin_password": "Potvrdi Administrativnu Lozinku", @@ -793,7 +798,6 @@ "edit_tag": "Uredi oznaku (tag)", "edit_title": "Uredi titulu", "edit_user": "Uredi korisnika", - "edited": "Uređeno", "editor": "Urednik", "editor_close_without_save_prompt": "Promene neće biti sačuvane", "editor_close_without_save_title": "Zatvoriti uređivač?", @@ -1001,7 +1005,6 @@ "header_settings_field_validator_msg": "Vrednost ne može biti prazna", "header_settings_header_name_input": "Naziv zaglavlja", "header_settings_header_value_input": "Vrednost zaglavlja", - "headers_settings_tile_subtitle": "Definišite proksi zaglavlja koja aplikacija treba da šalje sa svakim mrežnim zahtevom", "headers_settings_tile_title": "Prilagođeni proksi zaglavci", "hi_user": "Zdravo {name} ({email})", "hide_all_people": "Sakrij sve osobe", @@ -1350,11 +1353,7 @@ "privacy": "Privatnost", "profile": "Profil", "profile_drawer_app_logs": "Evidencija", - "profile_drawer_client_out_of_date_major": "Mobilna aplikacija je zastarela. Molimo vas da je ažurirate na najnoviju glavnu verziju.", - "profile_drawer_client_out_of_date_minor": "Mobilna aplikacija je zastarela. Molimo vas da je ažurirate na najnoviju sporednu verziju.", "profile_drawer_client_server_up_to_date": "Klijent i server su najnovije verzije", - "profile_drawer_server_out_of_date_major": "Server je zastareo. Molimo vas da ažurirate na najnoviju glavnu verziju.", - "profile_drawer_server_out_of_date_minor": "Server je zastareo. Molimo vas da ažurirate na najnoviju sporednu verziju.", "profile_image_of_user": "Slika profila od korisnika {user}", "profile_picture_set": "Profilna slika postavljena.", "public_album": "Javni album", diff --git a/i18n/sv.json b/i18n/sv.json index b7c0f2100c..d8f45465af 100644 --- a/i18n/sv.json +++ b/i18n/sv.json @@ -28,10 +28,12 @@ "add_to_album": "Lägg till i album", "add_to_album_bottom_sheet_added": "Tillagd till {album}", "add_to_album_bottom_sheet_already_exists": "Redan i {album}", + "add_to_album_bottom_sheet_some_local_assets": "Vissa lokala tillgångar kunde inte läggas till i albumet", "add_to_album_toggle": "Växla val för {album}", "add_to_albums": "Lägg till i album", "add_to_albums_count": "Lägg till i album ({count})", "add_to_shared_album": "Lägg till i delat album", + "add_upload_to_stack": "Lägg till uppladdning till stack", "add_url": "Lägg till URL", "added_to_archive": "Tillagd i arkiv", "added_to_favorites": "Tillagd till favoriter", @@ -116,13 +118,20 @@ "library_scanning_enable_description": "Aktivera periodisk biblioteksskanning", "library_settings": "Externa bibliotek", "library_settings_description": "Hantera inställningar för externa bibliotek", - "library_tasks_description": "Sök igenom externa bibliotek efter nya och/eller ändrade objekt", + "library_tasks_description": "Sök igenom externa bibliotek efter nya och/eller förändrade objekt", "library_watching_enable_description": "Bevaka externa bibliotek för filändringar", - "library_watching_settings": "Bevaka bibliotek (EXPERIMENTELLT)", + "library_watching_settings": "Bevaka bibliotek [EXPERIMENTELLT]", "library_watching_settings_description": "Bevaka automatiskt filförändringar", "logging_enable_description": "Aktivera loggning", "logging_level_description": "Vilken loggnivå som ska användas vid aktivering.", "logging_settings": "Loggning", + "machine_learning_availability_checks": "Tillgänglighetskontroller", + "machine_learning_availability_checks_description": "Upptäck och föredrar automatiskt tillgängliga maskininlärningsservrar", + "machine_learning_availability_checks_enabled": "Aktivera tillgänglighetskontroller", + "machine_learning_availability_checks_interval": "Kontrollera intervall", + "machine_learning_availability_checks_interval_description": "Intervall i millisekunder mellan tillgänglighetskontroller", + "machine_learning_availability_checks_timeout": "Begär timeout", + "machine_learning_availability_checks_timeout_description": "Timeout i millisekunder för tillgänglighetskontroller", "machine_learning_clip_model": "CLIP-modell", "machine_learning_clip_model_description": "Namnet på en CLIP-modell listad här . Observera att du måste köra ett \"Smart Sökning\" jobb för alla bilder när du ändrar modell.", "machine_learning_duplicate_detection": "Dubblettdetektering", @@ -145,6 +154,18 @@ "machine_learning_min_detection_score_description": "Lägsta självsäkerhetsnivå för att en sida ska upptäckas mellan 0-1. Lägre värden upptäcker fler sidor men kan resultera i falska positiv.", "machine_learning_min_recognized_faces": "Minsta identifierade ansikten", "machine_learning_min_recognized_faces_description": "Minsta antal identifierade ansikten för att en person ska kunna skapas. Om detta ökas blir ansiktsigenkänningen mer exakt, men risken för att ett ansikte inte kopplas till en person ökar.", + "machine_learning_ocr": "OCR", + "machine_learning_ocr_description": "Använd maskininlärning för att känna igen text i bilder", + "machine_learning_ocr_enabled": "Aktivera OCR", + "machine_learning_ocr_enabled_description": "Om den är inaktiverad kommer bilder inte att genomgå textigenkänning.", + "machine_learning_ocr_max_resolution": "Maximal upplösning", + "machine_learning_ocr_max_resolution_description": "Förhandsvisningar över denna upplösning kommer att ändras i storlek samtidigt som bildförhållandet behålls. Högre värden är mer exakta, men tar längre tid att bearbeta och använder mer minne.", + "machine_learning_ocr_min_detection_score": "Lägsta detektionspoäng", + "machine_learning_ocr_min_detection_score_description": "Lägsta konfidenspoäng för att text ska kunna detekteras är mellan 0 och 1. Lägre värden kommer att detektera mer text men kan resultera i falska positiva resultat.", + "machine_learning_ocr_min_recognition_score": "Lägsta igenkänningspoäng", + "machine_learning_ocr_min_score_recognition_description": "Lägsta konfidenspoäng för att detekterad text ska kunna identifieras är 0–1. Lägre värden identifierar mer text men kan resultera i falska positiva resultat.", + "machine_learning_ocr_model": "OCR modell", + "machine_learning_ocr_model_description": "Servermodeller är mer exakta än mobilmodeller, men tar längre tid att bearbeta och använder mer minne.", "machine_learning_settings": "Inställningar För Maskininlärning", "machine_learning_settings_description": "Hantera funktioner och inställningar för maskininlärning", "machine_learning_smart_search": "Smart Sökning", @@ -202,6 +223,8 @@ "notification_email_ignore_certificate_errors_description": "Ignorera valideringsfel för TLS-certifikat (rekommenderas ej)", "notification_email_password_description": "Lösenord att använda för att verifiera identitet med epostservern", "notification_email_port_description": "Port på epostservern (t.ex. 25, 465 eller 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "Använd SMTPS (SMTP över TLS)", "notification_email_sent_test_email_button": "Skicka test-epost och spara", "notification_email_setting_description": "Inställningar för att skicka epostnotiser", "notification_email_test_email": "Skicka test-epost", @@ -234,6 +257,7 @@ "oauth_storage_quota_default_description": "Kvot i GiB som används när ingen fordran angetts.", "oauth_timeout": "Begäran tog för lång tid", "oauth_timeout_description": "Timeout för förfrågningar i millisekunder", + "ocr_job_description": "Använd maskininlärning för att känna igen text i bilder", "password_enable_description": "Logga in med epost och lösenord", "password_settings": "Lösenordsinloggning", "password_settings_description": "Hantera inställningar för lösenords-inloggning", @@ -324,7 +348,7 @@ "transcoding_max_b_frames": "Max B-ramar", "transcoding_max_b_frames_description": "Högre värden förbättrar kompressionseffektiviteten, men saktar ner kodningen. Kan vara inkompatibel med hårdvaruacceleration på äldre enheter. 0 avaktiverar B-frames, medan -1 anger detta värde automatiskt.", "transcoding_max_bitrate": "Max bithastighet", - "transcoding_max_bitrate_description": "En maximal bitrate kan göra filstorlekar mer förutsägbara till en liten kostnad på kvalitet. Vid 720p är typiska värden 2600 kbit/s för VP9 eller HEVC, eller 4500 kbit/s för H.264. Inaktiverad om satt till 0.", + "transcoding_max_bitrate_description": "En maximal bitrate kan göra filstorlekar mer förutsägbara till en liten kostnad på kvalitet. Vid 720p är typiska värden 2600 kbit/s för VP9 eller HEVC, eller 4500 kbit/s för H.264. Inaktiverad om satt till 0. När ingen enhet anges så kommer k (för kbit/s) användas; därför är 5000, 5000k och 5M (för Mbit/s) likvärdiga.", "transcoding_max_keyframe_interval": "Max nyckelbildruteintervall", "transcoding_max_keyframe_interval_description": "Sätter det maximala bildruteavståndet mellan nyckelbildrutor. Lägre värden försämrar kompressionseffektiviteten, men förbättrar söktiderna och kan förbättra kvaliteten i scener med snabb rörelse. 0 ställer in detta värde automatiskt.", "transcoding_optimal_description": "Videor som är högre än mållösning eller inte i ett accepterat format", @@ -342,7 +366,7 @@ "transcoding_target_resolution": "Förväntad upplösning", "transcoding_target_resolution_description": "En högre upplösning kan bevara fler detaljer men kan ta längre tid at koda, ha större fil storlek och kan försämra appens svarstid.", "transcoding_temporal_aq": "Temporär AQ", - "transcoding_temporal_aq_description": "Gäller endast NVENC. Ökar kvaliteten på scener med hög detaljrikedom och låg rörelse. Kanske inte är kompatibel med äldre enheter.", + "transcoding_temporal_aq_description": "Gäller endast NVENC. Temporal adaptiv kvantisering ökar kvaliteten på scener med hög detaljrikedom och låg rörelse. Kan vara inkompatibel med äldre enheter.", "transcoding_threads": "Trådar", "transcoding_threads_description": "Högre värden leder till snabbare kodning, men lämnar mindre utrymme för servern att bearbeta andra uppgifter medan den är aktiv. Detta värde bör inte vara mer än antalet CPU-kärnor. Maximerar användningen om den är inställd på 0.", "transcoding_tone_mapping": "Ton mappning", @@ -393,18 +417,18 @@ "advanced_settings_prefer_remote_subtitle": "Vissa enheter är mycket långsamma på att ladda miniatyrer från objekt på enheten. Aktivera den här inställningen för att ladda bilder från servern istället.", "advanced_settings_prefer_remote_title": "Föredra bilder från servern", "advanced_settings_proxy_headers_subtitle": "Definiera proxy-headers som Immich ska skicka med i varje närverksanrop", - "advanced_settings_proxy_headers_title": "Proxy-headers", - "advanced_settings_readonly_mode_subtitle": "Aktiverar skrivskyddat läge där foton endast kan visas. Saker som att välja flera bilder, dela, casta och ta bort är alla inaktiverade. Aktivera/inaktivera skrivskyddat läge via användaravatar från huvudskärmen", + "advanced_settings_proxy_headers_title": "Anpassade proxyheaders [EXPERIMENTELLT]", + "advanced_settings_readonly_mode_subtitle": "Aktiverar skrivskyddat-läge där foton endast kan visas. Följande funktioner inaktiveras: välj flera bilder, dela, casta, ta bort bilder. Aktivera/inaktivera skrivskyddat läge via profilbilden på appens hemskärm", "advanced_settings_readonly_mode_title": "Skrivskyddat läge", - "advanced_settings_self_signed_ssl_subtitle": "Hoppar över SSL-certifikatverifiering för serverändpunkten. Krävs för självsignerade certifikat.", - "advanced_settings_self_signed_ssl_title": "Tillåt självsignerade SSL-certifikat", + "advanced_settings_self_signed_ssl_subtitle": "Hoppar över verifiering av serverns SSL-certifikat. Krävs för självsignerade certifikat.", + "advanced_settings_self_signed_ssl_title": "Tillåt självsignerade SSL-certifikat [EXPERIMENTELLT]", "advanced_settings_sync_remote_deletions_subtitle": "Radera eller återställ automatiskt en resurs på den här enheten när den åtgärden utförs på webben", - "advanced_settings_sync_remote_deletions_title": "Synkonisera fjärradering [EXPERIMENTELL]", + "advanced_settings_sync_remote_deletions_title": "Synkronisera fjärradering [EXPERIMENTELL]", "advanced_settings_tile_subtitle": "Avancerade användarinställningar", "advanced_settings_troubleshooting_subtitle": "Aktivera funktioner för felsökning", "advanced_settings_troubleshooting_title": "Felsökning", - "age_months": "Ålder {months, plural, one {# month} other {# months}}", - "age_year_months": "Ålder 1 år, {months, plural, one {# month} other {# months}}", + "age_months": "Ålder {months, plural, one {# månad} other {# månader}}", + "age_year_months": "Ålder 1 år, {months, plural, one {# månad} other {# månader}}", "age_years": "{years, plural, other {Ålder #}}", "album_added": "Albumet har lagts till", "album_added_notification_setting_description": "Få ett e-postmeddelande när du läggs till i ett delat album", @@ -423,6 +447,7 @@ "album_remove_user_confirmation": "Är du säker på att du vill ta bort {user}?", "album_search_not_found": "Inga album hittades som matchade din sökning", "album_share_no_users": "Det verkar som att du har delat det här albumet med alla användare eller så har du inte någon användare att dela med.", + "album_summary": "Albumsammanfattning", "album_updated": "Albumet uppdaterat", "album_updated_setting_description": "Få ett e-postmeddelande när ett delat album har nya tillgångar", "album_user_left": "Lämnade {album}", @@ -456,10 +481,14 @@ "api_key_description": "Detta värde kommer bara att visas en gång. Se till att kopiera det innan du stänger fönstret.", "api_key_empty": "Ditt API-nyckelnamn ska inte vara tomt", "api_keys": "API-Nycklar", + "app_architecture_variant": "Variant (Arkitektur)", "app_bar_signout_dialog_content": "Är du säker på att du vill logga ut?", "app_bar_signout_dialog_ok": "Ja", "app_bar_signout_dialog_title": "Logga ut", + "app_download_links": "Länkar för appnedladdning", "app_settings": "Appinställningar", + "app_stores": "Appstore", + "app_update_available": "Appuppdatering är tillgänglig", "appears_in": "Visas i", "apply_count": "Tillämpa ({count, number})", "archive": "Arkiv", @@ -494,6 +523,8 @@ "asset_restored_successfully": "Objekt återställt", "asset_skipped": "Överhoppad", "asset_skipped_in_trash": "I papperskorgen", + "asset_trashed": "Tillgång kasserad", + "asset_troubleshoot": "Felsökning av tillgångar", "asset_uploaded": "Uppladdad", "asset_uploading": "Laddar upp...…", "asset_viewer_settings_subtitle": "Hantera inställningar för gallerivisare", @@ -527,8 +558,10 @@ "autoplay_slideshow": "Spela upp bildspel automatiskt", "back": "Bakåt", "back_close_deselect": "Tillbaka, stäng eller avmarkera", + "background_backup_running_error": "Bakgrundssäkerhetskopiering körs för närvarande, kan inte starta manuell säkerhetskopiering", "background_location_permission": "Tillåtelse för bakgrundsplats", "background_location_permission_content": "För att kunna byta nätverk när appen körs i bakgrunden måste Immich *alltid* ha åtkomst till exakt plats så att appen kan läsa av Wi-Fi-nätverkets namn", + "background_options": "Bakgrundsalternativ", "backup": "Säkerhetskopiera", "backup_album_selection_page_albums_device": "Album på enhet ({count})", "backup_album_selection_page_albums_tap": "Tryck en gång för att inkludera, tryck två gånger för att exkludera", @@ -536,8 +569,10 @@ "backup_album_selection_page_select_albums": "Välj album", "backup_album_selection_page_selection_info": "Info om valda objekt", "backup_album_selection_page_total_assets": "Antal unika objekt", + "backup_albums_sync": "Säkerhetskopiera album synkronisering", "backup_all": "Allt", "backup_background_service_backup_failed_message": "Säkerhetskopiering av foton och videor misslyckades. Försöker igen…", + "backup_background_service_complete_notification": "Säkerhetskopiering av tillgångar klar", "backup_background_service_connection_failed_message": "Anslutning till servern misslyckades. Försöker igen…", "backup_background_service_current_upload_notification": "Laddar upp {filename}", "backup_background_service_default_notification": "Söker efter nya objekt…", @@ -585,6 +620,7 @@ "backup_controller_page_turn_on": "Aktivera automatisk säkerhetskopiering", "backup_controller_page_uploading_file_info": "Laddar upp filinformation", "backup_err_only_album": "Kan inte ta bort det enda albumet", + "backup_error_sync_failed": "Synkroniseringen misslyckades. Det går inte att bearbeta säkerhetskopian.", "backup_info_card_assets": "objekt", "backup_manual_cancelled": "Avbrutet", "backup_manual_in_progress": "Uppladdning pågår redan. Försök igen om en liten stund", @@ -646,12 +682,16 @@ "change_password_description": "Detta är antingen första gången du loggar in i systemet eller så har en begäran gjorts om att ändra ditt lösenord. Vänligen ange det nya lösenordet nedan.", "change_password_form_confirm_password": "Bekräfta lösenord", "change_password_form_description": "Hej {name},\n\nDet är antingen första gången du loggar in i systemet, eller så har det skett en förfrågan om återställning av ditt lösenord. Ange ditt nya lösenord nedan.", + "change_password_form_log_out": "Logga ut från alla andra enheter", + "change_password_form_log_out_description": "Det rekommenderas att logga ut från alla andra enheter", "change_password_form_new_password": "Nytt lösenord", "change_password_form_password_mismatch": "Lösenorden matchar inte", "change_password_form_reenter_new_password": "Ange Nytt Lösenord Igen", "change_pin_code": "Ändra PIN-kod", "change_your_password": "Ändra ditt lösenord", "changed_visibility_successfully": "Synligheten har ändrats", + "charging": "Laddar", + "charging_requirement_mobile_backup": "Bakgrundssäkerhetskopiering kräver att enheten laddas", "check_corrupt_asset_backup": "Kontrollera om det finns korrupta säkerhetskopior av objekt", "check_corrupt_asset_backup_button": "Kontrollera", "check_corrupt_asset_backup_description": "Kör kontrollen endast över Wi-Fi och när alla objekt har säkerhetskopierats. Det kan ta några minuter.", @@ -670,8 +710,8 @@ "client_cert_import_success_msg": "Klientcertifikatet är importerat", "client_cert_invalid_msg": "Felaktig certifikatfil eller fel lösenord", "client_cert_remove_msg": "Klientcertifikatet är borttaget", - "client_cert_subtitle": "Stödjer endast formatet PKCS12 (.p12, .pfx). Import/borttagning av certifikat är tillgängligt endast före inloggning", - "client_cert_title": "SSL-Klientcertifikat", + "client_cert_subtitle": "Stödjer endast formatet PKCS12 (.p12, .pfx). import/borttagning av certifikat är tillgängligt endast före inloggning", + "client_cert_title": "SSL klientcertifikat [EXPERIMENTELLT]", "clockwise": "Medsols", "close": "Stäng", "collapse": "Kollapsa", @@ -683,7 +723,6 @@ "comments_and_likes": "Kommentarer & likes", "comments_are_disabled": "Kommentarer är avstängda", "common_create_new_album": "Skapa ett nytt album", - "common_server_error": "Kontrollera din nätverksanslutning, se till att servern går att nå och att app- och server-versioner är kompatibla.", "completed": "Klar", "confirm": "Bekräfta", "confirm_admin_password": "Bekräfta administratörslösenord", @@ -722,7 +761,8 @@ "create": "Skapa", "create_album": "Skapa album", "create_album_page_untitled": "Namnlös", - "create_library": "Skapa Bibliotek", + "create_api_key": "Skapa API-nyckel", + "create_library": "Skapa bibliotek", "create_link": "Skapa länk", "create_link_to_share": "Skapa länk att dela", "create_link_to_share_description": "Låt alla med länken se de valda fotona", @@ -738,6 +778,7 @@ "create_user": "Skapa användare", "created": "Skapad", "created_at": "Skapad", + "creating_linked_albums": "Skapar länkade album...", "crop": "Beskär", "curated_object_page_title": "Objekt", "current_device": "Aktuell enhet", @@ -750,6 +791,7 @@ "daily_title_text_date_year": "E, dd MMM, yyyy", "dark": "Mörk", "dark_theme": "Växla mörkt tema", + "date": "Datum", "date_after": "Datum efter", "date_and_time": "Datum och Tid", "date_before": "Datum före", @@ -864,12 +906,11 @@ "edit_tag": "Redigera tagg", "edit_title": "Redigera titel", "edit_user": "Redigera användare", - "edited": "Redigerad", "editor": "Redigerare", "editor_close_without_save_prompt": "Ändringarna kommer inte att sparas", "editor_close_without_save_title": "Stäng redigeraren?", "editor_crop_tool_h2_aspect_ratios": "Bildförhållande", - "editor_crop_tool_h2_rotation": "Rotation", + "editor_crop_tool_h2_rotation": "Vridning", "email": "Epost", "email_notifications": "E-postaviseringar", "empty_folder": "Mappen är tom", @@ -887,7 +928,9 @@ "error": "Fel", "error_change_sort_album": "Kunde inte ändra sorteringsordning för album", "error_delete_face": "Fel uppstod när ansikte skulle tas bort från objektet", + "error_getting_places": "Det gick inte att hämta platser", "error_loading_image": "Fel vid bildladdning", + "error_loading_partners": "Fel vid inläsning av partner: {error}", "error_saving_image": "Fel: {error}", "error_tag_face_bounding_box": "Fel vid taggning av ansikte – kan inte hämta koordinater för begränsningsruta", "error_title": "Fel – något gick fel", @@ -1013,11 +1056,12 @@ "unable_to_update_user": "Kunde inte uppdatera användare", "unable_to_upload_file": "Det går inte att ladda upp filen" }, - "exif": "Exif", + "exif": "EXIF", "exif_bottom_sheet_description": "Lägg till beskrivning...", "exif_bottom_sheet_description_error": "Fel vid uppdatering av beskrivningen", "exif_bottom_sheet_details": "DETALJER", "exif_bottom_sheet_location": "PLATS", + "exif_bottom_sheet_no_description": "Ingen beskrivning", "exif_bottom_sheet_people": "PERSONER", "exif_bottom_sheet_person_add_person": "Lägg till namn", "exit_slideshow": "Avsluta bildspel", @@ -1052,9 +1096,11 @@ "favorites_page_no_favorites": "Inga favoritobjekt hittades", "feature_photo_updated": "Funktionsfoto uppdaterad", "features": "Funktioner", + "features_in_development": "Funktioner i utveckling", "features_setting_description": "Hantera appens funktioner", "file_name": "Filnamn", "file_name_or_extension": "Filnamn eller -tillägg", + "file_size": "Filstorlek", "filename": "Filnamn", "filetype": "Filtyp", "filter": "Filter", @@ -1069,7 +1115,7 @@ "folders_feature_description": "Bläddra i mappvyn för foton och videoklipp i filsystemet", "forgot_pin_code_question": "Glömt din pinkod?", "forward": "Framåt", - "gcast_enabled": "Google Cast", + "gcast_enabled": "Google-Cast", "gcast_enabled_description": "Denna funktion läser in externa resurser från Google för att fungera.", "general": "Allmänt", "geolocation_instruction_location": "Klicka på en tillgång med GPS-koordinater för att använda dess plats, eller välj en plats direkt från kartan", @@ -1094,11 +1140,10 @@ "hash_asset": "Hash-tillgång", "hashed_assets": "Hash-tillgångar", "hashing": "Hashning", - "header_settings_add_header_tip": "Lägg Till Header", + "header_settings_add_header_tip": "Lägg till header", "header_settings_field_validator_msg": "Värdet kan inte vara tomt", "header_settings_header_name_input": "Header-namn", "header_settings_header_value_input": "Header-värde", - "headers_settings_tile_subtitle": "Definiera proxy-headers som appen ska skicka med i varje närverksanrop", "headers_settings_tile_title": "Anpassade proxy-headers", "hi_user": "Hej {name} ({email})", "hide_all_people": "Göm alla personer", @@ -1211,13 +1256,15 @@ "link_to_oauth": "Länk till OAuth", "linked_oauth_account": "Länkat OAuth konto", "list": "Lista", - "loading": "Laddar", + "loading": "Inläsning", "loading_search_results_failed": "Det gick inte att läsa in sökresultat", "local": "Lokalt", "local_asset_cast_failed": "Det går inte att casta en tillgång som inte har laddats upp till servern", "local_assets": "Lokala tillgångar", + "local_media_summary": "Sammanfattning av lokala medier", "local_network": "Lokalt nätverk", "local_network_sheet_info": "Appen kommer ansluta till servern via denna URL när det specificerade WiFi-nätverket används", + "location": "Position", "location_permission": "Plats-rättighet", "location_permission_content": "För att använda funktionen för automatisk växling behöver Immich behörighet till exakt plats så att appen kan läsa av det aktuella Wi-Fi-nätverkets namn", "location_picker_choose_on_map": "Välj på karta", @@ -1227,6 +1274,7 @@ "location_picker_longitude_hint": "Ange din longitud här", "lock": "Lås", "locked_folder": "Låst Mapp", + "log_detail_title": "Loggdetalj", "log_out": "Logga ut", "log_out_all_devices": "Logga ut alla enheter", "logged_in_as": "Inloggad som {user}", @@ -1257,6 +1305,7 @@ "login_password_changed_success": "Uppdatering av lösenord lyckades", "logout_all_device_confirmation": "Är du säker på att du vill logga ut från alla enheter?", "logout_this_device_confirmation": "Är du säker på att du vill logga ut från denna enhet?", + "logs": "Loggar", "longitude": "Longitud", "look": "Titta", "loop_videos": "Loopa videor", @@ -1299,6 +1348,7 @@ "mark_as_read": "Markera som läst", "marked_all_as_read": "Markerade alla som lästa", "matches": "Matchar", + "matching_assets": "Matchande tillgångar", "media_type": "Mediatyp", "memories": "Minnen", "memories_all_caught_up": "Du är ikapp", @@ -1319,6 +1369,8 @@ "minute": "Minut", "minutes": "Minuter", "missing": "Saknade", + "mobile_app": "Mobilapp", + "mobile_app_download_onboarding_note": "Ladda ner den medföljande mobilappen med följande alternativ", "model": "Modell", "month": "Månad", "monthly_title_text_date_format": "MMMM y", @@ -1337,18 +1389,23 @@ "my_albums": "Mina album", "name": "Namn", "name_or_nickname": "Namn eller smeknamn", + "navigate": "Navigera", + "navigate_to_time": "Navigera till tid", "network_requirement_photos_upload": "Använd mobildata för att säkerhetskopiera foton", "network_requirement_videos_upload": "Använd mobildata för att säkerhetskopiera videor", + "network_requirements": "Nätverkskrav", "network_requirements_updated": "Nätverkskraven har ändrats, återställer säkerhetskopieringskön", "networking_settings": "Nätverk", "networking_subtitle": "Hantera inställningar för server-endpointen", "never": "aldrig", "new_album": "Nytt album", "new_api_key": "Ny API-nyckel", + "new_date_range": "Nytt datumintervall", "new_password": "Nytt lösenord", "new_person": "Ny person", "new_pin_code": "Ny PIN-kod", "new_pin_code_subtitle": "Det här är första gången du öppnar den låsta mappen. Skapa en PIN-kod för att säkert få åtkomst till den här sidan", + "new_timeline": "Ny tidslinje", "new_user_created": "Ny användare skapad", "new_version_available": "NY VERSION TILLGÄNGLIG", "newest_first": "Nyast först", @@ -1362,20 +1419,25 @@ "no_assets_message": "KLICKA FÖR ATT LADDA UPP DIN FÖRSTA BILD", "no_assets_to_show": "Inga objekt att visa", "no_cast_devices_found": "Inga Cast-enheter hittades", + "no_checksum_local": "Ingen kontrollsumma tillgänglig - kan inte hämta lokala tillgångar", + "no_checksum_remote": "Ingen kontrollsumma tillgänglig - kan inte hämta fjärrtillgång", "no_duplicates_found": "Inga dubbletter hittades.", "no_exif_info_available": "EXIF-information ej tillgänglig", "no_explore_results_message": "Ladda upp fler bilder för att utforska din samling.", "no_favorites_message": "Lägg till favoriter för att snabbt hitta dina bästa bilder och videor", "no_libraries_message": "Skapa ett externt bibliotek för att se dina bilder och videor", + "no_local_assets_found": "Inga lokala tillgångar hittades med denna kontrollsumma", "no_locked_photos_message": "Foton och videor i den låsta mappen är dolda och visas inte när du bläddrar eller söker i ditt bibliotek.", "no_name": "Inget namn", "no_notifications": "Inga aviseringar", "no_people_found": "Inga matchande personer hittade", "no_places": "Inga platser", + "no_remote_assets_found": "Inga fjärrtillgångar hittades med denna kontrollsumma", "no_results": "Inga resultat", "no_results_description": "Pröva en synonym eller ett annat mer allmänt sökord", "no_shared_albums_message": "Skapa ett album för att dela bilder och videor med andra personer", "no_uploads_in_progress": "Inga uppladdningar pågår", + "not_available": "N/A", "not_in_any_album": "Inte i något album", "not_selected": "Ej vald", "note_apply_storage_label_to_previously_uploaded assets": "Obs: Om du vill använda lagringsetiketten på tidigare uppladdade tillgångar kör du", @@ -1389,6 +1451,9 @@ "notifications": "Notifikationer", "notifications_setting_description": "Hantera aviseringar", "oauth": "OAuth", + "obtainium_configurator": "Obtainium-konfigurator", + "obtainium_configurator_instructions": "Använd Obtainium för att installera och uppdatera Android-appen direkt från Immichs GitHub-version. Skapa en API-nyckel och välj en variant för att skapa din Obtainium-konfigurationslänk", + "ocr": "OCR", "official_immich_resources": "Officiella Immich-resurser", "offline": "Frånkopplad", "offset": "Förskjutning", @@ -1470,7 +1535,7 @@ "permission_onboarding_permission_granted": "Rättigheten beviljad! Du är klar.", "permission_onboarding_permission_limited": "Rättighet begränsad. För att låta Immich säkerhetskopiera och hantera hela ditt galleri, tillåt foto- och video-rättigheter i Inställningar.", "permission_onboarding_request": "Immich kräver tillstånd för att se dina foton och videor.", - "person": "Person", + "person": "Individ", "person_age_months": "{months, plural, one {# månad} other {# månader}} gammal", "person_age_year_months": "1 år, {months, plural, one {# månad} other {# månader}} gammal", "person_age_years": "{years, plural, other {# år}} gammal", @@ -1493,10 +1558,14 @@ "play_memories": "Spela upp minnen", "play_motion_photo": "Spela upp rörligt foto", "play_or_pause_video": "Spela upp eller pausa video", + "play_original_video": "Spela upp originalvideo", + "play_original_video_setting_description": "Föredra uppspelning av originalvideor framför omkodade videor. Om originalresursen inte är kompatibel kanske den inte spelas upp korrekt.", + "play_transcoded_video": "Spela upp omkodad video", "please_auth_to_access": "Vänligen autentisera för att få åtkomst", "port": "Port", "preferences_settings_subtitle": "Hantera appens inställningar", "preferences_settings_title": "Inställningar", + "preparing": "Förbereder", "preset": "Förinställt värde", "preview": "Förhandsvisning", "previous": "Föregående", @@ -1509,18 +1578,14 @@ "privacy": "Sekretess", "profile": "Profil", "profile_drawer_app_logs": "Loggar", - "profile_drawer_client_out_of_date_major": "Mobilappen är föråldrad. Uppdatera till senaste versionen.", - "profile_drawer_client_out_of_date_minor": "Mobilappen är föråldrad. Uppdatera till senaste versionen.", "profile_drawer_client_server_up_to_date": "Klient och server är uppdaterade", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Skrivskyddat läge aktiverat. Långtryck användaravatarikonen för att avsluta.", - "profile_drawer_server_out_of_date_major": "Servern har en föråldrad mjukvara. Uppdatera till senaste versionen.", - "profile_drawer_server_out_of_date_minor": "Servern har en föråldrad mjukvara. Uppdatera till senaste versionen.", "profile_image_of_user": "{user} profilbild", "profile_picture_set": "Profilbild vald.", "public_album": "Publikt album", "public_share": "Offentlig delning", - "purchase_account_info": "Supporter", + "purchase_account_info": "Anhängare", "purchase_activated_subtitle": "Tack för att du stödjer Immich och open source-mjukvara", "purchase_activated_time": "Aktiverad {date}", "purchase_activated_title": "Aktiveringan av din nyckel lyckades", @@ -1562,6 +1627,7 @@ "read_changelog": "Läs ändringslogg", "readonly_mode_disabled": "Skrivskyddat läge inaktiverat", "readonly_mode_enabled": "Skrivskyddat läge aktiverat", + "ready_for_upload": "Redo för uppladdning", "reassign": "Omfördela", "reassigned_assets_to_existing_person": "Tilldelade om {count, plural, one {# objekt} other {# objekt}} till {name, select, null {an existing person} other {{name}}}", "reassigned_assets_to_new_person": "Tilldelade om {count, plural, one {# objekt} other {# objekt}} till en ny persson", @@ -1586,6 +1652,7 @@ "regenerating_thumbnails": "Uppdaterar miniatyrer", "remote": "Fjärrr", "remote_assets": "Fjärrtillgångar", + "remote_media_summary": "Sammanfattning av fjärrmedia", "remove": "Ta bort", "remove_assets_album_confirmation": "Är du säker på att du vill ta bort {count, plural, one {# asset} other {# assets}} från albumet?", "remove_assets_shared_link_confirmation": "Är du säker på att du vill ta bort {count, plural, one {# asset} other {# assets}} från denna delade länk?", @@ -1630,6 +1697,7 @@ "reset_sqlite_confirmation": "Är du säker på att du vill återställa SQLite-databasen? Du måste logga ut och logga in igen för att synkronisera om data", "reset_sqlite_success": "Återställde SQLite-databasen", "reset_to_default": "Återställ till standard", + "resolution": "Upplösning", "resolve_duplicates": "Lös dubletter", "resolved_all_duplicates": "Lös alla dubletter", "restore": "Återställ", @@ -1648,6 +1716,7 @@ "running": "Igångsatt", "save": "Spara", "save_to_gallery": "Spara i galleri", + "saved": "Sparad", "saved_api_key": "Sparad API-nyckel", "saved_profile": "Sparade profil", "saved_settings": "Sparade inställningar", @@ -1664,6 +1733,9 @@ "search_by_description_example": "Vandringsdag i Sapa", "search_by_filename": "Sök efter filnamn eller filändelse", "search_by_filename_example": "t.ex. IMG_1234.JPG eller PNG", + "search_by_ocr": "Sök efter OCR", + "search_by_ocr_example": "Latte", + "search_camera_lens_model": "Sök kameraobjektiv...", "search_camera_make": "Sök efter kameratillverkare...", "search_camera_model": "Sök efter kameramodell...", "search_city": "Sök efter stad...", @@ -1680,6 +1752,7 @@ "search_filter_location_title": "Välj plats", "search_filter_media_type": "Mediatyp", "search_filter_media_type_title": "Välj mediatyp", + "search_filter_ocr": "Sök efter OCR", "search_filter_people_title": "Välj personer", "search_for": "Sök efter", "search_for_existing_person": "Sök efter befintlig person", @@ -1742,6 +1815,7 @@ "server_online": "Server online", "server_privacy": "Serversekretess", "server_stats": "Serverstatistik", + "server_update_available": "Serveruppdatering är tillgänglig", "server_version": "Serverversion", "set": "Välj", "set_as_album_cover": "Ange som albumomslag", @@ -1770,6 +1844,8 @@ "setting_notifications_subtitle": "Anpassa dina notis-inställningar", "setting_notifications_total_progress_subtitle": "Övergripande uppladdningsförlopp (klar/totala tillgångar)", "setting_notifications_total_progress_title": "Visa totalt uppladdningsförlopp", + "setting_video_viewer_auto_play_subtitle": "Börja automatiskt spela upp videor när de öppnas", + "setting_video_viewer_auto_play_title": "Automatisk uppspelning av video", "setting_video_viewer_looping_title": "Loopar", "setting_video_viewer_original_video_subtitle": "Spela originalet när en video strömmas från servern, även när en transkodad version är tillgänglig. Kan leda till buffring. Videor som är tillgängliga lokalt spelas i originalkvalitet oavsett denna inställning.", "setting_video_viewer_original_video_title": "Tvinga orginalvideo", @@ -1861,6 +1937,7 @@ "show_slideshow_transition": "Visa bildspelsövergång", "show_supporter_badge": "Supporteremblem", "show_supporter_badge_description": "Visa supporteremblem", + "show_text_search_menu": "Visa textsökningsmeny", "shuffle": "Blanda", "sidebar": "Sidopanel", "sidebar_display_description": "Visa en länk till vyn i sidofältet", @@ -1891,6 +1968,7 @@ "stacktrace": "Stapelspårning", "start": "Starta", "start_date": "Startdatum", + "start_date_before_end_date": "Startdatumet måste vara före slutdatumet", "state": "Stat", "status": "Status", "stop_casting": "Sluta casta", @@ -1900,14 +1978,14 @@ "stop_sharing_photos_with_user": "Sluta dela dina bilder med denna användaren", "storage": "Lagring", "storage_label": "Förvaringsetikett", - "storage_quota": "Lagrinkskvot", + "storage_quota": "Lagringskvot", "storage_usage": "{used} av {available} används", "submit": "Skicka", "success": "Framgång", "suggestions": "Förslag", "sunrise_on_the_beach": "Soluppgång på stranden", "support": "Support", - "support_and_feedback": "Support & Feedback", + "support_and_feedback": "Support och Feedback", "support_third_party_description": "Din Immich-installation paketerades av en tredje part. Problem som du upplever kan orsakas av det paketet, så vänligen ta upp problem med dem i första hand med hjälp av länkarna nedan.", "swap_merge_direction": "Byt sammanfogningsriktning", "sync": "Synka", @@ -1947,6 +2025,7 @@ "theme_setting_three_stage_loading_title": "Aktivera trestegsladdning", "they_will_be_merged_together": "De kommer att slås samman", "third_party_resources": "Tredjepartsresurser", + "time": "Tid", "time_based_memories": "Tidsbaserade minnen", "timeline": "Tidslinje", "timezone": "Tidszon", @@ -1979,6 +2058,7 @@ "troubleshoot": "Felsök", "type": "Typ", "unable_to_change_pin_code": "Kunde inte ändra pinkod", + "unable_to_check_version": "Det går inte att kontrollera app- eller serverversionen", "unable_to_setup_pin_code": "Kunde inte konfigurera pinkod", "unarchive": "Ångra arkivering", "unarchive_action_prompt": "{count} borttagen från arkivet", @@ -2093,5 +2173,6 @@ "yes": "Ja", "you_dont_have_any_shared_links": "Du har inga delade länkar", "your_wifi_name": "Ditt Wi-Fi-namn", - "zoom_image": "Zooma bild" + "zoom_image": "Zooma bild", + "zoom_to_bounds": "Zooma till gränser" } diff --git a/i18n/ta.json b/i18n/ta.json index 3027bea3b3..90a7f8e214 100644 --- a/i18n/ta.json +++ b/i18n/ta.json @@ -28,10 +28,12 @@ "add_to_album": "ஆல்பமில் சேர்க்க", "add_to_album_bottom_sheet_added": "{album}-இல் சேர்க்கப்பட்டது", "add_to_album_bottom_sheet_already_exists": "ஏற்கனவே {album}-இல் உள்ளது", + "add_to_album_bottom_sheet_some_local_assets": "சில உள்ளக சொத்துக்களை ஆல்பத்தில் சேர்க்க முடியவில்லை", "add_to_album_toggle": "{album} க்கான தேர்வை மாற்று", "add_to_albums": "ஆல்பத்தில் சேர்", "add_to_albums_count": "ஆல்பங்களில் சேர்({count})", "add_to_shared_album": "பகிரப்பட்ட ஆல்பமில் சேர்க்க", + "add_upload_to_stack": "அடுக்கில் பதிவேற்றத்தைச் சேர்", "add_url": "URL ஐச் சேர்க்கவும்", "added_to_archive": "காப்பகத்தில் சேர்க்கப்பட்டது", "added_to_favorites": "விருப்பங்களில் (பேவரிட்ஸ்) சேர்க்கப்பட்டது", @@ -39,16 +41,16 @@ "admin": { "add_exclusion_pattern_description": "விலக்கு வடிவங்களைச் சேர்க்கவும். *, **, மற்றும் ? ஆதரிக்கப்படுகிறது. \"Raw\" என்ற பெயரிடப்பட்ட எந்த கோப்பகத்திலும் உள்ள எல்லா கோப்புகளையும் புறக்கணிக்க, \"**/Raw/**\" ஐப் பயன்படுத்தவும். \".tif\" இல் முடியும் எல்லா கோப்புகளையும் புறக்கணிக்க, \"**/*.tif\" ஐப் பயன்படுத்தவும். ஒரு முழுமையான பாதையை புறக்கணிக்க, \"/path/to/ignore/**\" ஐப் பயன்படுத்தவும்.", "admin_user": "நிர்வாக பயனர்", - "asset_offline_description": "இந்த வெளிப்புற நூலக சொத்து இனி வட்டில் காணப்படவில்லை மற்றும் குப்பைக்கு நகர்த்தப்பட்டுள்ளது. கோப்பு நூலகத்திற்குள் நகர்த்தப்பட்டிருந்தால், புதிய தொடர்புடைய சொத்துக்கான உங்கள் காலவரிசையை சரிபார்க்கவும். இந்த சொத்தை மீட்டெடுக்க, கீழேயுள்ள கோப்பு பாதையை இம்மிச் மூலம் அணுகலாம் மற்றும் நூலகத்தை ச்கேன் செய்ய முடியும் என்பதை உறுதிப்படுத்தவும்.", - "authentication_settings": "அடையாள உறுதிப்படுத்தல் அமைப்புகள் (செட்டிங்ஸ்)", - "authentication_settings_description": "கடவுச்சொல், OAuth, மற்றும் பிற அடையாள அமைப்புகள்", - "authentication_settings_disable_all": "எல்லா உள்நுழைவு முறைகளையும் நிச்சயமாக முடக்க விரும்புகிறீர்களா? உள்நுழைவு முற்றிலும் முடக்கப்படும்.", - "authentication_settings_reenable": "மீண்டும் இயக்க, சர்வர் கட்டளை பயன்படுத்தவும்.", + "asset_offline_description": "இந்த வெளிப்புற நூலகச் சொத்து (external library asset) இனி கோப்புப் பதிப்பில் காணப்படவில்லை மற்றும் குப்பைத்தொட்டியில் (trash) நகர்த்தப்பட்டுள்ளது.கோப்பு நூலகத்தில் உள்ளே நகர்த்தப்பட்டிருந்தால், புதிய இணை சொத்தை (corresponding asset) கண்டுபிடிக்க உங்கள் காலவரிசையை (timeline) சரிபார்க்கவும். இந்த சொத்தை மீட்டமைக்க, கீழே உள்ள கோப்புப் பாதை Immich மூலம் அணுகக்கூடியதா என்பதை உறுதி செய்து, நூலகத்தை (library) மீண்டும் ஸ்கேன் செய்யவும்.", + "authentication_settings": "அங்கீகார அமைப்புகள்", + "authentication_settings_description": "அடையாளச் சொல்லுகள் (Password), OAuth மற்றும் பிற அங்கீகார அமைப்புகளை நிர்வகிக்கவும்", + "authentication_settings_disable_all": "நீங்கள் உண்மையில் அனைத்து உள்நுழைவு முறைகளையும் முடக்க விரும்புகிறீர்களா? உள்நுழைவு முழுமையாக முடக்கப்படும்.", + "authentication_settings_reenable": "மீண்டும் செயல்படுத்த, சேவையகம் கட்டளை பயன்படுத்தவும்.", "background_task_job": "பின்னணி பணிகள்", - "backup_database": "தரவுத்தள காப்புப்பிரதியை உருவாக்கு", - "backup_database_enable_description": "தரவுத்தள திணிப்புகள் இயக்கவும்", - "backup_keep_last_amount": "வைத்திருக்க முந்தைய திணிப்புகள் அளவு", - "backup_onboarding_1_description": "மேகக்கட்டத்தில் அல்லது மற்றொரு உடல் இடத்தில் ஆஃப்சைட் நகல்.", + "backup_database": "தரவுத்தள நகல் உருவாக்கவும்", + "backup_database_enable_description": "தரவுத்தள நகல்களை இயக்கவும்", + "backup_keep_last_amount": "வைத்திருக்க முந்தைய நகல்களின் அளவு", + "backup_onboarding_1_description": "மேகம் அல்லது வேறு இடத்தில் நகல்.", "backup_onboarding_2_description": "வெவ்வேறு சாதனங்களில் உள்ள நகல் பிரதிகள். இதில் முக்கிய கோப்புகள் மற்றும் அந்தக் கோப்புகளின் நகல் காப்புப்பிரதி ஆகியவை அடங்கும்.", "backup_onboarding_3_description": "உங்கள் தரவின் மொத்த கோப்புகள் அசல் மற்றும் நகல்கள் உட்பட. இதில் 1 வெளிப்புற நகல் மற்றும் 2 சாதனப் பிரதிகள் அடங்கும்.", "backup_onboarding_description": "உங்கள் தரவை பாதுகாப்பதற்காக ஒரு 3-2-1 காப்புப் பிரதி பரிந்துரைக்கப்படுகிறது. முழுமையான காப்பு பாதுகாப்பு தீர்விற்காக, நீங்கள் பதிவேற்றிய புகைப்படங்கள்/வீடியோக்கள் மற்றும் Immich தரவுத்தளத்தின் நகல்களையும் வைத்திருக்க வேண்டும்.", @@ -56,7 +58,7 @@ "backup_onboarding_parts_title": "3-2-1 காப்புப்பிரதியில் பின்வருவன அடங்கும்:", "backup_onboarding_title": "காப்புப்பிரதிகள்", "backup_settings": "தரவுத்தள திணிப்பு அமைப்புகள்", - "backup_settings_description": "தரவுத்தள திணிப்பு அமைப்புகளை நிர்வகிக்கவும்", + "backup_settings_description": "தரவுத்தள நகல் அமைப்புகளை நிர்வகி.", "cleared_jobs": "முடித்த வேலைகள்: {job}", "config_set_by_file": "கட்டமைப்பு, தற்போது ஒரு கட்டமைப்பு கோப்பு மூலம் அமைக்கப்பட்டுள்ளது", "confirm_delete_library": "{library} படங்கள் நூலகத்தை நிச்சயமாக நீக்க விரும்புகிறீர்களா?", @@ -67,7 +69,7 @@ "confirm_user_pin_code_reset": "{user} இன் பின் குறியீட்டை மீட்டமைக்க விரும்புகிறீர்களா?", "create_job": "வேலையை உருவாக்கு", "cron_expression": "க்ரோன் வெளிப்பாடு", - "cron_expression_description": "CRON வடிவமைப்பைப் பயன்படுத்தி ச்கேனிங் இடைவெளியை அமைக்கவும். மேலும் தகவலுக்கு எ.கா. <இணைப்பு> க்ரோன்டாப் குரு ", + "cron_expression_description": "CRON வடிவமைப்பைப் பயன்படுத்தி ச்கேனிங் இடைவெளியை அமைக்கவும். மேலும் தகவலுக்கு எ.கா. க்ரோன்டாப் குரு ", "cron_expression_presets": "க்ரோன் வெளிப்பாடு முன்னமைவுகள்", "disable_login": "உள்நுழைவை முடக்கு", "duplicate_detection_job_description": "ஒத்த படங்களைக் கண்டறிய, சொத்துக்களில் இயந்திரக் கற்றலை இயக்கவும். ஸ்மார்ட் தேடலை நம்பியுள்ளது", @@ -116,9 +118,9 @@ "library_scanning_enable_description": "நியமிக்கப்பட்ட புகைப்பட நூலக ஸ்கேனிங்கை இயக்கு", "library_settings": "வெளிப்புற புகைப்பட நூலகம்", "library_settings_description": "வெளிப்புற புகைப்பட நூலக அமைப்புகளை மேலாண்மை செய்யவும்", - "library_tasks_description": "புதிய மற்றும்/அல்லது மாற்றப்பட்ட சொத்துக்களுக்கு வெளிப்புற நூலகங்களை ஸ்கேன் செய்யவும்.", + "library_tasks_description": "புதிய மற்றும்/அல்லது மாற்றப்பட்ட சொத்துக்களுக்கு வெளிப்புற நூலகங்களை வருடவும்", "library_watching_enable_description": "கோப்பு மாற்றங்களுக்கு வெளிப்புற நூலகங்களைப் பாருங்கள்", - "library_watching_settings": "நூலகப் பார்ப்பது (சோதனை)", + "library_watching_settings": "நூலகப் பார்ப்பது [சோதனை]", "library_watching_settings_description": "மாற்றப்பட்ட புகைப்படங்களைத் தானாகவே பார்க்கவும்", "logging_enable_description": "பதிவு செய்வதை இயக்கு", "logging_level_description": "இயக்கப்பட்டால், எந்தப் பதிவு நிலை பயன்படுத்த வேண்டும்.", @@ -152,6 +154,18 @@ "machine_learning_min_detection_score_description": "ஒரு முகம் 0-1 முதல் கண்டறியப்படுவதற்கு குறைந்தபட்ச நம்பிக்கை மதிப்பெண். குறைந்த மதிப்புகள் அதிக முகங்களைக் கண்டறியும், ஆனால் தவறான நேர்மறைகளை ஏற்படுத்தக்கூடும்.", "machine_learning_min_recognized_faces": "குறைந்தபட்ச அங்கீகரிக்கப்பட்ட முகங்கள்", "machine_learning_min_recognized_faces_description": "ஒரு நபருக்கு உருவாக்கப்பட வேண்டிய அங்கீகரிக்கப்பட்ட முகங்களின் குறைந்தபட்ச எண்ணிக்கை. இதை அதிகரிப்பது, ஒரு நபருக்கு முகம் ஒதுக்கப்படாமல் போகும் வாய்ப்பை அதிகரிக்கும் செலவில், முக அங்கீகாரத்தை மிகவும் துல்லியமாக்குகிறது.", + "machine_learning_ocr": "ஓசிஆர்", + "machine_learning_ocr_description": "படங்களில் உள்ள உரையை அடையாளம் காண இயந்திர கற்றலைப் பயன்படுத்தவும்", + "machine_learning_ocr_enabled": "ஓசிஆர் ஐ இயலச்செய்", + "machine_learning_ocr_enabled_description": "முடக்கப்பட்டால், படங்கள் உரை அடையாளம் காணப்படாது.", + "machine_learning_ocr_max_resolution": "அதிகபட்ச தெளிவுத்திறன்", + "machine_learning_ocr_max_resolution_description": "இந்தத் தெளிவுத்திறனுக்கு மேலே உள்ள மாதிரிக்காட்சிகள், தோற்ற விகிதத்தைப் பாதுகாக்கும் அதே வேளையில், அளவு மாற்றப்படும். அதிக மதிப்புகள் மிகவும் துல்லியமானவை, ஆனால் செயலாக்கவும் அதிக நினைவகத்தைப் பயன்படுத்தவும் அதிக நேரம் எடுக்கும்.", + "machine_learning_ocr_min_detection_score": "குறைந்தபட்ச கண்டறிதல் மதிப்பெண்", + "machine_learning_ocr_min_detection_score_description": "உரையைக் கண்டறிய குறைந்தபட்ச நம்பிக்கை மதிப்பெண் 0-1. குறைந்த மதிப்பெண் அதிக உரையைக் கண்டறியும், ஆனால் தவறான தகவல்க்கு வழிவகுக்கும்.", + "machine_learning_ocr_min_recognition_score": "குறைந்தபட்ச அங்கீகார மதிப்பெண்", + "machine_learning_ocr_min_score_recognition_description": "கண்டறியப்பட்ட உரையை அங்கீகரிக்க குறைந்தபட்ச நம்பிக்கை மதிப்பெண் 0-1 ஆகும். குறைந்த மதிப்பெண் அதிக உரையை அங்கீகரிக்கும், ஆனால் தவறான நேர்மறைகளுக்கு வழிவகுக்கும்.", + "machine_learning_ocr_model": "ஓசிஆர் மாதிரி", + "machine_learning_ocr_model_description": "மொபைல் மாடல்களை விட சர்வர் மாடல்கள் மிகவும் துல்லியமானவை, ஆனால் அதிக நினைவகத்தை செயலாக்கி பயன்படுத்த அதிக நேரம் எடுக்கும்.", "machine_learning_settings": "இயந்திர கற்றல் அமைப்புகள்", "machine_learning_settings_description": "இயந்திர கற்றல் அம்சங்கள் மற்றும் அமைப்புகளை நிர்வகிக்கவும்", "machine_learning_smart_search": "ஸ்மார்ட் தேடல்", @@ -209,6 +223,8 @@ "notification_email_ignore_certificate_errors_description": "TLS சான்றிதழ் சரிபார்ப்பு பிழைகளை புறக்கணிக்கவும் (பரிந்துரைக்கப்படவில்லை)", "notification_email_password_description": "மின்னஞ்சல் சேவையகத்துடன் அங்கீகரிக்கும்போது பயன்படுத்த வேண்டிய கடவுச்சொல்", "notification_email_port_description": "மின்னஞ்சல் சேவையகத்தின் போர்ட் (எ.கா. 25, 465, அல்லது 587)", + "notification_email_secure": "எச்எம்டிபிஎச்", + "notification_email_secure_description": "SMTPS (SMTP மூலம் TLS) பயன்படுத்தவும்", "notification_email_sent_test_email_button": "சோதனை மின்னஞ்சலை அனுப்பி சேமிக்கவும்", "notification_email_setting_description": "மின்னஞ்சல் அறிவிப்புகளை அனுப்புவதற்கான அமைப்புகள்", "notification_email_test_email": "சோதனை மின்னஞ்சல் அனுப்பவும்", @@ -241,6 +257,7 @@ "oauth_storage_quota_default_description": "GiB இல் உள்ள ஒதுக்கீடு எந்த உரிமைகோரலும் வழங்கப்படாதபோது பயன்படுத்தப்படும் .", "oauth_timeout": "கோரிக்கை நேரம் முடிந்தது", "oauth_timeout_description": "கோரிக்கைகளுக்கான காலக்கெடு மில்லி வினாடிகளில்", + "ocr_job_description": "படங்களில் உள்ள உரையை அடையாளம் காண இயந்திர கற்றலைப் பயன்படுத்தவும்", "password_enable_description": "மின்னஞ்சல் மற்றும் கடவுச்சொல் மூலம் உள்நுழையவும்", "password_settings": "கடவுச்சொல் உள்நுழைவு", "password_settings_description": "கடவுச்சொல் உள்நுழைவு அமைப்புகளை நிர்வகிக்கவும்", @@ -316,7 +333,7 @@ "transcoding_audio_codec": "ஆடியோ கோடெக்", "transcoding_audio_codec_description": "ஓபச் மிக உயர்ந்த தரமான விருப்பமாகும், ஆனால் பழைய சாதனங்கள் அல்லது மென்பொருளுடன் குறைந்த பொருந்தக்கூடிய தன்மையைக் கொண்டுள்ளது.", "transcoding_bitrate_description": "மேக்ச் பிட்ரேட்டை விட அதிகமான வீடியோக்கள் அல்லது ஏற்றுக்கொள்ளப்பட்ட வடிவத்தில் இல்லை", - "transcoding_codecs_learn_more": "இங்கே பயன்படுத்தப்படும் சொற்களைப் பற்றி மேலும் அறிய, H.264 கோடெக் , HEVC கோடெக் மற்றும் VP9 க்கான FFMPEG ஆவணங்களைப் பார்க்கவும் கோடெக் .", + "transcoding_codecs_learn_more": "இங்கே பயன்படுத்தப்படும் சொற்களைப் பற்றி மேலும் அறிய, H.264 கோடெக், HEVC கோடெக் மற்றும் VP9 க்கான கோடெக் FFMPEG ஆவணங்களைப் பார்க்கவும்.", "transcoding_constant_quality_mode": "நிலையான தர முறை", "transcoding_constant_quality_mode_description": "CQP ஐ விட ICQ சிறந்தது, ஆனால் சில வன்பொருள் முடுக்கம் சாதனங்கள் இந்த பயன்முறையை ஆதரிக்கவில்லை. இந்த விருப்பத்தை அமைப்பது தர அடிப்படையிலான குறியாக்கத்தைப் பயன்படுத்தும் போது குறிப்பிட்ட பயன்முறையை விரும்புகிறது. NVENC ஆல் புறக்கணிக்கப்பட்டது, ஏனெனில் இது ICQ ஐ ஆதரிக்காது.", "transcoding_constant_rate_factor": "நிலையான வீத காரணி (-crf)", @@ -325,13 +342,13 @@ "transcoding_encoding_options": "குறியீட்டு விருப்பங்கள்", "transcoding_encoding_options_description": "குறியிடப்பட்ட வீடியோக்களுக்கான கோடெக்குகள், தெளிவுத்திறன், தரம் மற்றும் பிற விருப்பங்களை அமைக்கவும்", "transcoding_hardware_acceleration": "வன்பொருள் முடுக்கம்", - "transcoding_hardware_acceleration_description": "பரிசோதனை: வேகமான டிரான்ஸ்கோடிங் ஆனால் அதே பிட்ரேட்டில் தரத்தைக் குறைக்கலாம்.", + "transcoding_hardware_acceleration_description": "பரிசோதனை: வேகமான டிரான்ஸ்கோடிங் ஆனால் அதே பிட்ரேட்டில் தரத்தைக் குறைக்கலாம்", "transcoding_hardware_decoding": "வன்பொருள் டிகோடிங்", "transcoding_hardware_decoding_setting_description": "குறியாக்கத்தை விரைவுபடுத்துவதற்கு பதிலாக இறுதி முதல் இறுதி முடுக்கம் ஆகியவற்றை செயல்படுத்துகிறது. எல்லா வீடியோக்களிலும் வேலை செய்யக்கூடாது.", "transcoding_max_b_frames": "அதிகபட்ச பி-பிரேம்கள்", "transcoding_max_b_frames_description": "அதிக மதிப்புகள் சுருக்க செயல்திறனை மேம்படுத்துகின்றன, ஆனால் குறியாக்கத்தை மெதுவாக்குகின்றன. பழைய சாதனங்களில் வன்பொருள் முடுக்கம் உடன் பொருந்தாது. 0 பி -பிரேம்களை முடக்குகிறது, அதே நேரத்தில் -1 இந்த மதிப்பை தானாக அமைக்கிறது.", "transcoding_max_bitrate": "அதிகபட்ச பிட்ரேட்", - "transcoding_max_bitrate_description": "அதிகபட்ச பிட்ரேட்டை அமைப்பது கோப்பு அளவுகளை ஒரு சிறிய செலவில் தரத்திற்கு கணிக்கக்கூடியதாக மாற்றும். 720p இல், வழக்கமான மதிப்புகள் VP9 அல்லது HEVC க்கு 2600 kbit/s அல்லது H.264 க்கு 4500 kbit/s ஆகும். 0 என அமைக்கப்பட்டால் முடக்கப்பட்டது.", + "transcoding_max_bitrate_description": "அதிகபட்ச பிட்ரேட்டை அமைப்பது, தரத்திற்கு குறைந்த செலவில் கோப்பு அளவுகளை மேலும் கணிக்கக்கூடியதாக மாற்றும். 720p இல், வழக்கமான மதிப்புகள் VP9 அல்லது HEVCக்கு 2600 kbit/s அல்லது H.264க்கு 4500 kbit/s ஆகும். 0 என அமைத்தால் முடக்கப்படும். அலகு எதுவும் குறிப்பிடப்படாதபோது, k (kbit/sக்கு) கருதப்படுகிறது; எனவே 5000, 5000k மற்றும் 5M (Mbit/s க்கு) சமமானவை.", "transcoding_max_keyframe_interval": "அதிகபட்ச கீஃப்ரேம் இடைவெளி", "transcoding_max_keyframe_interval_description": "கீஃப்ரேம்களுக்கு இடையில் அதிகபட்ச பிரேம் தூரத்தை அமைக்கிறது. குறைந்த மதிப்புகள் சுருக்க செயல்திறனை மோசமாக்குகின்றன, ஆனால் தேடல் நேரங்களை மேம்படுத்துகின்றன, மேலும் வேகமான இயக்கத்துடன் காட்சிகளில் தரத்தை மேம்படுத்தலாம். 0 இந்த மதிப்பை தானாக அமைக்கிறது.", "transcoding_optimal_description": "இலக்கு தீர்மானத்தை விட உயர்ந்த வீடியோக்கள் அல்லது ஏற்றுக்கொள்ளப்பட்ட வடிவத்தில் இல்லை", @@ -349,7 +366,7 @@ "transcoding_target_resolution": "இலக்கு தீர்மானம்", "transcoding_target_resolution_description": "அதிக தீர்மானங்கள் அதிக விவரங்களை பாதுகாக்க முடியும், ஆனால் குறியாக்க அதிக நேரம் எடுக்கும், பெரிய கோப்பு அளவுகளைக் கொண்டிருக்கலாம், மேலும் பயன்பாட்டு மறுமொழியைக் குறைக்கலாம்.", "transcoding_temporal_aq": "தம்போர்ல்", - "transcoding_temporal_aq_description": "NVENC க்கு மட்டுமே பொருந்தும். உயர்-விவரம், குறைந்த இயக்க காட்சிகளின் தரத்தை அதிகரிக்கிறது. பழைய சாதனங்களுடன் பொருந்தாது.", + "transcoding_temporal_aq_description": "NVENC க்கு மட்டுமே பொருந்தும். டெம்போரல் அடாப்டிவ் குவாண்டேசேசன் உயர் விவரம், குறைந்த இயக்கக் காட்சிகளின் தரத்தை அதிகரிக்கிறது. பழைய சாதனங்களுடன் இணங்காமல் இருக்கலாம்.", "transcoding_threads": "நூல்கள்", "transcoding_threads_description": "அதிக மதிப்புகள் விரைவான குறியாக்கத்திற்கு வழிவகுக்கும், ஆனால் செயலில் இருக்கும்போது மற்ற பணிகளைச் செயலாக்க சேவையகத்திற்கு குறைந்த இடத்தை விட்டு விடுங்கள். இந்த மதிப்பு சிபியு கோர்களின் எண்ணிக்கையை விட அதிகமாக இருக்கக்கூடாது. 0 என அமைக்கப்பட்டால் பயன்பாட்டை அதிகரிக்கிறது.", "transcoding_tone_mapping": "தொனி-மேப்பிங்", @@ -400,11 +417,11 @@ "advanced_settings_prefer_remote_subtitle": "சில சாதனங்கள் உள் சொத்துக்களிலிருந்து சிறுபடங்களை ஏற்றுவதில் மிகவும் மெதுவாக இருக்கும். அதற்கு பதிலாக சர்வர் படங்களை ஏற்ற இந்த அமைப்பைச் செயல்படுத்தவும்.", "advanced_settings_prefer_remote_title": "ரிமோட் படங்களுக்கு முன்னுரிமை கொடு", "advanced_settings_proxy_headers_subtitle": "ஒவ்வொரு நெட்வொர்க் கோரிக்கையுடனும் இம்மிச் அனுப்ப வேண்டிய ப்ராக்ஸி தலைப்புகளை வரையறுக்கவும்", - "advanced_settings_proxy_headers_title": "ப்ராக்ஸி தலைப்புகள்", + "advanced_settings_proxy_headers_title": "தனிப்பயன் பதிலாள் தலைப்புகள் [பரிசோதனை]", "advanced_settings_readonly_mode_subtitle": "புகைப்படங்களை பார்ப்பதற்கு மட்டுமே அனுமதிக்கும் படிப்பதற்கு மட்டும் பயன்முறையை இயக்குகிறது, பல படங்களைத் தேர்ந்தெடுப்பது, பகிர்தல், அனுப்புதல், நீக்குதல் போன்ற அனைத்தும் முடக்கப்பட்டுள்ளன. பிரதான திரையில் இருந்து பயனர் அவதார் வழியாக படிக்க மட்டும் என்பதை இயக்கு/முடக்கு", - "advanced_settings_readonly_mode_title": "படிக்க மட்டுமேயான பயன்முறை", + "advanced_settings_readonly_mode_title": "படிக்க-மட்டும் பயன்முறை", "advanced_settings_self_signed_ssl_subtitle": "சர்வர் எண்ட்பாயிண்டிற்கான SSL சான்றிதழ் சரிபார்ப்பை தவிர்க்கிறது. சுய கையொப்பமிட்ட சான்றிதழ்களுக்கு இது தேவையானது.", - "advanced_settings_self_signed_ssl_title": "சுய கையொப்பமிட்ட SSL சான்றிதழ்களை அனுமதி", + "advanced_settings_self_signed_ssl_title": "தன்வய கையொப்பமிட்ட SSL சான்றிதழ்களை இசைவு [பரிசோதனை]", "advanced_settings_sync_remote_deletions_subtitle": "இணையத்தில் நடவடிக்கை எடுக்கப்படும்போது, இந்தச் சாதனத்தில் உள்ள ஒரு சொத்தை தானாகவே நீக்கவும் அல்லது மீட்டெடுக்கவும்", "advanced_settings_sync_remote_deletions_title": "தொலைநிலை நீக்குதல்களை ஒத்திசைக்கவும் [பரிசோதனைக்கு உட்பட்டது]", "advanced_settings_tile_subtitle": "மேம்பட்ட பயனர் அமைப்புகள்", @@ -464,10 +481,14 @@ "api_key_description": "இந்த மதிப்பு ஒரு முறை மட்டுமே காண்பிக்கப்படும். சாளரத்தை மூடுவதற்கு முன் அதை நகலெடுக்க மறக்காதீர்கள்.", "api_key_empty": "உங்கள் பநிஇ விசை பெயர் காலியாக இருக்கக்கூடாது", "api_keys": "பநிஇ விசைகள்", + "app_architecture_variant": "மாறுபாடு (கட்டிடக்கலை)", "app_bar_signout_dialog_content": "நீங்கள் நிச்சயமாக வெளியேற விரும்புகிறீர்களா?", "app_bar_signout_dialog_ok": "ஆம்", "app_bar_signout_dialog_title": "வெளியேறு", + "app_download_links": "ஆப் பதிவிறக்க இணைப்புகள்", "app_settings": "பயன்பாட்டு அமைப்புகள்", + "app_stores": "ஆப் ச்டோர்கள்", + "app_update_available": "பயன்பாட்டு புதுப்பிப்பு கிடைக்கிறது", "appears_in": "தோன்றும்", "apply_count": "போடு ({count, number})", "archive": "காப்பகம்", @@ -537,9 +558,9 @@ "autoplay_slideshow": "ஆட்டோபிளே ச்லைடுசோ", "back": "பின்", "back_close_deselect": "பின், மூடு அல்லது தேர்வுநீக்கம்", - "background_backup_running_error": "பின்னணி காப்புப்பிரதி தற்போது இயங்குகிறது, கையேடு காப்புப்பிரதியைத் தொடங்க முடியாது", + "background_backup_running_error": "பின்னணி காப்புப்பிரதி தற்போது இயங்குகிறது, கைமுறை காப்புப்பிரதியைத் தொடங்க முடியாது", "background_location_permission": "பின்னணி இருப்பிட இசைவு", - "background_location_permission_content": "பின்னணியில் இயங்கும் போது நெட்வொர்க்குகளை மாற்ற, இம்மிச் * எப்போதும் * துல்லியமான இருப்பிட அணுகலைக் கொண்டிருக்க வேண்டும், எனவே பயன்பாடு வைஃபை நெட்வொர்க்கின் பெயரைப் படிக்க முடியும்", + "background_location_permission_content": "பின்னணியில் இயங்கும் போது நெட்வொர்க்குகளை மாற்ற, இம்மிச் *எப்போதும்* துல்லியமான இருப்பிட அணுகலைக் கொண்டிருக்க வேண்டும், எனவே பயன்பாடு வைஃபை நெட்வொர்க்கின் பெயரைப் படிக்க முடியும்", "background_options": "பின்னணி விருப்பங்கள்", "backup": "காப்புப்பிரதி", "backup_album_selection_page_albums_device": "சாதனத்தில் ஆல்பங்கள் ({count})", @@ -551,6 +572,7 @@ "backup_albums_sync": "காப்புப்பிரதி ஆல்பங்கள் ஒத்திசைவு", "backup_all": "அனைத்தும்", "backup_background_service_backup_failed_message": "சொத்துக்களை காப்புப்பிரதி எடுக்கத் தவறிவிட்டது. மீண்டும் முயற்சிப்பது…", + "backup_background_service_complete_notification": "சொத்து காப்புப்பிரதி முடிந்தது", "backup_background_service_connection_failed_message": "சேவையகத்துடன் இணைக்கத் தவறிவிட்டது. மீண்டும் முயற்சிப்பது…", "backup_background_service_current_upload_notification": "பதிவேற்றுதல் {filename}", "backup_background_service_default_notification": "புதிய சொத்துக்களைச் சரிபார்க்கிறது…", @@ -598,6 +620,7 @@ "backup_controller_page_turn_on": "முன்புற காப்புப்பிரதியை இயக்கவும்", "backup_controller_page_uploading_file_info": "கோப்பு தகவலைப் பதிவேற்றுகிறது", "backup_err_only_album": "ஒரே ஆல்பத்தை அகற்ற முடியாது", + "backup_error_sync_failed": "ஒத்திசைவு தோல்வியுற்றது. காப்புப்பிரதியை செயலாக்க முடியாது.", "backup_info_card_assets": "சொத்துக்கள்", "backup_manual_cancelled": "ரத்து செய்யப்பட்டது", "backup_manual_in_progress": "ஏற்கனவே முன்னேற்றத்தில் பதிவேற்றவும். சிறிது நேரம் கழித்து முயற்சிக்கவும்", @@ -618,9 +641,9 @@ "bugs_and_feature_requests": "பிழைகள் மற்றும் அம்ச கோரிக்கைகள்", "build": "உருவாக்கு", "build_image": "படத்தை உருவாக்குங்கள்", - "bulk_delete_duplicates_confirmation": "{எண்ணிக்கை, பன்மை, ஒன்று {# நகல் சொத்து} பிற {# நகல் சொத்துக்கள்}}}}}}}}} {{# நகல் சொத்து ஆகியவற்றை மொத்தமாக நீக்க விரும்புகிறீர்களா? இது ஒவ்வொரு குழுவின் மிகப்பெரிய சொத்தை வைத்திருக்கும் மற்றும் மற்ற அனைத்து நகல்களையும் நிரந்தரமாக நீக்குகிறது. இந்த செயலை நீங்கள் செயல்தவிர்க்க முடியாது!", - "bulk_keep_duplicates_confirmation": "நீங்கள் {எண்ணிக்கை, பன்மை, ஒன்று {# நகல் சொத்து} பிற {# நகல் சொத்துக்கள்} be வைக்க விரும்புகிறீர்களா? இது எதையும் நீக்காமல் அனைத்து நகல் குழுக்களையும் தீர்க்கும்.", - "bulk_trash_duplicates_confirmation": "நீங்கள் மொத்தமாக குப்பை {எண்ணிக்கை, பன்மை, ஒன்று {# நகல் சொத்து} பிற {# நகல் சொத்துக்கள்}}}} செய்ய விரும்புகிறீர்களா? இது ஒவ்வொரு குழுவின் மிகப்பெரிய சொத்தை வைத்திருக்கும் மற்றும் மற்ற அனைத்து நகல்களையும் குப்பைத் தொட்டியாக இருக்கும்.", + "bulk_delete_duplicates_confirmation": "நீங்கள் நிச்சயமாக {count, plural, one {# நகல் சொத்து} other {# நகல் சொத்துகள்}} மொத்தமாக நீக்க விரும்புகிறீர்களா? இது ஒவ்வொரு குழுவின் மிகப்பெரிய சொத்தை வைத்திருக்கும் மற்றும் மற்ற அனைத்து நகல்களையும் நிரந்தரமாக நீக்கும். இந்தச் செயலை நீங்கள் செயல்தவிர்க்க முடியாது!", + "bulk_keep_duplicates_confirmation": "நீங்கள் நிச்சயமாக {count, plural, one {# நகல் சொத்து} other {# நகல் சொத்துக்கள்}} வைத்திருக்க விரும்புகிறீர்களா? இது எதையும் நீக்காமல் அனைத்து நகல் குழுக்களையும் தீர்க்கும்.", + "bulk_trash_duplicates_confirmation": "நீங்கள் நிச்சயமாக மொத்தமாகக் குப்பையில் போட விரும்புகிறீர்களா {count, plural, one {# நகல் சொத்து} other {# நகல் சொத்துக்கள்}}? இது ஒவ்வொரு குழுவின் மிகப்பெரிய சொத்தையும் வைத்திருக்கும், மற்ற அனைத்து நகல்களையும் குப்பையில் போடும்.", "buy": "இம்மியை வாங்கவும்", "cache_settings_clear_cache_button": "தெளிவான தற்காலிக சேமிப்பு", "cache_settings_clear_cache_button_title": "பயன்பாட்டின் தற்காலிக சேமிப்பை அழிக்கிறது. கேச் மீண்டும் கட்டப்படும் வரை இது பயன்பாட்டின் செயல்திறனை கணிசமாக பாதிக்கும்.", @@ -659,6 +682,8 @@ "change_password_description": "நீங்கள் கணினியில் கையொப்பமிடுவது இதுவே முதல் முறை அல்லது உங்கள் கடவுச்சொல்லை மாற்றுவதற்கான கோரிக்கை செய்யப்பட்டுள்ளது. கீழே புதிய கடவுச்சொல்லை உள்ளிடவும்.", "change_password_form_confirm_password": "கடவுச்சொல்லை உறுதிப்படுத்தவும்", "change_password_form_description": "ஆய் {name}, \n\nநீங்கள் கணினியில் கையொப்பமிடுவது இதுவே முதல் முறை அல்லது உங்கள் கடவுச்சொல்லை மாற்றுவதற்கான கோரிக்கை செய்யப்பட்டுள்ளது. கீழே புதிய கடவுச்சொல்லை உள்ளிடவும்.", + "change_password_form_log_out": "மற்ற எல்லா சாதனங்களிலிருந்தும் வெளியேறு", + "change_password_form_log_out_description": "மற்ற எல்லா சாதனங்களிலிருந்தும் வெளியேற பரிந்துரைக்கப்படுகிறது", "change_password_form_new_password": "புதிய கடவுச்சொல்", "change_password_form_password_mismatch": "கடவுச்சொற்கள் பொருந்தவில்லை", "change_password_form_reenter_new_password": "புதிய கடவுச்சொல்லை மீண்டும் உள்ளிடவும்", @@ -685,8 +710,8 @@ "client_cert_import_success_msg": "கிளையன்ட் சான்றிதழ் இறக்குமதி செய்யப்படுகிறது", "client_cert_invalid_msg": "தவறான சான்றிதழ் கோப்பு அல்லது தவறான கடவுச்சொல்", "client_cert_remove_msg": "கிளையன்ட் சான்றிதழ் அகற்றப்பட்டது", - "client_cert_subtitle": "PKCS12 (.p12, .pfx) வடிவமைப்பை மட்டுமே ஆதரிக்கிறது. சான்றிதழ் இறக்குமதி/அகற்றுதல் உள்நுழைவதற்கு முன்புதான் கிடைக்கும்", - "client_cert_title": "எச்எச்எல் கிளையன்ட் சான்றிதழ்", + "client_cert_subtitle": "PKCS12 (.p12, .pfx) வடிவமைப்பை மட்டுமே ஆதரிக்கிறது. உள்நுழைவதற்கு முன் மட்டுமே சான்றிதழ் இறக்குமதி/அகற்றுதல் கிடைக்கும்", + "client_cert_title": "SSL கிளையன்ட் சான்றிதழ் [பரிசோதனை]", "clockwise": "Locklowsy", "close": "மூடு", "collapse": "சரிவு", @@ -698,7 +723,6 @@ "comments_and_likes": "கருத்துகள் மற்றும் விருப்பங்கள்", "comments_are_disabled": "கருத்துகள் முடக்கப்பட்டுள்ளன", "common_create_new_album": "புதிய ஆல்பத்தை உருவாக்கவும்", - "common_server_error": "தயவுசெய்து உங்கள் பிணைய இணைப்பைச் சரிபார்க்கவும், சேவையகம் அடையக்கூடியது மற்றும் பயன்பாடு/சேவையக பதிப்புகள் இணக்கமானவை என்பதை உறுதிப்படுத்தவும்.", "completed": "முடிந்தது", "confirm": "உறுதிப்படுத்தவும்", "confirm_admin_password": "நிர்வாகி கடவுச்சொல்லை உறுதிப்படுத்தவும்", @@ -707,7 +731,7 @@ "confirm_keep_this_delete_others": "இந்த சொத்தைத் தவிர அடுக்கில் உள்ள மற்ற அனைத்து சொத்துகளும் நீக்கப்படும். நீங்கள் தொடர விரும்புகிறீர்களா?", "confirm_new_pin_code": "புதிய முள் குறியீட்டை உறுதிப்படுத்தவும்", "confirm_password": "கடவுச்சொல்லை உறுதிப்படுத்தவும்", - "confirm_tag_face": "இந்த முகத்தை {பெயர் அச் எனக் குறிக்க விரும்புகிறீர்களா?", + "confirm_tag_face": "இந்த முகத்தை {name} எனக் குறிக்க விரும்புகிறீர்களா?", "confirm_tag_face_unnamed": "இந்த முகத்தை குறிக்க விரும்புகிறீர்களா?", "connected_device": "இணைக்கப்பட்ட சாதனம்", "connected_to": "இணைக்கப்பட்டுள்ளது", @@ -737,6 +761,7 @@ "create": "உருவாக்கு", "create_album": "ஆல்பத்தை உருவாக்கவும்", "create_album_page_untitled": "தலைப்பிடப்படாத", + "create_api_key": "பநிஇ விசையை உருவாக்கவும்", "create_library": "நூலகத்தை உருவாக்கவும்", "create_link": "இணைப்பை உருவாக்கவும்", "create_link_to_share": "பகிர்வுக்கு இணைப்பை உருவாக்கவும்", @@ -766,6 +791,7 @@ "daily_title_text_date_year": "E, mmm dd, yyyy", "dark": "இருண்ட", "dark_theme": "இருண்ட கருப்பொருளை மாற்றவும்", + "date": "தேதி", "date_after": "தேதி", "date_and_time": "தேதி மற்றும் நேரம்", "date_before": "முன் தேதி", @@ -880,7 +906,6 @@ "edit_tag": "குறிச்சொல்லைத் திருத்து", "edit_title": "தலைப்பைத் திருத்து", "edit_user": "பயனரைத் திருத்து", - "edited": "திருத்தப்பட்டது", "editor": "திருத்தி", "editor_close_without_save_prompt": "மாற்றங்கள் சேமிக்கப்படாது", "editor_close_without_save_title": "மூடு ஆசிரியர்?", @@ -913,9 +938,9 @@ "cannot_navigate_next_asset": "அடுத்த சொத்துக்கு செல்ல முடியாது", "cannot_navigate_previous_asset": "முந்தைய சொத்துக்கு செல்ல முடியாது", "cant_apply_changes": "மாற்றங்களைப் பயன்படுத்த முடியாது", - "cant_change_activity": "{செயல்படுத்த முடியாது, தேர்ந்தெடுக்கவும், உண்மை {disable} பிற {enable}} செயல்பாடு", + "cant_change_activity": "செயல்பாட்டை {enabled, select, true {முடக்க} other {இயக்க}} முடியாது", "cant_change_asset_favorite": "சொத்துக்கு பிடித்ததை மாற்ற முடியாது", - "cant_change_metadata_assets_count": "{எண்ணிக்கை, பன்மை, ஒன்று {# சொத்து} பிற {# சொத்துக்கள்}} இன் மெட்டாடேட்டாவை மாற்ற முடியாது", + "cant_change_metadata_assets_count": "{count, plural, one {# சொத்து} other {# சொத்துக்கள்}}இன் மீள்தரவை மாற்ற முடியாது", "cant_get_faces": "முகங்களைப் பெற முடியாது", "cant_get_number_of_comments": "கருத்துகளின் எண்ணிக்கையைப் பெற முடியாது", "cant_search_people": "மக்களைத் தேட முடியாது", @@ -944,7 +969,7 @@ "failed_to_update_notification_status": "அறிவிப்பு நிலையைப் புதுப்பிக்கத் தவறிவிட்டது", "import_path_already_exists": "இந்த இறக்குமதி பாதை ஏற்கனவே உள்ளது.", "incorrect_email_or_password": "தவறான மின்னஞ்சல் அல்லது கடவுச்சொல்", - "paths_validation_failed": "{பாதைகள், பன்மை, ஒன்று {# பாதை} மற்ற {# பாதைகள்}} தோல்வியுற்ற சரிபார்ப்பு", + "paths_validation_failed": "தோல்வியுற்ற சரிபார்ப்பு {paths, plural, one {# பாதை} other {# பாதைகள்}}", "profile_picture_transparent_pixels": "சுயவிவரப் படங்களுக்கு வெளிப்படையான படப்புள்ளிகள் இருக்க முடியாது. தயவுசெய்து பெரிதாக்கவும்/அல்லது படத்தை நகர்த்தவும்.", "quota_higher_than_disk_size": "வட்டு அளவை விட அதிகமாக ஒதுக்கீட்டை அமைத்துள்ளீர்கள்", "something_went_wrong": "ஏதோ தவறு நடந்தது", @@ -954,16 +979,16 @@ "unable_to_add_exclusion_pattern": "விலக்கு முறையைச் சேர்க்க முடியவில்லை", "unable_to_add_import_path": "இறக்குமதி பாதையைச் சேர்க்க முடியவில்லை", "unable_to_add_partners": "கூட்டாளர்களைச் சேர்க்க முடியவில்லை", - "unable_to_add_remove_archive": "{காப்பகப்படுத்த முடியவில்லை, தேர்ந்தெடுக்கவும், உண்மையாகவும்} பிற {remove asset from}}} காப்பகத்திற்குச் சேர்க்கவும்", - "unable_to_add_remove_favorites": "{பிடித்த, தேர்ந்தெடுக்கவும், உண்மையாகவும்}}} பிடித்தவைகளிலிருந்து சொத்தை அகற்று", - "unable_to_archive_unarchive": "{காப்பகப்படுத்த முடியவில்லை, தேர்ந்தெடுக்க முடியவில்லை, உண்மை {archive} பிற {unarchive}}", + "unable_to_add_remove_archive": "காப்பகத்தில் {archived, select, true {இருந்து சொத்தை அகற்ற} other {சொத்தைச் சேர்க்க}} முடியவில்லை", + "unable_to_add_remove_favorites": "பிடித்தவையில் {favorite, select, true {சொத்தைச் சேர்க்க} other {சொத்தை அகற்ற}} முடியவில்லை", + "unable_to_archive_unarchive": "{archived, select, true {சுருக்க} other {விரிக்க}} முடியவில்லை", "unable_to_change_album_user_role": "ஆல்பத்தின் பயனரின் பாத்திரத்தை மாற்ற முடியவில்லை", "unable_to_change_date": "தேதியை மாற்ற முடியவில்லை", "unable_to_change_description": "விளக்கத்தை மாற்ற முடியவில்லை", "unable_to_change_favorite": "சொத்துக்கு பிடித்ததை மாற்ற முடியவில்லை", "unable_to_change_location": "இருப்பிடத்தை மாற்ற முடியவில்லை", "unable_to_change_password": "கடவுச்சொல்லை மாற்ற முடியவில்லை", - "unable_to_change_visibility": "{எண்ணிக்கை, பன்மை, ஒன்று {# நபர்} பிற {# மக்கள்}} க்கான தெரிவுநிலையை மாற்ற முடியவில்லை", + "unable_to_change_visibility": "{count, plural, one {# நபர்} other {# பேர்}}க்கான தெரிவுநிலையை மாற்ற முடியவில்லை", "unable_to_complete_oauth_login": "OAuth உள்நுழைவை முடிக்க முடியவில்லை", "unable_to_connect": "இணைக்க முடியவில்லை", "unable_to_copy_to_clipboard": "இடைநிலைப்பலகைக்கு நகலெடுக்க முடியாது, நீங்கள் HTTPS மூலம் பக்கத்தை அணுகுகிறீர்கள் என்பதை உறுதிப்படுத்திக் கொள்ளுங்கள்", @@ -993,7 +1018,7 @@ "unable_to_log_out_device": "சாதனத்தை விட்டு வெளியேற முடியவில்லை", "unable_to_login_with_oauth": "OAUTH உடன் உள்நுழைய முடியவில்லை", "unable_to_play_video": "வீடியோவை இயக்க முடியவில்லை", - "unable_to_reassign_assets_existing_person": "சொத்துக்களை {பெயருக்கு மறுசீரமைக்க முடியவில்லை, தேர்ந்தெடுக்கவும், சுழிய {an existing person} பிற {{name}}}", + "unable_to_reassign_assets_existing_person": "{name, select, null {ஏற்கனவே உள்ள ஒருவர்} other {{name}}}க்கு சொத்துக்களை மறுஒதுக்கீடு செய்ய முடியவில்லை", "unable_to_reassign_assets_new_person": "ஒரு புதிய நபருக்கு சொத்துக்களை மறுசீரமைக்க முடியவில்லை", "unable_to_refresh_user": "பயனரைப் புதுப்பிக்க முடியவில்லை", "unable_to_remove_album_users": "ஆல்பத்திலிருந்து பயனர்களை அகற்ற முடியவில்லை", @@ -1036,6 +1061,7 @@ "exif_bottom_sheet_description_error": "விளக்கத்தை புதுப்பிப்பதில் பிழை", "exif_bottom_sheet_details": "விவரங்கள்", "exif_bottom_sheet_location": "இடம்", + "exif_bottom_sheet_no_description": "விளக்கம் இல்லை", "exif_bottom_sheet_people": "மக்கள்", "exif_bottom_sheet_person_add_person": "பெயரைச் சேர்க்கவும்", "exit_slideshow": "ச்லைடுசோவிலிருந்து வெளியேறவும்", @@ -1064,7 +1090,7 @@ "failed_to_load_assets": "சொத்துக்களை ஏற்றுவதில் தோல்வி", "failed_to_load_folder": "கோப்புறையை ஏற்றுவதில் தோல்வி", "favorite": "பிடித்த", - "favorite_action_prompt": "{எண்ணிக்கை the பிடித்தவைகளில் சேர்க்கப்பட்டது", + "favorite_action_prompt": "{count} பிடித்தவைகளில் சேர்க்கப்பட்டது", "favorite_or_unfavorite_photo": "பிடித்த அல்லது சாதகமற்ற புகைப்படம்", "favorites": "பிடித்தவை", "favorites_page_no_favorites": "பிடித்த சொத்துக்கள் எதுவும் கிடைக்கவில்லை", @@ -1074,6 +1100,7 @@ "features_setting_description": "பயன்பாட்டு அம்சங்களை நிர்வகிக்கவும்", "file_name": "கோப்பு பெயர்", "file_name_or_extension": "கோப்பு பெயர் அல்லது நீட்டிப்பு", + "file_size": "கோப்பு அளவு", "filename": "கோப்புப்பெயர்", "filetype": "பைல்டைப்", "filter": "வடிப்பி", @@ -1113,11 +1140,10 @@ "hash_asset": "ஆச் சொத்து", "hashed_assets": "சொத்துக்கள்", "hashing": "ஏசிங்", - "header_settings_add_header_tip": "தலைப்புச் சேர்க்கவும்", + "header_settings_add_header_tip": "தலைப்பைச் சேர்க்கவும்", "header_settings_field_validator_msg": "மதிப்பு காலியாக இருக்க முடியாது", "header_settings_header_name_input": "தலைப்பு பெயர்", "header_settings_header_value_input": "தலைப்பு மதிப்பு", - "headers_settings_tile_subtitle": "ஒவ்வொரு பிணைய கோரிக்கையுடனும் பயன்பாடு அனுப்ப வேண்டிய பதிலாள் தலைப்புகளை வரையறுக்கவும்", "headers_settings_tile_title": "தனிப்பயன் பதிலாள் தலைப்புகள்", "hi_user": "ஆய் {name} ({email})", "hide_all_people": "எல்லா மக்களையும் மறைக்கவும்", @@ -1126,9 +1152,9 @@ "hide_password": "கடவுச்சொல்லை மறைக்கவும்", "hide_person": "நபரை மறைக்க", "hide_unnamed_people": "பெயரிடப்படாதவர்களை மறைக்கவும்", - "home_page_add_to_album_conflicts": "சேர்க்கப்பட்டது {ஆல்பம் {added} சொத்துக்கள் {ஆல்பம். {album} சொத்துக்கள் ஏற்கனவே ஆல்பத்தில் உள்ளன.", + "home_page_add_to_album_conflicts": "{album} ஆல்பத்தில் {added} சொத்துக்கள் சேர்க்கப்பட்டன. {failed} சொத்துக்கள் ஏற்கனவே ஆல்பத்தில் உள்ளன.", "home_page_add_to_album_err_local": "ஆல்பங்களில் உள்ளக சொத்துக்களை இன்னும் சேர்க்க முடியாது, தவிர்க்கவும்", - "home_page_add_to_album_success": "சேர்க்கப்பட்டது {ஆல்பம் {added} சொத்துக்கள் {ஆல்பம்.", + "home_page_add_to_album_success": "{album} ஆல்பத்தில் {added} சொத்துக்கள் சேர்க்கப்பட்டன.", "home_page_album_err_partner": "ஒரு ஆல்பத்திற்கு இன்னும் கூட்டாளர் சொத்துக்களைச் சேர்க்க முடியவில்லை, தவிர்க்கவும்", "home_page_archive_err_local": "உள்ளக சொத்துக்களை இன்னும் காப்பகப்படுத்த முடியாது, தவிர்க்கவும்", "home_page_archive_err_partner": "கூட்டாளர் சொத்துக்களை காப்பகப்படுத்த முடியாது, தவிர்க்கவும்", @@ -1150,16 +1176,16 @@ "ignore_icloud_photos": "ICloud புகைப்படங்களை புறக்கணிக்கவும்", "ignore_icloud_photos_description": "ICloud இல் சேமிக்கப்படும் புகைப்படங்கள் இம்மிச் சேவையகத்தில் பதிவேற்றப்படாது", "image": "படம்", - "image_alt_text_date": "{isvideo, தேர்ந்தெடு, உண்மை {Video} பிற {Image}} {date} இல் எடுக்கப்பட்டது", - "image_alt_text_date_1_person": "{isvideo, தேர்ந்தெடு, உண்மை {Video} பிற {Image}} {{person1} இல் {date}", - "image_alt_text_date_2_people": "{isvideo, தேர்ந்தெடுக்கவும், உண்மை {Video} பிற {Image}} {{person1} மற்றும் {person2} {date} இல் எடுக்கப்பட்டது", - "image_alt_text_date_3_people": "{isvideo, தேர்ந்தெடு, உண்மை {Video} பிற {Image} the {person1}, {person2}, மற்றும் {person3} இல் எடுக்கப்பட்டது {date}", - "image_alt_text_date_4_or_more_people": "{isvideo, தேர்ந்தெடு, உண்மை {Video} பிற {Image} the {person1}, {person2}, மற்றும் {கூடுதல் COUNT, எண்} மற்றவர்கள் {date}", - "image_alt_text_date_place": "{isvideo, தேர்ந்தெடு, உண்மை {Video} பிற {Image}} {city}, {country} இல் எடுக்கப்பட்டது {date}", - "image_alt_text_date_place_1_person": "{isvideo, தேர்ந்தெடு, உண்மை {Video} பிற {Image}} {city}, {country} {person1} உடன் {date}", - "image_alt_text_date_place_2_people": "{isvideo, தேர்ந்தெடு, உண்மை {Video} பிற {Image}} {city}, {country} உடன் {person1} மற்றும் {person2} {date}", - "image_alt_text_date_place_3_people": "{isvideo, தேர்ந்தெடு, உண்மை {Video} பிற {Image}} {city}, {country} {person1}, {person2}, மற்றும் {person3} இல் எடுக்கப்பட்டது {date}", - "image_alt_text_date_place_4_or_more_people": "{isvideo, தேர்ந்தெடு, உண்மை {Video} பிற {Image}} {city}, {country} {person1}, {person2}, மற்றும் {கூடுதல் அக்கவுண்ட், எண்} மற்றவர்கள் {date}", + "image_alt_text_date": "{isVideo, select, true {காணொளி} other {படம்}} {date} அன்று எடுக்கப்பட்டது", + "image_alt_text_date_1_person": "{isVideo, select, true {காணொளி} other {படம்}} {person1} உடன் {date} அன்று எடுக்கப்பட்டது", + "image_alt_text_date_2_people": "{isVideo, select, true {காணொளி} other {படம்}} {person1} மற்றும் {person2} உடன் {date} அன்று எடுக்கப்பட்டது", + "image_alt_text_date_3_people": "{isVideo, select, true {காணொளி} other {படம்}} {person1}, {person2}, மற்றும் {person3} உடன் {date} அன்று எடுக்கப்பட்டது", + "image_alt_text_date_4_or_more_people": "{isVideo, select, true {காணொளி} other {படம்}} {person1}, {person2}, மற்றும் {additionalCount, number} உடன் {date} அன்று எடுக்கப்பட்டது", + "image_alt_text_date_place": "{isVideo, select, true {காணொளி} other {படம்}} {city}, {country} இல் {date} அன்று எடுக்கப்பட்டது", + "image_alt_text_date_place_1_person": "{isVideo, select, true {காணொளி} other {படம்}} {city}, {country} இல் {person1} உடன் {date}அன்று எடுக்கப்பட்டது", + "image_alt_text_date_place_2_people": "{isVideo, select, true {காணொளி} other {படம்}} {city}, {country} இல் {person1} மற்றும் {person2} உடன் {date} அன்று எடுக்கப்பட்டது", + "image_alt_text_date_place_3_people": "{isVideo, select, true {காணொளி} other {படம்}} {city}, {country} இல் {person1}, {person2}, மற்றும் {person3} உடன் {date} அன்று எடுக்கப்பட்டது", + "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {காணொளி} other {படம்}} {city}, {country} இல் {person1}, {person2}, மற்றும் {additionalCount, number} பிறருடன் {date} அன்று எடுக்கப்பட்டது", "image_saved_successfully": "படம் சேமிக்கப்பட்டது", "image_viewer_page_state_provider_download_started": "பதிவிறக்கம் தொடங்கியது", "image_viewer_page_state_provider_download_success": "வெற்றியைப் பதிவிறக்கவும்", @@ -1168,7 +1194,7 @@ "immich_web_interface": "இம்ரிச் வலை இடைமுகம்", "import_from_json": "சாதொபொகு இலிருந்து இறக்குமதி", "import_path": "இறக்குமதி பாதை", - "in_albums": "{எண்ணிக்கையில், பன்மை, ஒன்று {# ஆல்பம்} மற்ற {# ஆல்பங்கள்}}", + "in_albums": "{count, plural, one {# செருகேடு} other {# செருகேடுகள்}} இல்", "in_archive": "காப்பகத்தில்", "include_archived": "காப்பகப்படுத்தப்பட்டவர்", "include_shared_albums": "பகிரப்பட்ட ஆல்பங்களைச் சேர்க்கவும்", @@ -1178,7 +1204,7 @@ "info": "தகவல்", "interval": { "day_at_onepm": "ஒவ்வொரு நாளும் மதியம் 1 மணிக்கு", - "hours": "ஒவ்வொரு {மணிநேரம், பன்மை, ஒரு {hour} மற்ற {{மணிநேரம், எண்} மணிநேரம்}}", + "hours": "ஒவ்வொரு {hours, plural, one {மணி} other {{hours, number} மணிகள்}}", "night_at_midnight": "ஒவ்வொரு இரவும் நள்ளிரவில்", "night_at_twoam": "ஒவ்வொரு இரவும் அதிகாலை 2 மணிக்கு" }, @@ -1190,14 +1216,14 @@ "ios_debug_info_last_sync_at": "கடைசி ஒத்திசைவு {dateTime}", "ios_debug_info_no_processes_queued": "பின்னணி செயல்முறைகள் வரிசையில் இல்லை", "ios_debug_info_no_sync_yet": "பின்னணி ஒத்திசைவு வேலை இன்னும் இயங்கவில்லை", - "ios_debug_info_processes_queued": "{எண்ணிக்கை, பன்மை, ஒன்று {{count} பின்னணி செயல்முறை வரிசையில் வரிசைப்படுத்தப்பட்டது} பிற {{count} பின்னணி செயல்முறைகள் வரிசைப்படுத்தப்பட்டன", + "ios_debug_info_processes_queued": "{count, plural, one {{count} பின்னணி செயல்முறை வரிசைப்படுத்தப்பட்டது} other {{count} பின்னணி செயல்முறைகள் வரிசைப்படுத்தப்பட்டுள்ளன}}", "ios_debug_info_processing_ran_at": "செயலாக்கம் {dateTime}", - "items_count": "{எண்ணிக்கை, பன்மை, ஒன்று {# உருப்படி} பிற {# உருப்படிகள்}}", + "items_count": "{count, plural, one {# உருப்படி} other {# உருப்படிகள்}}", "jobs": "வேலைகள்", "keep": "வைத்திருங்கள்", "keep_all": "அனைத்தையும் வைத்திருங்கள்", "keep_this_delete_others": "இதை வைத்திருங்கள், மற்றவர்களை நீக்கு", - "kept_this_deleted_others": "இந்த சொத்தை வைத்து நீக்கப்பட்டது {எண்ணிக்கை, பன்மை, ஒன்று {# சொத்து} பிற {# சொத்துக்கள்}}", + "kept_this_deleted_others": "இந்தச் சொத்தை வைத்து, {count, plural, one {# சொத்து} other {# சொத்துகள்}} நீக்கப்பட்டது", "keyboard_shortcuts": "விசைப்பலகை குறுக்குவழிகள்", "language": "மொழி", "language_no_results_subtitle": "உங்கள் தேடல் காலத்தை சரிசெய்ய முயற்சிக்கவும்", @@ -1238,6 +1264,7 @@ "local_media_summary": "உள்ளக ஊடக சுருக்கம்", "local_network": "உள்ளக பிணையம்", "local_network_sheet_info": "குறிப்பிட்ட வைஃபை நெட்வொர்க்கைப் பயன்படுத்தும் போது பயன்பாடு இந்த முகவரி மூலம் சேவையகத்துடன் இணைக்கப்படும்", + "location": "இடம்", "location_permission": "இருப்பிட இசைவு", "location_permission_content": "ஆட்டோ-ச்விட்சிங் அம்சத்தைப் பயன்படுத்த, இம்மிக்கு துல்லியமான இருப்பிட இசைவு தேவை, எனவே இது தற்போதைய வைஃபை நெட்வொர்க்கின் பெயரைப் படிக்க முடியும்", "location_picker_choose_on_map": "வரைபடத்தில் தேர்வு செய்யவும்", @@ -1295,7 +1322,7 @@ "manage_your_devices": "உங்கள் உள்நுழைந்த சாதனங்களை நிர்வகிக்கவும்", "manage_your_oauth_connection": "உங்கள் OAuth இணைப்பை நிர்வகிக்கவும்", "map": "வரைபடம்", - "map_assets_in_bounds": "{எண்ணிக்கை, பன்மை, = 0 {No photos in this area} ஒன்று {# புகைப்படம்} மற்ற {# புகைப்படங்கள்}}", + "map_assets_in_bounds": "{count, plural, =0 {இந்தப் பகுதியில் புகைப்படங்கள் எதுவும் இல்லை} one {# புகைப்படம்} other {# புகைப்படங்கள்}}", "map_cannot_get_user_location": "பயனரின் இருப்பிடத்தைப் பெற முடியாது", "map_location_dialog_yes": "ஆம்", "map_location_picker_page_use_location": "இந்த இருப்பிடத்தைப் பயன்படுத்தவும்", @@ -1337,22 +1364,24 @@ "merge_people_limit": "நீங்கள் ஒரு நேரத்தில் 5 முகங்கள் வரை மட்டுமே ஒன்றிணைக்க முடியும்", "merge_people_prompt": "இந்த மக்களை ஒன்றிணைக்க விரும்புகிறீர்களா? இந்த நடவடிக்கை மாற்ற முடியாதது.", "merge_people_successfully": "மக்களை வெற்றிகரமாக ஒன்றிணைக்கவும்", - "merged_people_count": "ஒன்றிணைக்கப்பட்ட {எண்ணிக்கை, பன்மை, ஒன்று {# நபர்} மற்ற {# மக்கள்}}", + "merged_people_count": "{count, plural, one {# நபர்} other {# பேர்}} இணைக்கப்பட்டது", "minimize": "குறைக்கவும்", "minute": "நிமிடங்கள்", "minutes": "நிமிடங்கள்", "missing": "இல்லை", + "mobile_app": "மொபைல் ஆப்", + "mobile_app_download_onboarding_note": "பின்வரும் விருப்பங்களைப் பயன்படுத்தி துணை மொபைல் பயன்பாட்டைப் பதிவிறக்கவும்", "model": "மாதிரியுரு", "month": "மாதம்", "monthly_title_text_date_format": "Mmmm ஒய்", "more": "மேலும்", "move": "நகர்த்தவும்", "move_off_locked_folder": "பூட்டப்பட்ட கோப்புறையிலிருந்து வெளியேறவும்", - "move_to_lock_folder_action_prompt": "{எண்ணிக்கை the பூட்டப்பட்ட கோப்புறையில் சேர்க்கப்பட்டுள்ளது", + "move_to_lock_folder_action_prompt": "பூட்டிய கோப்புறையில் {count} சேர்க்கப்பட்டது", "move_to_locked_folder": "பூட்டப்பட்ட கோப்புறையில் செல்லுங்கள்", "move_to_locked_folder_confirmation": "இந்த புகைப்படங்கள் மற்றும் வீடியோ அனைத்து ஆல்பங்களிலிருந்தும் அகற்றப்படும், மேலும் பூட்டப்பட்ட கோப்புறையிலிருந்து மட்டுமே பார்க்க முடியும்", - "moved_to_archive": "நகர்த்தப்பட்டது", - "moved_to_library": "நகர்த்தப்பட்டது", + "moved_to_archive": "{count, plural, one {# சொத்து} other {# சொத்துக்கள்}} காப்பகத்திற்கு நகர்த்தப்பட்டது", + "moved_to_library": "{count, plural, one {# சொத்து} other {# சொத்துக்கள்}} நூலகத்திற்கு நகர்த்தப்பட்டது", "moved_to_trash": "குப்பைக்கு நகர்த்தப்பட்டது", "multiselect_grid_edit_date_time_err_read_only": "சொத்து (களை) மட்டுமே படித்த தேதியைத் திருத்த முடியாது, தவிர்க்கவும்", "multiselect_grid_edit_gps_err_read_only": "சொத்து (களை) மட்டுமே படிக்க, ச்கிப்பிங் ஆகியவற்றைத் திருத்த முடியாது", @@ -1360,6 +1389,8 @@ "my_albums": "எனது ஆல்பங்கள்", "name": "பெயர்", "name_or_nickname": "பெயர் அல்லது புனைப்பெயர்", + "navigate": "வழிசெலுத்தவும்", + "navigate_to_time": "நேரத்திற்கு செல்லவும்", "network_requirement_photos_upload": "காப்புப்பிரதி புகைப்படங்களுக்கு செல்லுலார் தரவைப் பயன்படுத்தவும்", "network_requirement_videos_upload": "காப்புப்பிரதி வீடியோக்களுக்கு செல்லுலார் தரவைப் பயன்படுத்தவும்", "network_requirements": "பிணைய தேவைகள்", @@ -1369,6 +1400,7 @@ "never": "ஒருபோதும்", "new_album": "புதிய ஆல்பம்", "new_api_key": "புதிய பநிஇ விசை", + "new_date_range": "புதிய தேதி வரம்பு", "new_password": "புதிய கடவுச்சொல்", "new_person": "புதிய நபர்", "new_pin_code": "புதிய முள் குறியீடு", @@ -1419,6 +1451,9 @@ "notifications": "அறிவிப்புகள்", "notifications_setting_description": "அறிவிப்புகளை நிர்வகிக்கவும்", "oauth": "Oauth", + "obtainium_configurator": "ஒப்டெய்னியம் கட்டமைப்பாளர்", + "obtainium_configurator_instructions": "Immich GitHub வெளியீட்டில் இருந்து நேரடியாக ஆண்ட்ராய்டு பயன்பாட்டை நிறுவவும் புதுப்பிக்கவும் Obtainium ஐப் பயன்படுத்தவும். பநிஇ விசையை உருவாக்கி, உங்கள் ஒப்டெய்னியம் உள்ளமைவு இணைப்பை உருவாக்க ஒரு மாறுபாட்டைத் தேர்ந்தெடுக்கவும்", + "ocr": "ஓசிஆர்", "official_immich_resources": "உத்தியோகபூர்வ இம்மா வளங்கள்", "offline": "இணையமில்லாமல்", "offset": "ஈடுசெய்யும்", @@ -1454,14 +1489,14 @@ "partner_can_access": "{partner} அணுகலாம்", "partner_can_access_assets": "காப்பகப்படுத்தப்பட்ட மற்றும் நீக்கப்பட்டவை தவிர உங்கள் புகைப்படங்கள் மற்றும் வீடியோக்கள் அனைத்தும்", "partner_can_access_location": "உங்கள் புகைப்படங்கள் எடுக்கப்பட்ட இடம்", - "partner_list_user_photos": "{பயனரின் புகைப்படங்கள்", + "partner_list_user_photos": "{user} இன் புகைப்படங்கள்", "partner_list_view_all": "அனைத்தையும் காண்க", "partner_page_empty_message": "உங்கள் புகைப்படங்கள் இன்னும் எந்த கூட்டாளருடனும் பகிரப்படவில்லை.", "partner_page_no_more_users": "சேர்க்க இனி பயனர்கள் இல்லை", "partner_page_partner_add_failed": "கூட்டாளரைச் சேர்க்கத் தவறிவிட்டது", "partner_page_select_partner": "கூட்டாளரைத் தேர்ந்தெடுக்கவும்", "partner_page_shared_to_title": "பகிரப்பட்டது", - "partner_page_stop_sharing_content": "{கூட்டாளர் your இனி உங்கள் புகைப்படங்களை அணுக முடியாது.", + "partner_page_stop_sharing_content": "{partner} இனி உங்கள் படங்களை அணுக முடியாது.", "partner_sharing": "கூட்டாளர் பகிர்வு", "partners": "கூட்டாளர்கள்", "password": "கடவுச்சொல்", @@ -1469,9 +1504,9 @@ "password_required": "கடவுச்சொல் தேவை", "password_reset_success": "கடவுச்சொல் மீட்டமை செய்", "past_durations": { - "days": "கடந்த {நாட்கள், பன்மை, ஒரு {day} மற்ற {# நாட்கள்}}", - "hours": "கடந்த {மணிநேரம், பன்மை, ஒரு {hour} மற்ற {# மணிநேரம்}}", - "years": "கடந்த {ஆண்டுகள், பன்மை, ஒன்று {year} மற்ற {# ஆண்டுகள்}}" + "days": "கடந்த {days, plural, one {நாள்} other {# நாட்கள்}}", + "hours": "கடந்த {hours, plural, one {மணிநேரம்} other {# மணிநேரங்கள்}}", + "years": "கடந்த {years, plural, one {ஆண்டு} other {# ஆண்டுகள்}}" }, "path": "பாதை", "pattern": "முறை", @@ -1480,16 +1515,16 @@ "paused": "இடைநிறுத்தப்பட்டது", "pending": "நிலுவையில் உள்ளது", "people": "மக்கள்", - "people_edits_count": "திருத்தப்பட்டது {எண்ணிக்கை, பன்மை, ஒன்று {# நபர்} மற்ற {# மக்கள்}}", + "people_edits_count": "{count, plural, one {# நபர்} other {# பேர்}} திருத்தப்பட்டது", "people_feature_description": "மக்கள் தொகுத்த புகைப்படங்கள் மற்றும் வீடியோக்களை உலாவுதல்", "people_sidebar_description": "பக்கப்பட்டியில் உள்ளவர்களுக்கு ஒரு இணைப்பைக் காண்பி", "permanent_deletion_warning": "நிரந்தர நீக்குதல் எச்சரிக்கை", "permanent_deletion_warning_setting_description": "சொத்துக்களை நிரந்தரமாக நீக்கும்போது ஒரு எச்சரிக்கையைக் காட்டுங்கள்", "permanently_delete": "நிரந்தரமாக நீக்கு", - "permanently_delete_assets_count": "நிரந்தரமாக நீக்கு {எண்ணிக்கை, பன்மை, ஒன்று {asset} மற்ற {assets}}", - "permanently_delete_assets_prompt": "நீங்கள் நிச்சயமாக {எண்ணிக்கை, பன்மை, ஒன்று {இந்த சொத்து?} மற்ற {இந்த # சொத்துக்கள்?", + "permanently_delete_assets_count": "நிரந்தரமாக நீக்கப்பட்டது {count, plural, one {சொத்து} other {சொத்துக்கள்}}", + "permanently_delete_assets_prompt": "{count, plural, one {this asset?} other {these # assets?}} என்பதை நிரந்தரமாக நீக்க விரும்புகிறீர்களா? இது {count, plural, one {it from its} other {them from their}} ஆல்பத்தையும் நீக்கும்.", "permanently_deleted_asset": "நிரந்தரமாக நீக்கப்பட்ட சொத்து", - "permanently_deleted_assets_count": "நிரந்தரமாக நீக்கப்பட்டது {எண்ணிக்கை, பன்மை, ஒன்று {# சொத்து} பிற {# சொத்துக்கள்}}", + "permanently_deleted_assets_count": "நிரந்தரமாக நீக்கப்பட்டது {count, plural, one {# சொத்து} other {# சொத்துக்கள்}}", "permission": "இசைவு", "permission_empty": "உங்கள் இசைவு காலியாக இருக்கக்கூடாது", "permission_onboarding_back": "பின்", @@ -1501,15 +1536,15 @@ "permission_onboarding_permission_limited": "இசைவு லிமிடெட். உங்கள் முழு கேலரி சேகரிப்பையும் நிர்வகிக்கவும் நிர்வகிக்கவும், அமைப்புகளில் புகைப்படம் மற்றும் வீடியோ அனுமதிகளை வழங்கவும்.", "permission_onboarding_request": "உங்கள் புகைப்படங்கள் மற்றும் வீடியோக்களைக் காண இம்மிச்சுக்கு இசைவு தேவை.", "person": "ஆள்", - "person_age_months": "{மாதங்கள், பன்மை, ஒன்று {# மாதம்} மற்ற {# மாதங்கள்}} பழையது", - "person_age_year_months": "1 ஆண்டு, {மாதங்கள், பன்மை, ஒன்று {# மாதம்} மற்ற {# மாதங்கள்}} பழையது", - "person_age_years": "{ஆண்டுகள், பன்மை, பிற {# ஆண்டுகள்}} பழையது", + "person_age_months": "{months, plural, one {# திங்கள்} other {# திங்கள்கள்}} அகவை", + "person_age_year_months": "1 வருடம், {months, plural, one {# திங்கள்} other {# திங்கள்கள்}} அகவை", + "person_age_years": "{years, plural, other {# ஆண்டுகள்}} பழையது", "person_birthdate": "{date} இல் பிறந்தார்", - "person_hidden": "{name} {மறைக்கப்பட்ட, தேர்ந்தெடு, உண்மை {(மறைக்கப்பட்ட)} பிற {}}", + "person_hidden": "{name}{hidden, select, true { (மறைக்கப்பட்ட)} other {}}", "photo_shared_all_users": "உங்கள் புகைப்படங்களை எல்லா பயனர்களுடனும் பகிர்ந்து கொண்டதாகத் தெரிகிறது அல்லது பகிர்வதற்கு உங்களிடம் எந்த பயனரும் இல்லை.", "photos": "புகைப்படங்கள்", "photos_and_videos": "புகைப்படங்கள் & வீடியோக்கள்", - "photos_count": "{எண்ணிக்கை, பன்மை, ஒன்று {{எண்ணிக்கை, எண்} புகைப்படம்} பிற {{எண்ணிக்கை, எண்} புகைப்படங்கள்}}", + "photos_count": "{count, plural, one {{count, number} படம்} other {{count, number} படங்கள்}}", "photos_from_previous_years": "முந்தைய ஆண்டுகளின் புகைப்படங்கள்", "pick_a_location": "ஒரு இடத்தைத் தேர்ந்தெடுங்கள்", "pin_code_changed_successfully": "முள் குறியீட்டை வெற்றிகரமாக மாற்றியது", @@ -1518,35 +1553,35 @@ "pin_verification": "குறியீடு சரிபார்ப்பு", "place": "இடம்", "places": "இடங்கள்", - "places_count": "{எண்ணிக்கை, பன்மை, ஒன்று {{எண்ணிக்கை, எண்} இடம்} பிற {{எண்ணிக்கை, எண்} இடங்கள்}}", + "places_count": "{count, plural, one {{count, number} இடம்} other {{count, number} இடங்கள்}}", "play": "விளையாடுங்கள்", "play_memories": "பிளேமெமரிகள்", "play_motion_photo": "இயக்க புகைப்படத்தை விளையாடுங்கள்", "play_or_pause_video": "வீடியோவை இயக்கவும் அல்லது இடைநிறுத்தவும்", + "play_original_video": "அசல் காணொளியை இயக்கு", + "play_original_video_setting_description": "குறிமாற்றம் செய்யப்பட்ட காணொளிகளைவிட அசல் காணொளிகளின் இயக்கத்தை விரும்புங்கள். அசல் சொத்து இணக்கமாக இல்லாவிட்டால் அது சரியாக இயக்கப்படாமல் போகலாம்.", + "play_transcoded_video": "குறிமாற்றம் செய்யப்பட்ட காணொளியை இயக்கு", "please_auth_to_access": "அணுகலை அங்கீகரிக்கவும்", "port": "துறைமுகம்", "preferences_settings_subtitle": "பயன்பாட்டின் விருப்பங்களை நிர்வகிக்கவும்", "preferences_settings_title": "விருப்பத்தேர்வுகள்", + "preparing": "தயாராகிறது", "preset": "முன்னமைவு", "preview": "முன்னோட்டம்", "previous": "முந்தைய", "previous_memory": "முந்தைய நினைவகம்", - "previous_or_next_day": "நாள் முன்னோக்கி/பின்புறம்", - "previous_or_next_month": "மாதம் முன்னோக்கி/பின்", - "previous_or_next_photo": "புகைப்படம் முன்னோக்கி/பின்", - "previous_or_next_year": "ஆண்டு முன்னோக்கி/பின்புறம்", + "previous_or_next_day": "நாள் முன்னோக்கி/பின்னோக்கி", + "previous_or_next_month": "மாதம் முன்னோக்கி/பின்னோக்கி", + "previous_or_next_photo": "புகைப்படம் முன்னோக்கி/பின்னோக்கி", + "previous_or_next_year": "ஆண்டு முன்னோக்கி/பின்னோக்கி", "primary": "முதன்மை", "privacy": "தனியுரிமை", "profile": "சுயவிவரம்", "profile_drawer_app_logs": "பதிவுகள்", - "profile_drawer_client_out_of_date_major": "மொபைல் பயன்பாடு காலாவதியானது. அண்மைக் கால முக்கிய பதிப்பிற்கு புதுப்பிக்கவும்.", - "profile_drawer_client_out_of_date_minor": "மொபைல் பயன்பாடு காலாவதியானது. அண்மைக் கால சிறிய பதிப்பிற்கு புதுப்பிக்கவும்.", "profile_drawer_client_server_up_to_date": "வாங்கி மற்றும் சேவையகம் புதுப்பித்த நிலையில் உள்ளன", - "profile_drawer_github": "கிரப்", - "profile_drawer_readonly_mode": "படிக்க மட்டும் பயன்முறை இயக்கப்பட்டது. வெளியேற பயனர் அவதார் ஐகானை நீண்ட அழுத்தவும்.", - "profile_drawer_server_out_of_date_major": "சேவையகம் காலாவதியானது. அண்மைக் கால முக்கிய பதிப்பிற்கு புதுப்பிக்கவும்.", - "profile_drawer_server_out_of_date_minor": "சேவையகம் காலாவதியானது. அண்மைக் கால சிறிய பதிப்பிற்கு புதுப்பிக்கவும்.", - "profile_image_of_user": "{பயனரின் சுயவிவரப் படம்", + "profile_drawer_github": "கிட்ஹப்", + "profile_drawer_readonly_mode": "படிக்க மட்டும் பயன்முறை இயக்கப்பட்டது. வெளியேற பயனர் அவதார் ஐகானை நீண்ட நேரம் அழுத்தவும்.", + "profile_image_of_user": "{user} இன் சுயவிவரப் படம்", "profile_picture_set": "சுயவிவரப் பட தொகுப்பு.", "public_album": "பொது ஆல்பம்", "public_share": "பொது பங்கு", @@ -1586,15 +1621,16 @@ "queue_status": "வரிசை {count}/{total}", "rating": "நட்சத்திர மதிப்பீடு", "rating_clear": "தெளிவான மதிப்பீடு", - "rating_count": "{எண்ணிக்கை, பன்மை, ஒன்று {# நட்சத்திரம்} மற்ற {# நட்சத்திரங்கள்}}", + "rating_count": "{count, plural, one {# விண்மீன்} other {# விண்மீன்கள்}}", "rating_description": "செய்தி குழுவில் EXIF மதிப்பீட்டைக் காண்பி", "reaction_options": "எதிர்வினை விருப்பங்கள்", "read_changelog": "சேஞ்ச்லாக் படிக்கவும்", "readonly_mode_disabled": "படிக்க மட்டும் பயன்முறை முடக்கப்பட்டுள்ளது", "readonly_mode_enabled": "படிக்க மட்டும் பயன்முறை இயக்கப்பட்டது", - "reassign": "மீண்டும் இணைக்கவும்", - "reassigned_assets_to_existing_person": "மீண்டும் ஒதுக்கப்பட்ட {எண்ணிக்கை, பன்மை, ஒன்று {# சொத்து} பிற {# சொத்துக்கள்}} பெறுநர் {பெயருக்கு, தேர்ந்தெடுக்கவும், சுழிய {an existing person} பிற {{name}}}", - "reassigned_assets_to_new_person": "மீண்டும் ஒதுக்கப்பட்ட {எண்ணிக்கை, பன்மை, ஒன்று {# சொத்து} பிற {# சொத்துக்கள்}} ஒரு புதிய நபருக்கு", + "ready_for_upload": "பதிவேற்றத் தயார்", + "reassign": "மீண்டும் ஒதுக்கு", + "reassigned_assets_to_existing_person": "மீண்டும் ஒதுக்கப்பட்டது {count, plural, one {# சொத்து} other {# சொத்துக்கள்}} to {name, select, null {ஒரு இருக்கும் நபர்} other {{name}}}", + "reassigned_assets_to_new_person": "புதிய நபருக்கு {count, plural, one {# சொத்து} other {# சொத்துகள்}} மீண்டும் ஒதுக்கப்பட்டது", "reassing_hint": "தேர்ந்தெடுக்கப்பட்ட சொத்துக்களை ஏற்கனவே இருக்கும் நபருக்கு ஒதுக்குங்கள்", "recent": "அண்மைக் கால", "recent-albums": "அண்மைக் கால ஆல்பங்கள்", @@ -1618,15 +1654,15 @@ "remote_assets": "தொலை சொத்துக்கள்", "remote_media_summary": "தொலை ஊடக சுருக்கம்", "remove": "அகற்று", - "remove_assets_album_confirmation": "ஆல்பத்திலிருந்து {எண்ணிக்கை, பன்மை, ஒன்று {# சொத்து} பிற {# சொத்துக்கள்} your ஐ அகற்ற விரும்புகிறீர்களா?", - "remove_assets_shared_link_confirmation": "இந்த பகிரப்பட்ட இணைப்பிலிருந்து {எண்ணிக்கை, பன்மை, ஒன்று {# சொத்து} பிற {# சொத்துக்கள்} your ஐ அகற்ற விரும்புகிறீர்களா?", + "remove_assets_album_confirmation": "செருகேட்டிலிருந்து {count, plural, one {# சொத்து} other {# சொத்துக்கள்}} நிச்சயமாக அகற்ற விரும்புகிறீர்களா?", + "remove_assets_shared_link_confirmation": "இந்தப் பகிரப்பட்ட இணைப்பிலிருந்து {count, plural, one {# சொத்து} other {# சொத்துக்களை}} நிச்சயமாக அகற்ற விரும்புகிறீர்களா?", "remove_assets_title": "சொத்துக்களை அகற்றவா?", "remove_custom_date_range": "தனிப்பயன் தேதி வரம்பை அகற்று", "remove_deleted_assets": "நீக்கப்பட்ட சொத்துக்களை அகற்றவும்", "remove_from_album": "ஆல்பத்திலிருந்து அகற்று", - "remove_from_album_action_prompt": "{எண்ணிக்கை the ஆல்பத்திலிருந்து அகற்றப்பட்டது", + "remove_from_album_action_prompt": "ஆல்பத்திலிருந்து {count} அகற்றப்பட்டது", "remove_from_favorites": "பிடித்தவைகளிலிருந்து அகற்று", - "remove_from_lock_folder_action_prompt": "{எண்ணிக்கை the பூட்டப்பட்ட கோப்புறையிலிருந்து அகற்றப்பட்டது", + "remove_from_lock_folder_action_prompt": "பூட்டிய கோப்புறையிலிருந்து {count} அகற்றப்பட்டது", "remove_from_locked_folder": "பூட்டப்பட்ட கோப்புறையிலிருந்து அகற்று", "remove_from_locked_folder_confirmation": "பூட்டப்பட்ட கோப்புறையிலிருந்து இந்த புகைப்படங்களையும் வீடியோக்களையும் நகர்த்த விரும்புகிறீர்களா? அவை உங்கள் நூலகத்தில் தெரியும்.", "remove_from_shared_link": "பகிரப்பட்ட இணைப்பிலிருந்து அகற்று", @@ -1638,10 +1674,10 @@ "removed_api_key": "அகற்றப்பட்ட பநிஇ விசை: {name}", "removed_from_archive": "காப்பகத்திலிருந்து அகற்றப்பட்டது", "removed_from_favorites": "பிடித்தவைகளிலிருந்து அகற்றப்பட்டது", - "removed_from_favorites_count": "{எண்ணிக்கை, பன்மை, பிற {பிடித்தவைகளிலிருந்து #}} அகற்றப்பட்டது", + "removed_from_favorites_count": "பிடித்தவற்றிலிருந்து {count, plural, other {நீக்கப்பட்டது #}}", "removed_memory": "அகற்றப்பட்ட நினைவகம்", "removed_photo_from_memory": "நினைவிலிருந்து புகைப்படத்தை அகற்றியது", - "removed_tagged_assets": "{எண்ணிக்கை, பன்மை, ஒன்று {# சொத்து} பிற {# சொத்துக்கள்} இருந்து இலிருந்து அகற்றப்பட்ட குறிச்சொல்", + "removed_tagged_assets": "{count, plural, one {# asset} other {# சொத்துக்கள்}} என்பதிலிருந்து குறிச்சொல் அகற்றப்பட்டது", "rename": "மறுபெயரிடுங்கள்", "repair": "பழுது", "repair_no_results_message": "கட்டுப்படுத்தப்படாத மற்றும் காணாமல் போன கோப்புகள் இங்கே காண்பிக்கப்படும்", @@ -1661,15 +1697,16 @@ "reset_sqlite_confirmation": "SQLITE தரவுத்தளத்தை மீட்டமைக்க விரும்புகிறீர்களா? தரவை மீண்டும் ஒத்திசைக்க நீங்கள் வெளியேறி மீண்டும் உள்நுழைய வேண்டும்", "reset_sqlite_success": "SQLITE தரவுத்தளத்தை வெற்றிகரமாக மீட்டமைக்கவும்", "reset_to_default": "இயல்புநிலைக்கு மீட்டமைக்கவும்", + "resolution": "தெளிவுத்திறன்", "resolve_duplicates": "நகல்களைத் தீர்க்கவும்", "resolved_all_duplicates": "அனைத்து நகல்களையும் தீர்க்கும்", "restore": "மீட்டமை", "restore_all": "அனைத்தையும் மீட்டெடுக்கவும்", - "restore_trash_action_prompt": "{எண்ணிக்கை the குப்பைகளிலிருந்து மீட்டெடுக்கப்பட்டது", + "restore_trash_action_prompt": "குப்பையிலிருந்து {count} மீட்டெடுக்கப்பட்டது", "restore_user": "பயனரை மீட்டமைக்கவும்", "restored_asset": "மீட்டெடுக்கப்பட்ட சொத்து", "resume": "மீண்டும் தொடங்குங்கள்", - "resume_paused_jobs": "மீண்டும் தொடங்குங்கள் {எண்ணிக்கை, பன்மை, ஒன்று {# இடைநிறுத்தப்பட்ட வேலை} மற்ற {# இடைநிறுத்தப்பட்ட வேலைகள்}}", + "resume_paused_jobs": "{count, plural, one {# இடைநிறுத்தப்பட்ட வேலை} other {# இடைநிறுத்தப்பட்ட வேலைகள்}} மறுதொடக்கம்", "retry_upload": "பதிவேற்ற முயற்சிக்கவும்", "review_duplicates": "நகல்களை மதிப்பாய்வு செய்யவும்", "review_large_files": "பெரிய கோப்புகளை மதிப்பாய்வு செய்யவும்", @@ -1679,6 +1716,7 @@ "running": "இயங்கும்", "save": "சேமி", "save_to_gallery": "கேலரியில் சேமிக்கவும்", + "saved": "சேமிக்கப்பட்டது", "saved_api_key": "சேமித்த பநிஇ விசை", "saved_profile": "சேமித்த சுயவிவரம்", "saved_settings": "சேமித்த அமைப்புகள்", @@ -1695,6 +1733,9 @@ "search_by_description_example": "சப்பாவில் நடைபயணம்", "search_by_filename": "கோப்பு பெயர் அல்லது நீட்டிப்பு மூலம் தேடுங்கள்", "search_by_filename_example": "I.E. IMG_1234.JPG அல்லது PNG", + "search_by_ocr": "ஓசிஆர் மூலம் தேடு", + "search_by_ocr_example": "லேட்", + "search_camera_lens_model": "கண்ணாடி வில்லை மாதிரியைத் தேடு...", "search_camera_make": "தேடல் கேமரா செய்யுங்கள் ...", "search_camera_model": "கேமரா மாதிரியைத் தேடுங்கள் ...", "search_city": "தேடல் நகரம் ...", @@ -1711,6 +1752,7 @@ "search_filter_location_title": "இருப்பிடத்தைத் தேர்ந்தெடுக்கவும்", "search_filter_media_type": "ஊடக வகை", "search_filter_media_type_title": "மீடியா வகையைத் தேர்ந்தெடுக்கவும்", + "search_filter_ocr": "ஓசிஆர் மூலம் தேடு", "search_filter_people_title": "மக்களைத் தேர்ந்தெடுக்கவும்", "search_for": "தேடுங்கள்", "search_for_existing_person": "இருக்கும் நபரைத் தேடுங்கள்", @@ -1749,7 +1791,7 @@ "select_album_cover": "ஆல்பம் அட்டையைத் தேர்ந்தெடுக்கவும்", "select_all": "அனைத்தையும் தெரிவுசெய்", "select_all_duplicates": "அனைத்து நகல்களையும் தேர்ந்தெடுக்கவும்", - "select_all_in": "{குழுவில் அனைத்தையும் தேர்ந்தெடுக்கவும்", + "select_all_in": "{group} இல் உள்ள அனைத்தையும் தேர்ந்தெடுக்கவும்", "select_avatar_color": "அவதார் நிறத்தைத் தேர்ந்தெடுக்கவும்", "select_face": "முகத்தைத் தேர்ந்தெடுக்கவும்", "select_featured_photo": "பிரத்யேக புகைப்படத்தைத் தேர்ந்தெடுக்கவும்", @@ -1762,7 +1804,7 @@ "select_trash_all": "குப்பைத் தொட்டியைத் தேர்ந்தெடுக்கவும்", "select_user_for_sharing_page_err_album": "ஆல்பத்தை உருவாக்கத் தவறிவிட்டது", "selected": "தேர்ந்தெடுக்கப்பட்டது", - "selected_count": "{எண்ணிக்கை, பன்மை, பிற {# தேர்ந்தெடுக்கப்பட்ட}}", + "selected_count": "{count, plural, other {# தேர்ந்தெடுக்கப்பட்டன}}", "selected_gps_coordinates": "தேர்ந்தெடுக்கப்பட்ட சி.பி.எச் ஆயத்தொலைவுகள்", "send_message": "செய்தி அனுப்பவும்", "send_welcome_email": "வரவேற்பு மின்னஞ்சலை அனுப்பவும்", @@ -1773,6 +1815,7 @@ "server_online": "ஆன்லைனில் சேவையகம்", "server_privacy": "சேவையக தனியுரிமை", "server_stats": "சேவையக புள்ளிவிவரங்கள்", + "server_update_available": "சேவையக புதுப்பிப்பு கிடைக்கிறது", "server_version": "சேவையக பதிப்பு", "set": "கணம்", "set_as_album_cover": "ஆல்பம் அட்டையாக அமைக்கவும்", @@ -1801,6 +1844,8 @@ "setting_notifications_subtitle": "உங்கள் அறிவிப்பு விருப்பங்களை சரிசெய்யவும்", "setting_notifications_total_progress_subtitle": "ஒட்டுமொத்த பதிவேற்ற முன்னேற்றம் (முடிந்தது/மொத்த சொத்துக்கள்)", "setting_notifications_total_progress_title": "பின்னணி காப்புப்பிரதி மொத்த முன்னேற்றத்தைக் காட்டு", + "setting_video_viewer_auto_play_subtitle": "வீடியோக்கள் திறக்கப்பட்டவுடன் தானாகவே இயங்கத் தொடங்கும்", + "setting_video_viewer_auto_play_title": "வீடியோக்களை தானாக இயக்கவும்", "setting_video_viewer_looping_title": "லூப்பிங்", "setting_video_viewer_original_video_subtitle": "சேவையகத்திலிருந்து ஒரு வீடியோவை ச்ட்ரீமிங் செய்யும் போது, ஒரு டிரான்ச்கோடு கிடைக்கும்போது கூட அசலை இயக்கவும். இடையகத்திற்கு வழிவகுக்கும். இந்த அமைப்பைப் பொருட்படுத்தாமல் உள்நாட்டில் கிடைக்கும் வீடியோக்கள் அசல் தரத்தில் இயக்கப்படுகின்றன.", "setting_video_viewer_original_video_title": "அசல் வீடியோவை கட்டாயப்படுத்துங்கள்", @@ -1823,7 +1868,7 @@ "shared_album_section_people_action_remove_user": "ஆல்பத்திலிருந்து பயனரை அகற்று", "shared_album_section_people_title": "மக்கள்", "shared_by": "பகிரப்பட்டது", - "shared_by_user": "{பயனரால் பகிரப்பட்டது", + "shared_by_user": "{user} ஆல் பகிரப்பட்டது", "shared_by_you": "நீங்கள் பகிர்ந்து கொண்டார்", "shared_from_partner": "{partner} இலிருந்து புகைப்படங்கள்", "shared_intent_upload_button_progress_text": "{current} / {total} பதிவேற்றப்பட்டது", @@ -1860,9 +1905,9 @@ "shared_link_password_description": "இந்த பகிரப்பட்ட இணைப்பை அணுக கடவுச்சொல் தேவை", "shared_links": "பகிரப்பட்ட இணைப்புகள்", "shared_links_description": "புகைப்படங்கள் மற்றும் வீடியோக்களை இணைப்புடன் பகிரவும்", - "shared_photos_and_videos_count": "{ASSETCOUNT, பன்மை, பிற {# பகிரப்பட்ட புகைப்படங்கள் மற்றும் வீடியோக்கள்.}}", + "shared_photos_and_videos_count": "{assetCount, plural, other {# பகிரப்பட்ட புகைப்படங்கள் & காணொளிகள்.}}", "shared_with_me": "என்னுடன் பகிரப்பட்டது", - "shared_with_partner": "{கூட்டாளர் with உடன் பகிரப்பட்டது", + "shared_with_partner": "{partner} உடன் பகிரப்பட்டது", "sharing": "பகிர்வு", "sharing_enter_password": "இந்த பக்கத்தைக் காண கடவுச்சொல்லை உள்ளிடவும்.", "sharing_page_album": "பகிரப்பட்ட ஆல்பங்கள்", @@ -1919,7 +1964,7 @@ "stack_duplicates": "அடுக்கு நகல்கள்", "stack_select_one_photo": "அடுக்குக்கு ஒரு முக்கிய புகைப்படத்தைத் தேர்ந்தெடுக்கவும்", "stack_selected_photos": "தேர்ந்தெடுக்கப்பட்ட புகைப்படங்களை அடுக்கி வைக்கவும்", - "stacked_assets_count": "அடுக்கப்பட்ட {எண்ணிக்கை, பன்மை, ஒன்று {# சொத்து} மற்ற {# சொத்துக்கள்}}", + "stacked_assets_count": "அடுக்கப்பட்ட {count, plural, one {# சொத்து} other {# சொத்துக்கள்}}", "stacktrace": "ச்டாக் ட்ரேச்", "start": "தொடங்கு", "start_date": "தொடக்க தேதி", @@ -1929,12 +1974,12 @@ "stop_casting": "வார்ப்பதை நிறுத்துங்கள்", "stop_motion_photo": "இயக்க புகைப்படத்தை நிறுத்து", "stop_photo_sharing": "உங்கள் புகைப்படங்களைப் பகிர்வதை நிறுத்தவா?", - "stop_photo_sharing_description": "{கூட்டாளர் your இனி உங்கள் புகைப்படங்களை அணுக முடியாது.", + "stop_photo_sharing_description": "{partner} இனி உங்கள் படங்களை அணுக முடியாது.", "stop_sharing_photos_with_user": "இந்த பயனருடன் உங்கள் புகைப்படங்களைப் பகிர்வதை நிறுத்துங்கள்", "storage": "சேமிப்பக இடம்", "storage_label": "சேமிப்பக சிட்டை", "storage_quota": "சேமிப்பக ஒதுக்கீடு", - "storage_usage": "{used} பயன்படுத்தப்படுகிறது", + "storage_usage": "{available} இல் {used} பயன்படுத்தப்பட்டது", "submit": "சமர்ப்பிக்கவும்", "success": "செய்", "suggestions": "பரிந்துரைகள்", @@ -1955,10 +2000,10 @@ "tag_assets": "குறிச்சொல் சொத்துக்கள்", "tag_created": "உருவாக்கப்பட்ட குறிச்சொல்: {tag}", "tag_feature_description": "தர்க்கரீதியான குறிச்சொல் தலைப்புகளால் தொகுக்கப்பட்ட புகைப்படங்கள் மற்றும் வீடியோக்களை உலாவுதல்", - "tag_not_found_question": "குறிச்சொல்லைக் கண்டுபிடிக்க முடியவில்லையா? <இணைப்பு> புதிய குறிச்சொல்லை உருவாக்கவும். ", + "tag_not_found_question": "குறிச்சொல்லைக் கண்டுபிடிக்க முடியவில்லையா?புதிய குறிச்சொல்லை உருவாக்கவும்.", "tag_people": "மக்களை குறிக்கவும்", "tag_updated": "புதுப்பிக்கப்பட்ட குறிச்சொல்: {tag}", - "tagged_assets": "குறித்துள்ளார் {எண்ணிக்கை, பன்மை, ஒன்று {# சொத்து} மற்ற {# சொத்துக்கள்}}", + "tagged_assets": "குறியிடப்பட்டது {count, plural, one {# சொத்து} other {# சொத்துக்கள்}}", "tags": "குறிச்சொற்கள்", "tap_to_run_job": "வேலையை இயக்க தட்டவும்", "template": "வார்ப்புரு", @@ -1976,10 +2021,11 @@ "theme_setting_system_primary_color_title": "கணினி நிறத்தைப் பயன்படுத்துங்கள்", "theme_setting_system_theme_switch": "தானியங்கி (கணினி அமைப்பைப் பின்பற்றவும்)", "theme_setting_theme_subtitle": "பயன்பாட்டின் கருப்பொருள் அமைப்பைத் தேர்வுசெய்க", - "theme_setting_three_stage_loading_subtitle": "மூன்று-நிலை ஏற்றுதல் ஏற்றுதல் செயல்திறனை அதிகரிக்கக்கூடும், ஆனால் கணிசமாக அதிக பிணைய சுமையை ஏற்படுத்துகிறது", + "theme_setting_three_stage_loading_subtitle": "மூன்று-நிலை ஏற்றுதல் இயக்கினால் ஏற்றுதல் செயல்திறனை அதிகரிக்கக்கூடும், ஆனால் கணிசமாக மிகை பிணையச் சுமையை ஏற்படுத்துகிறது", "theme_setting_three_stage_loading_title": "மூன்று-நிலை ஏற்றுதலை இயக்கவும்", "they_will_be_merged_together": "அவர்கள் ஒன்றாக இணைக்கப்படுவார்கள்", "third_party_resources": "மூன்றாம் தரப்பு வளங்கள்", + "time": "நேரம்", "time_based_memories": "நேர அடிப்படையிலான நினைவுகள்", "timeline": "காலவரிசை", "timezone": "நேர மண்டலம்", @@ -1997,7 +2043,7 @@ "trash": "குப்பை", "trash_action_prompt": "{count} குப்பைக்கு நகர்த்தப்பட்டது", "trash_all": "அனைத்தையும் குப்பை", - "trash_count": "குப்பை {எண்ணிக்கை, எண்}", + "trash_count": "குப்பை {count, number}", "trash_delete_asset": "குப்பை/சொத்தை நீக்கு", "trash_emptied": "காலியாக குப்பை", "trash_no_results_message": "குப்பைத் தொட்டிகள் மற்றும் வீடியோக்கள் இங்கே காண்பிக்கப்படும்.", @@ -2008,17 +2054,18 @@ "trash_page_restore_all": "அனைத்தையும் மீட்டெடுக்கவும்", "trash_page_select_assets_btn": "சொத்துக்களைத் தேர்ந்தெடுக்கவும்", "trash_page_title": "({count})", - "trashed_items_will_be_permanently_deleted_after": "{நாட்கள், பன்மை, ஒன்று {# நாள்} பிற {# நாட்கள்}} க்குப் பிறகு குப்பைத் தொட்டிகள் நிரந்தரமாக நீக்கப்படும்.", + "trashed_items_will_be_permanently_deleted_after": "குப்பையில் உள்ள உருப்படிகள் {days, plural, one {# நாளுக்கு} other {# நாட்களுக்கு}}பிறகு நிரந்தரமாக நீக்கப்படும்.", "troubleshoot": "சரிசெய்தல்", "type": "வகை", "unable_to_change_pin_code": "முள் குறியீட்டை மாற்ற முடியவில்லை", + "unable_to_check_version": "ஆப்ச் அல்லது சர்வர் பதிப்பைச் சரிபார்க்க முடியவில்லை", "unable_to_setup_pin_code": "முள் குறியீட்டை அமைக்க முடியவில்லை", "unarchive": "அன்கான்", - "unarchive_action_prompt": "{எண்ணிக்கை the காப்பகத்திலிருந்து அகற்றப்பட்டது", - "unarchived_count": "{எண்ணிக்கை, பன்மை, பிற {அல்லாத #}}", + "unarchive_action_prompt": "காப்பகத்திலிருந்து {count} அகற்றப்பட்டது", + "unarchived_count": "{count, plural, other {காப்பகப்படுத்தப்படவில்லை #}}", "undo": "செயல்தவிர்", "unfavorite": "மாறாத", - "unfavorite_action_prompt": "{எண்ணிக்கை the பிடித்தவைகளிலிருந்து அகற்றப்பட்டது", + "unfavorite_action_prompt": "பிடித்தவையிலிருந்து {count} அகற்றப்பட்டது", "unhide_person": "அருவருப்பான நபர்", "unknown": "தெரியவில்லை", "unknown_country": "தெரியாத நாடு", @@ -2037,7 +2084,7 @@ "unselect_all_in": "{group}", "unstack": "அன்-ச்டாக்", "unstack_action_prompt": "{count} தடையின்றி", - "unstacked_assets_count": "அன்-ச்டாக் {எண்ணிக்கை, பன்மை, ஒன்று {# சொத்து} பிற {# சொத்துக்கள்}}", + "unstacked_assets_count": "அடுக்கப்படாத {count, plural, one {# சொத்து} other {# சொத்துக்கள்}}", "untagged": "அவிழ்க்கப்படாதது", "up_next": "அடுத்து", "update_location_action_prompt": "{count} தேர்ந்தெடுக்கப்பட்ட சொத்துக்களின் இருப்பிடத்தைப் புதுப்பிக்கவும்:", @@ -2049,10 +2096,10 @@ "upload_details": "விவரங்களை பதிவேற்றவும்", "upload_dialog_info": "தேர்ந்தெடுக்கப்பட்ட சொத்து (களை) சேவையகத்திற்கு காப்புப் பிரதி எடுக்க விரும்புகிறீர்களா?", "upload_dialog_title": "சொத்தை பதிவேற்றவும்", - "upload_errors": "பதிவேற்றம் {எண்ணிக்கை, பன்மை, ஒன்று {# பிழை} மற்ற {# பிழைகள்}} உடன் முடிக்கப்பட்டது, புதிய பதிவேற்ற சொத்துக்களைக் காண பக்கத்தைப் புதுப்பிக்கவும்.", + "upload_errors": "{count, plural, one {# பிழை} other {# பிழைகள்}}மூலம் பதிவேற்றம் முடிந்தது, புதிய பதிவேற்ற சொத்துகளைப் பார்க்கப் பக்கத்தைப் புதுப்பிக்கவும்.", "upload_finished": "பதிவேற்றம் முடிந்தது", - "upload_progress": "மீதமுள்ள {மீதமுள்ள, எண்} - செயலாக்கப்பட்ட {செயலாக்கப்பட்டது, எண்}/{மொத்தம், எண்}", - "upload_skipped_duplicates": "{எண்ணிக்கை, பன்மை, ஒன்று {# நகல் சொத்து} பிற {# நகல் சொத்துக்கள்}}", + "upload_progress": "மீதமுள்ள {remaining, number} - செயலாக்கப்பட்டது {processed, number}/{total, number}", + "upload_skipped_duplicates": "தவிர்க்கப்பட்டது {count, plural, one {# நகல் சொத்து} other {# நகல் சொத்துக்கள்}}", "upload_status_duplicates": "நகல்கள்", "upload_status_errors": "பிழைகள்", "upload_status_uploaded": "பதிவேற்றப்பட்டது", @@ -2068,19 +2115,19 @@ "user": "பயனர்", "user_has_been_deleted": "இந்தப் பயனர் நீக்கப்பட்டார்.", "user_id": "பயனர் ஐடி", - "user_liked": "{user} விரும்பினார் {வகை, தேர்ந்தெடு, புகைப்படம் {this photo} வீடியோ {this video} சொத்து {this asset} பிற {it}}", + "user_liked": "{user} விரும்பினார் {type, select, photo {இந்தப் புகைப்படம்} video {இந்தக் காணொளி} asset {இந்தச் சொத்து} other {it}}", "user_pin_code_settings": "பின் குறியீடு", "user_pin_code_settings_description": "உங்கள் பின் குறியீட்டை நிர்வகிக்கவும்", "user_privacy": "பயனர் தனியுரிமை", "user_purchase_settings": "வாங்க", "user_purchase_settings_description": "உங்கள் வாங்குதலை நிர்வகிக்கவும்", - "user_role_set": "{user} {பாத்திரமாக அமைக்கவும்", + "user_role_set": "{user}ஐ {role} ஆக அமை", "user_usage_detail": "பயனர் பயன்பாட்டு விவரம்", "user_usage_stats": "கணக்கு பயன்பாட்டு புள்ளிவிவரங்கள்", "user_usage_stats_description": "கணக்கு உபயோகப் புள்ளிவிவரங்களைப் பார்க்க", "username": "பயனர்பெயர்", "users": "பயனர்கள்", - "users_added_to_album_count": "{எண்ணிக்கை, பன்மை, ஒன்று {# பயனர்} மற்ற {# பயனர்கள்}} ஆல்பத்தில் சேர்க்கப்பட்டது", + "users_added_to_album_count": "செருகேட்டில் {count, plural, one {# பயனர்} other {# பயனர்கள்}} சேர்க்கப்பட்டார்", "utilities": "பயன்பாடுகள்", "validate": "சரிபார்க்கவும்", "validate_endpoint_error": "தயவுசெய்து ஒரு செல்லுபடியாகும் URL ஐ உள்ளிடவும்", @@ -2094,7 +2141,7 @@ "video_hover_setting": "ஓவரில் வீடியோ சிறு உருவத்தை இயக்கவும்", "video_hover_setting_description": "மவுச் உருப்படியைக் கொண்டு செல்லும்போது வீடியோ சிறு உருவத்தை இயக்கவும். முடக்கப்பட்டாலும் கூட, பிளே ஐகானுக்கு மேல் சுற்றுவதன் மூலம் பிளேபேக்கைத் தொடங்கலாம்.", "videos": "வீடியோக்கள்", - "videos_count": "{எண்ணிக்கை, பன்மை, ஒன்று {# வீடியோ} மற்ற {# வீடியோக்கள்}}", + "videos_count": "{count, plural, one {# காணொளி} other {# காணொளிகள்}}", "view": "பார்வை", "view_album": "ஆல்பத்தைக் காண்க", "view_all": "அனைத்தையும் காண்க", @@ -2113,7 +2160,7 @@ "viewer_remove_from_stack": "அடுக்கிலிருந்து அகற்று", "viewer_stack_use_as_main_asset": "பிரதான சொத்தாகப் பயன்படுத்தவும்", "viewer_unstack": "அடுக்கை நீக்கு", - "visibility_changed": "{எண்ணிக்கை, பன்மை, ஒன்று {# நபர்} மற்ற {# நபர்கள்} க்கு க்கு தெரிவுநிலை மாற்றப்பட்டது", + "visibility_changed": "{count, plural, one {# நபர்} other {# நபர்கள்}} க்கான தெரிவுநிலை மாற்றப்பட்டது", "waiting": "காத்திருக்கிறது", "warning": "எச்சரிக்கை", "week": "வாரம்", diff --git a/i18n/te.json b/i18n/te.json index 0d5242891e..3cbe6e36bd 100644 --- a/i18n/te.json +++ b/i18n/te.json @@ -573,7 +573,6 @@ "edit_tag": "ట్యాగ్‌ను సవరించు", "edit_title": "శీర్షికను సవరించు", "edit_user": "వినియోగదారుని సవరించు", - "edited": "సవరించబడింది", "editor": "ఎడిటర్", "editor_close_without_save_prompt": "మార్పులు సేవ్ చేయబడవు", "editor_close_without_save_title": "ఎడిటర్‌ను మూసివేయాలా?", diff --git a/i18n/th.json b/i18n/th.json index 809d9e24af..759b09363d 100644 --- a/i18n/th.json +++ b/i18n/th.json @@ -28,6 +28,9 @@ "add_to_album": "เพิ่มไปอัลบั้ม", "add_to_album_bottom_sheet_added": "เพิ่มไปยัง {album}", "add_to_album_bottom_sheet_already_exists": "อยู่ใน {album} อยู่แล้ว", + "add_to_album_bottom_sheet_some_local_assets": "ไฟล์บางส่วนไม่สามารถเพิ่มไปยังอัลบั้มได้", + "add_to_albums": "เพิ่มเข้าในอัลบั้ม", + "add_to_albums_count": "เพิ่มไปยังอัลบั้ม ({count})", "add_to_shared_album": "เพิ่มไปยังอัลบั้มที่แชร์กัน", "add_url": "เพิ่ม URL", "added_to_archive": "เพิ่มไปยังที่จัดเก็บถาวร", @@ -644,7 +647,6 @@ "comments_and_likes": "ความคิดเห็นและการถูกใจ", "comments_are_disabled": "ความคิดเห็นถูกปิดใช้งาน", "common_create_new_album": "สร้างอัลบั้มใหม่", - "common_server_error": "กรุณาตรวจสอบการเชื่อมต่ออินเทอร์เน็ต ให้แน่ใจว่าเซิร์ฟเวอร์สามารถเข้าถึงได้ และเวอร์ชันแอพกับเซิร์ฟเวอร์เข้ากันได้", "completed": "สำเร็จ", "confirm": "ยืนยัน", "confirm_admin_password": "ยืนยันรหัสผ่านผู้ดูแลระบบ", @@ -808,7 +810,6 @@ "edit_tag": "แก้ไขแท็ก", "edit_title": "แก้ไขชื่อ", "edit_user": "แก้ไขผู้ใช้", - "edited": "แก้ไขแล้ว", "editor": "ผู้แก้ไข", "editor_close_without_save_prompt": "การเปลี่ยนแปลงนี้จะไม่ได้รับการบันทึก", "editor_close_without_save_title": "ปิดโปรแกรมแก้ไข?", @@ -1023,7 +1024,7 @@ "haptic_feedback_switch": "เปิดการตอบสนองแบบสัมผัส", "haptic_feedback_title": "การตอบสนองแบบสัมผัส", "has_quota": "เหลือพื้นที่", - "header_settings_add_header_tip": "เพิ่ม Header", + "header_settings_add_header_tip": "เพิ่มส่วนหัว", "header_settings_field_validator_msg": "ค่าต้องไม่ว่างเปล่า", "header_settings_header_name_input": "ชื่อ Header", "header_settings_header_value_input": "ค่า Header", @@ -1381,11 +1382,7 @@ "primary": "หลัก", "privacy": "ความเป็นส่วนตัว", "profile_drawer_app_logs": "การบันทึก", - "profile_drawer_client_out_of_date_major": "แอปพลิเคชันมีอัพเดต โปรดอัปเดตเป็นเวอร์ชันหลักล่าสุด", - "profile_drawer_client_out_of_date_minor": "แอปพลิเคชันมีอัพเดต โปรดอัปเดตเป็นเวอร์ชันรองล่าสุด", "profile_drawer_client_server_up_to_date": "ไคลเอนต์และเซิร์ฟเวอร์เป็นปัจจุบัน", - "profile_drawer_server_out_of_date_major": "เซิร์ฟเวอร์มีอัพเดต โปรดอัปเดตเป็นเวอร์ชันหลักล่าสุด", - "profile_drawer_server_out_of_date_minor": "เซิร์ฟเวอร์มีอัพเดต โปรดอัปเดตเป็นเวอร์ชันรองล่าสุด", "profile_image_of_user": "รูปภาพโปรไฟล์ของ {user}", "profile_picture_set": "ตั้งภาพโปรไฟล์แล้ว", "public_album": "อัลบั้มสาธารณะ", diff --git a/i18n/tr.json b/i18n/tr.json index 835ce754c5..1c1f4c7354 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -11,16 +11,16 @@ "activity_changed": "Etkinlik {enabled, select, true {etkin} other {devre dışı}}", "add": "Ekle", "add_a_description": "Açıklama ekle", - "add_a_location": "Konum ekle", + "add_a_location": "Bir konum ekle", "add_a_name": "İsim ekle", - "add_a_title": "Başlık ekle", + "add_a_title": "Bir başlık ekleyin", "add_birthday": "Doğum günü ekle", "add_endpoint": "Uç nokta ekle", "add_exclusion_pattern": "Hariç tutma deseni ekle", "add_import_path": "İçe aktarma yolu ekle", - "add_location": "Lokasyon ekle", + "add_location": "Konum ekle", "add_more_users": "Daha fazla kullanıcı ekle", - "add_partner": "Partner ekle", + "add_partner": "Ortak ekle", "add_path": "Yol ekle", "add_photos": "Fotoğraf ekle", "add_tag": "Etiket ekle", @@ -28,10 +28,12 @@ "add_to_album": "Albüme ekle", "add_to_album_bottom_sheet_added": "{album} albümüne eklendi", "add_to_album_bottom_sheet_already_exists": "Zaten {album} albümüne ekli", + "add_to_album_bottom_sheet_some_local_assets": "Bazı yerel öğeler albüme eklenemedi", "add_to_album_toggle": "{album} için seçimi değiştir", "add_to_albums": "Albümlere ekle", "add_to_albums_count": "{count} albümlerine ekle", "add_to_shared_album": "Paylaşılan albüme ekle", + "add_upload_to_stack": "Yüklemeyi yığına ekle", "add_url": "URL ekle", "added_to_archive": "Arşive eklendi", "added_to_favorites": "Favorilere eklendi", @@ -61,7 +63,7 @@ "config_set_by_file": "Ayarlar şuanda config dosyası tarafından ayarlanmıştır", "confirm_delete_library": "{library} kütüphanesini silmek istediğinize emin misiniz?", "confirm_delete_library_assets": "Bu kütüphaneyi silmek istediğinize emin misiniz? Bu işlem {count, plural, one {# tane öğeyi} other {all # tane öğeyi}} Immich'den silecek ve bu işlem geri alınamaz. Dosyalar diskte kalacaktır.", - "confirm_email_below": "Onaylamak için aşağıya {email} yazın", + "confirm_email_below": "Onaylamak için aşağıya \"{email}\" yazın", "confirm_reprocess_all_faces": "Tüm yüzleri tekrardan işlemek istediğinize emin misiniz? Bu işlem isimlendirilmiş insanları da silecek.", "confirm_user_password_reset": "{user} adlı kullanıcının şifresini sıfırlamak istediğinize emin misiniz?", "confirm_user_pin_code_reset": "{user} adlı kullanıcının PIN kodunu sıfırlamak istediğinize emin misiniz?", @@ -91,7 +93,7 @@ "image_prefer_wide_gamut_setting_description": "Önizleme görseli için P3 renk paletini tercih et. Bu, geniş renk paletli fotoğraflarda renk canlılığını daha iyi korur, fakat fotoğraflar eski tarayıcılarda ve eski cihazlarda daha farklı görünebilir. sRGB fotoğraflar renk paletini korumak için sRGB olarak tutulur.", "image_preview_description": "Orta boyutlu görüntü, meta verisi çıkarılmış, tekil bir öğe görüntülenirken ve makine öğrenimi için kullanılır", "image_preview_quality_description": "Ön izleme kalitesi 1-100 arasıdır. Yüksek değerler daha iyi kalite sağlar, ancak daha büyük dosyalar üretir ve uygulama yanıt verme hızını düşürebilir. Düşük bir değer belirlemek, makine öğrenimi kalitesini etkileyebilir.", - "image_preview_title": "Ön izleme Ayarları", + "image_preview_title": "Ön İzleme Ayarları", "image_quality": "Kalite", "image_resolution": "Çözünürlük", "image_resolution_description": "Daha yüksek çözünürlükle, daha fazla detayı koruyabilir ancak kodlanması daha uzun sürer, daha büyük dosya boyutlarına sahip olur ve uygulamanın yanıt verme hızını azaltabilir.", @@ -105,10 +107,10 @@ "job_not_concurrency_safe": "Bu işlem eşzamanlama için uygun değil.", "job_settings": "Görev Ayarları", "job_settings_description": "Aynı anda çalışacak görevleri yönet", - "job_status": "Görev Statüleri", + "job_status": "Görev Durumu", "jobs_delayed": "{jobCount, plural, other {# gecikmeli}}", "jobs_failed": "{jobCount, plural, other {# Başarısız}}", - "library_created": "{library} kütüphanesi oluşturuldu", + "library_created": "Oluşturulan kütüphane : {library}", "library_deleted": "Kütüphane silindi", "library_import_path_description": "Belirtilecek klasörü içe aktarın. Bu klasör, alt klasörler dahil olmak üzere, görüntüler ve videolar için taranacaktır.", "library_scanning": "Periyodik Tarama", @@ -118,9 +120,9 @@ "library_settings_description": "Harici kütüphane ayarlarını yönet", "library_tasks_description": "Yeni yada değiştirilmiş öğeler için dış kütüphaneleri tara", "library_watching_enable_description": "Harici kütüphanelerdeki dosya değişikliklerini izle", - "library_watching_settings": "Kütüphane izleme (DENEYSEL)", + "library_watching_settings": "Kütüphane izleme [DENEYSEL]", "library_watching_settings_description": "Değişen dosyalar için otomatik olarak izle", - "logging_enable_description": "Günlüğü aktifleştir", + "logging_enable_description": "Günlüğü etkinleştir", "logging_level_description": "Etkinleştirildiğinde hangi günlük seviyesi kullanılır.", "logging_settings": "Günlük Tutma", "machine_learning_availability_checks": "Kullanılabilirlik kontrolleri", @@ -152,6 +154,16 @@ "machine_learning_min_detection_score_description": "Bir yüzün algılanması için gerekli asgari kararlılık miktarı; 0-1 aralığında bir değerdir. Düşük değerler daha fazla yüz tanır ama hatalı tanıma oranı artar.", "machine_learning_min_recognized_faces": "Minimum tanınan yüzler", "machine_learning_min_recognized_faces_description": "Kişi oluşturulması için gereken minimum yüzler. Bu değeri yükseltmek yüz tanıma doğruluğunu arttırır fakat yüzün bir kişiye atanmama olasılığını arttırır.", + "machine_learning_ocr": "OCR", + "machine_learning_ocr_description": "İmajladaki metinleri tanımak için makine öğrenmesi kullan", + "machine_learning_ocr_enabled": "OCR'ı etkileştir", + "machine_learning_ocr_enabled_description": "Devre dışı bırakılırsa, resimler metin tanıma işleminden geçmeyecektir.", + "machine_learning_ocr_max_resolution": "En yüksek çözünürlük", + "machine_learning_ocr_max_resolution_description": "Bu çözünürlüğün üzerindeki önizlemeler, en-boy oranı korunarak yeniden boyutlandırılacaktır. Daha yüksek değerler daha doğru sonuç verir, ancak işlemesi daha uzun sürer ve daha fazla bellek kullanır.", + "machine_learning_ocr_min_detection_score": "En düşük tespit puanı", + "machine_learning_ocr_min_detection_score_description": "Metnin tespit edilmesi için minimum güven puanı 0-1 arasındadır. Düşük değerler daha fazla metin tespit eder, ancak yanlış pozitif sonuçlara yol açabilir.", + "machine_learning_ocr_min_recognition_score": "Minimum tespit puanı", + "machine_learning_ocr_model": "OCR modeli", "machine_learning_settings": "Makine Öğrenmesi ayarları", "machine_learning_settings_description": "Makine öğrenmesi özelliklerini ve ayarlarını yönet", "machine_learning_smart_search": "Akıllı Arama", @@ -209,6 +221,8 @@ "notification_email_ignore_certificate_errors_description": "TLS sertifika doğrulama ayarlarını görmezden gel (Önerilmez)", "notification_email_password_description": "E-posta sunucusunda kimlik doğrulama yaparken kullanılacak şifre", "notification_email_port_description": "Email sunucusunun port numarası (25, 465, 587 gibi)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "SMTPS kullan (TLS üzerinden SMTP)", "notification_email_sent_test_email_button": "Test emaili yolla ve kaydet", "notification_email_setting_description": "Email yollama bildirim ayarları", "notification_email_test_email": "Test emaili yolla", @@ -331,7 +345,7 @@ "transcoding_max_b_frames": "Maksimum B-kareler", "transcoding_max_b_frames_description": "Daha yüksek değerler sıkıştırma verimliliğini artırır, ancak kodlamayı yavaşlatır. Eski cihazlarda donanım hızlandırma ile uyumlu olmayabilir. 0, B-çerçevelerini devre dışı bırakır, -1 ise bu değeri otomatik olarak ayarlar.", "transcoding_max_bitrate": "Maksimum bitrate", - "transcoding_max_bitrate_description": "Maksimum bit hızı ayarlamak, kaliteyi az bir maliyetle düşürerek dosya boyutlarını daha öngörülebilir hale getirebilir. 720p çözünürlükte, tipik değerler VP9 veya HEVC için 2600 kbit/s, H.264 için ise 4500 kbit/s’dir. 0 olarak ayarlanırsa devre dışı bırakılır.", + "transcoding_max_bitrate_description": "Maksimum bit hızı ayarlamak, kaliteyi az bir maliyetle düşürerek dosya boyutlarını daha öngörülebilir hale getirebilir. 720p çözünürlükte, tipik değerler VP9 veya HEVC için 2600 kbit/s, H.264 için ise 4500 kbit/s’dir. 0 olarak ayarlanırsa devre dışı bırakılır. Birim belirtilmediğinde, k (kbit/s için) varsayılır; bu nedenle 5000, 5000k ve 5M (Mbit/s için) eşdeğerdir.", "transcoding_max_keyframe_interval": "Maksimum ana kare aralığı", "transcoding_max_keyframe_interval_description": "Ana kareler arasındaki maksimum kare mesafesini ayarlar. Düşük değerler sıkıştırma verimliliğini kötüleştirir, ancak arama sürelerini iyileştirir ve hızlı hareket içeren sahnelerde kaliteyi artırabilir. 0 bu değeri otomatik olarak ayarlar.", "transcoding_optimal_description": "Hedef çözünürlükten yüksek veya kabul edilen formatta olmayan videolar", @@ -349,7 +363,7 @@ "transcoding_target_resolution": "Hedef çözünürlük", "transcoding_target_resolution_description": "Daha yüksek çözünürlükler daha fazla detayı koruyabilir fakat işlemesi daha uzun sürer, dosya boyutu daha yüksek olur ve uygulamanın akıcılığını etkileyebilir.", "transcoding_temporal_aq": "Zamansal AQ", - "transcoding_temporal_aq_description": "Sadece NVENC için geçerlidir. Yüksek-detayların ve düşük-hareket sahnelerin kalitesini arttır. Eski cihazlarla uyumlu olmayabilir.", + "transcoding_temporal_aq_description": "Sadece NVENC için geçerlidir. Ge.ici Uyarlamalı Kuantizasyon yüksek-detayların ve düşük-hareket sahnelerin kalitesini arttır. Eski cihazlarla uyumlu olmayabilir.", "transcoding_threads": "İş Parçacıkları", "transcoding_threads_description": "Daha yüksek değerler daha hızlı kodlamaya yol açar, ancak sunucunun etkin durumdayken diğer görevleri işlemesi için daha az alan bırakır. Bu değer İşlemci çekirdeği sayısından fazla olmamalıdır. 0'a ayarlanırsa kullanımı en üst düzeye çıkarır.", "transcoding_tone_mapping": "Ton-haritalama", @@ -390,7 +404,7 @@ "video_conversion_job": "Videoları dönüştür", "video_conversion_job_description": "Tarayıcılar ve cihazlarla daha geniş uyumluluk için videoları dönüştür" }, - "admin_email": "Yönetici Emaili", + "admin_email": "Yönetici E-postası", "admin_password": "Yönetici Şifresi", "administration": "Yönetim", "advanced": "Gelişmiş", @@ -400,11 +414,11 @@ "advanced_settings_prefer_remote_subtitle": "Bazı cihazlar yerel öğelerden küçük resimleri yüklerken çok yavaş çalışır. Bunun yerine uzak görüntüleri yüklemek için bu ayarı etkinleştirin.", "advanced_settings_prefer_remote_title": "Uzak görüntüleri tercih et", "advanced_settings_proxy_headers_subtitle": "Immich'in her ağ isteğiyle birlikte göndermesi gereken proxy header'ları tanımlayın", - "advanced_settings_proxy_headers_title": "Proxy Header'lar", + "advanced_settings_proxy_headers_title": "Özel proxy başlıkları [DENEYSEL]", "advanced_settings_readonly_mode_subtitle": "Fotoğrafların yalnızca görüntülenebildiği salt okunur modu etkinleştirir; birden fazla görüntü seçme, paylaşma, aktarma, silme gibi işlemler devre dışı bırakılır. Ana ekrandan kullanıcı avatarı aracılığıyla salt okunur modu Etkinleştirin/Devre dışı bırakın", - "advanced_settings_readonly_mode_title": "Salt okunur Mod", + "advanced_settings_readonly_mode_title": "Salt okunur mod", "advanced_settings_self_signed_ssl_subtitle": "Sunucu uç noktası için SSL sertifika doğrulamasını atlar. Kendinden imzalı sertifikalar için gereklidir.", - "advanced_settings_self_signed_ssl_title": "Kendi kendine imzalanmış SSL sertifikalarına izin ver", + "advanced_settings_self_signed_ssl_title": "Kendinden imzalı SSL sertifikalarına izin ver [DENEYSEL]", "advanced_settings_sync_remote_deletions_subtitle": "Web üzerinde işlem yapıldığında, bu aygıttaki öğeyi otomatik olarak sil veya geri yükle", "advanced_settings_sync_remote_deletions_title": "Uzaktan silmeleri eşzamanla [DENEYSEL]", "advanced_settings_tile_subtitle": "Gelişmiş kullanıcı ayarları", @@ -414,15 +428,15 @@ "age_year_months": "1 yaş, {months, plural, one {# ay} other {# ay}}", "age_years": "{years, plural, other {Yaş #}}", "album_added": "Albüm eklendi", - "album_added_notification_setting_description": "Paylaşılan bir albüme eklendiğinizde email bildirimi alın", - "album_cover_updated": "Albüm Kapağı güncellendi", + "album_added_notification_setting_description": "Paylaşılan bir albüme eklendiğinizde e-posta bildirimi alın", + "album_cover_updated": "Albüm kapağı güncellendi", "album_delete_confirmation": "{album} albümünü silmek istediğinize emin misiniz?", "album_delete_confirmation_description": "Albüm paylaşılıyorsa, diğer kullanıcılar artık bu albüme erişemeyecektir.", "album_deleted": "Albüm silindi", "album_info_card_backup_album_excluded": "HARİÇ", "album_info_card_backup_album_included": "DAHİL", "album_info_updated": "Albüm bilgisi güncellendi", - "album_leave": "Albümden Ayrıl?", + "album_leave": "Albümden ayrıl?", "album_leave_confirmation": "{album} albümünden ayrılmak istediğinize emin misiniz?", "album_name": "Albüm Adı", "album_options": "Albüm seçenekleri", @@ -464,10 +478,14 @@ "api_key_description": "Bu değer sadece bir kere gösterilecek. Lütfen bu pencereyi kapatmadan önce kopyaladığınıza emin olun.", "api_key_empty": "Apı Anahtarı isminiz boş olmamalı", "api_keys": "API Anahtarları", + "app_architecture_variant": "Varyant (Mimari)", "app_bar_signout_dialog_content": "Çıkış yapmak istediğinize emin misiniz?", "app_bar_signout_dialog_ok": "Evet", "app_bar_signout_dialog_title": "Çıkış", + "app_download_links": "Uygulama İndirme Linkleri", "app_settings": "Uygulama Ayarları", + "app_stores": "Uygulama Mağazaları", + "app_update_available": "Uygulama güncellemesi mevcut", "appears_in": "Şurada görünür", "apply_count": "Uygula ({count, number})", "archive": "Arşiv", @@ -551,6 +569,7 @@ "backup_albums_sync": "Yedekleme albümlerinin senkronizasyonu", "backup_all": "Tümü", "backup_background_service_backup_failed_message": "Yedekleme başarısız. Tekrar deneniyor…", + "backup_background_service_complete_notification": "Öğe yedekleme tamamlandı", "backup_background_service_connection_failed_message": "Sunucuya bağlanılamadı. Tekrar deneniyor…", "backup_background_service_current_upload_notification": "{filename} yükleniyor", "backup_background_service_default_notification": "Yeni öğeler kontrol ediliyor…", @@ -574,16 +593,16 @@ "backup_controller_page_background_turn_off": "Arka plan hizmetini kapat", "backup_controller_page_background_turn_on": "Arka plan hizmetini aç", "backup_controller_page_background_wifi": "Sadece Wi-Fi", - "backup_controller_page_backup": "Yedekle", + "backup_controller_page_backup": "Yedek", "backup_controller_page_backup_selected": "Seçilen: ", - "backup_controller_page_backup_sub": "Yedeklenen öğeler", + "backup_controller_page_backup_sub": "Yedeklenen fotoğraflar ve videolar", "backup_controller_page_created": "Oluşturma tarihi: {date}", "backup_controller_page_desc_backup": "Uygulamayı açtığınızda yeni öğelerin sunucuya otomatik olarak yüklenmesi için ön planda yedeklemeyi açın.", "backup_controller_page_excluded": "Hariç tutuldu: ", "backup_controller_page_failed": "Başarısız ({count})", "backup_controller_page_filename": "Dosya adı: {filename} [{size}]", "backup_controller_page_id": "KNu: {id}", - "backup_controller_page_info": "Yedekleme bilgileri", + "backup_controller_page_info": "Yedekleme Bilgileri", "backup_controller_page_none_selected": "Hiçbiri seçilmedi", "backup_controller_page_remainder": "Kalan", "backup_controller_page_remainder_sub": "Seçili albümlerden yedeklenecek kalan öğeler", @@ -598,6 +617,7 @@ "backup_controller_page_turn_on": "Ön planda yedeklemeyi aç", "backup_controller_page_uploading_file_info": "Dosya bilgisi yükleniyor", "backup_err_only_album": "Tek albüm kaldırılamaz", + "backup_error_sync_failed": "Senkronizasyon başarısız. Yedekleme işlemi gerçekleştirilemiyor.", "backup_info_card_assets": "öğeler", "backup_manual_cancelled": "İptal Edildi", "backup_manual_in_progress": "Yükleme halihazırda devam ediyor. Bir süre sonra deneyin", @@ -614,7 +634,7 @@ "biometric_not_available": "Bu cihazda biyometrik kimlik doğrulama mevcut değil", "birthdate_saved": "Doğum günü başarılı bir şekilde kaydedildi", "birthdate_set_description": "Doğum günü, fotoğraftaki insanın fotoğraf çekildiği zamandaki yaşının hesaplanması için kullanılır.", - "blurred_background": "Bulanık arkaplan", + "blurred_background": "Bulanık arka plan", "bugs_and_feature_requests": "Hatalar ve Özellik Talepleri", "build": "Yapı", "build_image": "Görüntü Oluştur", @@ -676,7 +696,7 @@ "clear": "Temizle", "clear_all": "Hepsini temizle", "clear_all_recent_searches": "Son aramaların hepsini temizle", - "clear_file_cache": "Dosya Önbelleği Temizle", + "clear_file_cache": "Dosya Önbelleğini Temizle", "clear_message": "Mesajı temizle", "clear_value": "Değeri temizle", "client_cert_dialog_msg_confirm": "Tamam", @@ -685,8 +705,8 @@ "client_cert_import_success_msg": "İstemci sertifikası içe aktarıldı", "client_cert_invalid_msg": "Geçersiz sertifika dosyası veya yanlış şifre", "client_cert_remove_msg": "İstemci sertifikası kaldırıldı", - "client_cert_subtitle": "Yalnızca PKCS12 (.p12, .pfx) biçimini destekler. Sertifika İçe Aktarma/Kaldırma yalnızca oturum açmadan önce kullanılabilir", - "client_cert_title": "SSL İstemci Sertifikası", + "client_cert_subtitle": "Yalnızca PKCS12 (.p12, .pfx) formatını destekler. Sertifika içe aktarma/kaldırma işlemi yalnızca oturum açmadan önce yapılabilir", + "client_cert_title": "SSL istemci sertifikası [DENEYSEL]", "clockwise": "Saat yönü", "close": "Kapat", "collapse": "Daralt", @@ -698,7 +718,6 @@ "comments_and_likes": "Yorumlar & beğeniler", "comments_are_disabled": "Yorumlar devre dışı", "common_create_new_album": "Yeni Albüm", - "common_server_error": "Lütfen ağ bağlantınızı kontrol edin, sunucunun erişilebilir olduğundan ve uygulama/sunucu sürümlerinin uyumlu olduğundan emin olun.", "completed": "Tamamlandı", "confirm": "Onayla", "confirm_admin_password": "Yönetici Şifresini Onayla", @@ -711,7 +730,7 @@ "confirm_tag_face_unnamed": "Bu yüzü etiketlemek ister misin?", "connected_device": "Cihaz bağlandı", "connected_to": "Bağlı", - "contain": "İçermek", + "contain": "Sığdır", "context": "Bağlam", "continue": "Devam et", "control_bottom_app_bar_create_new_album": "Yeni albüm", @@ -733,10 +752,11 @@ "copy_to_clipboard": "Panoya Kopyala", "country": "Ülke", "cover": "Kapla", - "covers": "Kaplar", + "covers": "Kapak", "create": "Oluştur", "create_album": "Albüm oluştur", "create_album_page_untitled": "Başlıksız", + "create_api_key": "API anahtarı oluştur", "create_library": "Kütüphane Oluştur", "create_link": "Link oluştur", "create_link_to_share": "Paylaşmak için link oluştur", @@ -766,6 +786,7 @@ "daily_title_text_date_year": "dd MMM yyyy E", "dark": "Koyu", "dark_theme": "Karanlık temaya geç", + "date": "Tarih", "date_after": "Sonraki tarih", "date_and_time": "Tarih ve Zaman", "date_before": "Önceki tarih", @@ -880,7 +901,6 @@ "edit_tag": "Etiketi düzenle", "edit_title": "Başlığı düzenle", "edit_user": "Kullanıcıyı düzenle", - "edited": "Düzenlendi", "editor": "Editör", "editor_close_without_save_prompt": "Değişiklikler kaydedilmeyecek", "editor_close_without_save_title": "Düzenleyici kapatılsın mı?", @@ -1036,6 +1056,7 @@ "exif_bottom_sheet_description_error": "Açıklama güncelleme hatası", "exif_bottom_sheet_details": "DETAYLAR", "exif_bottom_sheet_location": "KONUM", + "exif_bottom_sheet_no_description": "Açıklama yok", "exif_bottom_sheet_people": "KİŞİLER", "exif_bottom_sheet_person_add_person": "İsim ekle", "exit_slideshow": "Slayt gösterisinden çık", @@ -1074,6 +1095,7 @@ "features_setting_description": "Uygulamanın özelliklerini yönet", "file_name": "Dosya adı", "file_name_or_extension": "Dosya adı veya uzantı", + "file_size": "Dosya boyutu", "filename": "Dosya adı", "filetype": "Dosya tipi", "filter": "Filtre", @@ -1113,11 +1135,10 @@ "hash_asset": "Karma öğe", "hashed_assets": "Karma öğeler", "hashing": "Hashleme", - "header_settings_add_header_tip": "Header Ekle", + "header_settings_add_header_tip": "Başlık ekle", "header_settings_field_validator_msg": "Değer boş olamaz", "header_settings_header_name_input": "Header adı", "header_settings_header_value_input": "Header değeri", - "headers_settings_tile_subtitle": "Uygulamanın her ağ isteğiyle birlikte göndermesi gereken proxy header'ları tanımlayın", "headers_settings_tile_title": "Özel proxy headers", "hi_user": "Merhaba {name} {email}", "hide_all_people": "Tüm kişileri gizle", @@ -1129,11 +1150,11 @@ "home_page_add_to_album_conflicts": "{album} albümüne {added} öğe eklendi. {failed} öğe zaten albümdeydi.", "home_page_add_to_album_err_local": "Yerel öğeler henüz albümlere eklenemiyor, atlanıyor", "home_page_add_to_album_success": "{album} albümüne {added} öğe eklendi.", - "home_page_album_err_partner": "Partner öğeler henüz bir albüme eklenemiyor, atlanıyor", + "home_page_album_err_partner": "Ortak öğeler henüz bir albüme eklenemiyor, atlanıyor", "home_page_archive_err_local": "Yerel öğeler henüz arşivlenemiyor, atlanıyor", - "home_page_archive_err_partner": "Partner öğeler henüz arşivlenemiyor, atlanıyor", + "home_page_archive_err_partner": "Ortak öğeler henüz arşivlenemiyor, atlanıyor", "home_page_building_timeline": "Zaman çizelgesi oluşturuluyor", - "home_page_delete_err_partner": "Partner öğeler silinemez, atlanıyor", + "home_page_delete_err_partner": "Ortak öğeler silinemez, atlanıyor", "home_page_delete_remote_err_local": "Uzaktan silme seçimindeki yerel öğeler atlanıyor", "home_page_favorite_err_local": "Yerel öğeler henüz favorilere eklenemiyor, atlanıyor", "home_page_favorite_err_partner": "Ortak öğeler henüz favorilere eklenemiyor, atlanıyor", @@ -1236,7 +1257,7 @@ "local_asset_cast_failed": "Sunucuya yüklenmemiş bir öğe yansıtılamaz", "local_assets": "Yerel Öğeler", "local_media_summary": "Yerel Medya Özeti", - "local_network": "Yerel Wi-Fi", + "local_network": "Yerel ağ", "local_network_sheet_info": "Uygulama belirlenmiş Wi-Fi ağını kullanırken bu URL üzerinden sunucuya bağlanacaktır", "location_permission": "Konum izni", "location_permission_content": "Otomatik geçiş özelliğinin çalışabilmesi için Immich'in mevcut Wi-Fi ağının adını bilmesi, bunu sağlamak için de tam konum iznine ihtiyacı vardır", @@ -1313,7 +1334,7 @@ "map_settings_date_range_option_years": "Son {years} yıl", "map_settings_dialog_title": "Harita Ayarları", "map_settings_include_show_archived": "Arşivdekileri dahil et", - "map_settings_include_show_partners": "Partnerleri Dahil Et", + "map_settings_include_show_partners": "Ortakları Dahil Et", "map_settings_only_show_favorites": "Sadece Favorileri Göster", "map_settings_theme_settings": "Harita Teması", "map_zoom_to_see_photos": "Fotoğrafları görmek için uzaklaştırın", @@ -1340,11 +1361,13 @@ "merged_people_count": "{count, plural, one {# kişi} other {# kişi}} birleştirildi", "minimize": "Küçült", "minute": "Dakika", - "minutes": "Dakikalar", + "minutes": "Dakika", "missing": "Eksik", + "mobile_app": "Mobil Uygulama", + "mobile_app_download_onboarding_note": "Aşağıdaki seçenekleri kullanarak eşlik eden mobil uygulamayı indirin", "model": "Model", "month": "Ay", - "monthly_title_text_date_format": "MMMM y", + "monthly_title_text_date_format": "AAAA y", "more": "Daha fazla", "move": "Taşı", "move_off_locked_folder": "Kilitli klasörden taşı", @@ -1360,6 +1383,8 @@ "my_albums": "Albümlerim", "name": "İsim", "name_or_nickname": "İsim veya takma isim", + "navigate": "Gezin", + "navigate_to_time": "Zamana Git", "network_requirement_photos_upload": "Fotoğrafları yedeklemek için mobil veriyi kullan", "network_requirement_videos_upload": "Videoları yedeklemek için mobil veriyi kullan", "network_requirements": "Ağ Gereksinimleri", @@ -1369,6 +1394,7 @@ "never": "Asla", "new_album": "Yeni albüm", "new_api_key": "Yeni API Anahtarı", + "new_date_range": "Yeni tarih aralığı", "new_password": "Yeni şifre", "new_person": "Yeni kişi", "new_pin_code": "Yeni PIN kodu", @@ -1389,14 +1415,14 @@ "no_cast_devices_found": "Yansıtılacak cihaz bulunamadı", "no_checksum_local": "Sağlama toplamı mevcut değil - yerel varlıkları alamıyor", "no_checksum_remote": "Sağlama toplamı mevcut değil - uzak varlık alınamıyor", - "no_duplicates_found": "Çift bulunamadı.", + "no_duplicates_found": "Hiçbir kopya bulunamadı.", "no_exif_info_available": "EXIF bilgisi mevcut değil", "no_explore_results_message": "Koleksiyonunuzu keşfetmek için daha fazla fotoğraf yükleyin.", "no_favorites_message": "En sevdiğiniz fotoğraf ve videoları hızlıca bulmak için favorilere ekleyin", "no_libraries_message": "Fotoğraf ve videolarınızı görmek için bir harici kütüphane oluşturun", "no_local_assets_found": "Bu sağlama toplamı ile yerel varlık bulunamadı", "no_locked_photos_message": "Kilitli klasördeki fotoğraf ve videolar gizlidir; kitaplığınızda gezinirken veya arama yaparken görünmezler.", - "no_name": "İsim yok", + "no_name": "İsim Yok", "no_notifications": "Bildirim yok", "no_people_found": "Eşleşen kişi bulunamadı", "no_places": "Yer yok", @@ -1419,6 +1445,9 @@ "notifications": "Bildirimler", "notifications_setting_description": "Bildirimleri yönetin", "oauth": "OAuth", + "obtainium_configurator": "Obtainium Yapılandırıcı", + "obtainium_configurator_instructions": "Obtainium kullanarak Android uygulamasını doğrudan Immich GitHub sürümünden yükleyin ve güncelleyin. Bir API anahtarı oluşturun ve bir varyant seçerek Obtainium yapılandırma bağlantınızı oluşturun", + "ocr": "OCR", "official_immich_resources": "Resmi Immich Kaynakları", "offline": "Çevrim dışı", "offset": "Ofset", @@ -1456,13 +1485,13 @@ "partner_can_access_location": "Fotoğraf ve videolarınızın çekildiği konum", "partner_list_user_photos": "{user} fotoğrafları", "partner_list_view_all": "Tümünü gör", - "partner_page_empty_message": "Fotoğraflarınız henüz hiçbir partnerle paylaşılmadı.", + "partner_page_empty_message": "Fotoğraflarınız henüz hiçbir ortakla paylaşılmadı.", "partner_page_no_more_users": "Eklenecek başka kullanıcı yok", - "partner_page_partner_add_failed": "Partner eklenemedi", - "partner_page_select_partner": "Partner seç", + "partner_page_partner_add_failed": "Ortak eklenemedi", + "partner_page_select_partner": "Ortak seç", "partner_page_shared_to_title": "Paylaşıldı", "partner_page_stop_sharing_content": "{partner} artık fotoğraflarınıza erişemeyecek.", - "partner_sharing": "Ortak paylaşımı", + "partner_sharing": "Ortak Paylaşımı", "partners": "Ortaklar", "password": "Şifre", "password_does_not_match": "Şifre eşleşmiyor", @@ -1486,10 +1515,10 @@ "permanent_deletion_warning": "Kalıcı silme uyarısı", "permanent_deletion_warning_setting_description": "Nesneleri kalıcı olarak silerken uyarı göster", "permanently_delete": "Kalıcı olarak sil", - "permanently_delete_assets_count": "{count, plural, one {öğe} other {öğeler}} kalıcı olarak silindi", + "permanently_delete_assets_count": "{count, plural, one {öğe} other {öğe}} kalıcı olarak silindi", "permanently_delete_assets_prompt": "Bu {count, plural, one {öğeyi} other {# öğeleri}} kalıcı olarak silmek istediğinizden emin misiniz? Bu işlem {count, plural, one {bu öğeyi} other {bu öğeleri}} albümlerinizden de kaldırır.", "permanently_deleted_asset": "Kalıcı olarak silinmiş öğeler", - "permanently_deleted_assets_count": "{count, plural, one {# öğe} other {# öğeler}} kalıcı olarak silindi", + "permanently_deleted_assets_count": "{count, plural, one {# öğe} other {# öğe}} kalıcı olarak silindi", "permission": "İzin", "permission_empty": "İzniniz boş olmamalı", "permission_onboarding_back": "Geri", @@ -1501,9 +1530,9 @@ "permission_onboarding_permission_limited": "Sınırlı izin. Immich'in tüm fotoğrav ve videolarınızı yedeklemesine ve yönetmesine izin vermek için Ayarlar'da fotoğraf ve video izinlerini verin.", "permission_onboarding_request": "Immich'in fotoğraflarınızı ve videolarınızı görüntüleyebilmesi için izne ihtiyacı var.", "person": "Kişi", - "person_age_months": "{months, plural, one {# month} other {# months}} eski", - "person_age_year_months": "1 yıl, {months, plural, one {# month} other {# months}} eski", - "person_age_years": "{years, plural, other {# sene}} önce", + "person_age_months": "{months, plural, one {# aylık} other {# aylık}}", + "person_age_year_months": "1 yıl, {months, plural, one {# aylık} other {# aylık}}", + "person_age_years": "{years, plural, other {# yaşında}}", "person_birthdate": "{date} tarihinde doğdu", "person_hidden": "{name}{hidden, select, true { (gizli)} other {}}", "photo_shared_all_users": "Fotoğraflarınızı tüm kullanıcılarla paylaştınız gibi görünüyor veya paylaşacak kullanıcı bulunmuyor.", @@ -1521,12 +1550,16 @@ "places_count": "{count, plural, one {{count, number} yer} other {{count, number} yer}}", "play": "Oynat", "play_memories": "Anıları oynat", - "play_motion_photo": "Hareketli fotoğrafı oynat", + "play_motion_photo": "Hareketli Fotoğrafı Oynat", "play_or_pause_video": "Videoyu oynat ya da durdur", + "play_original_video": "Orijinal videoyu oynat", + "play_original_video_setting_description": "Kodlanmış videolar yerine orijinal videoların oynatılmasını tercih edin. Orijinal öğe uyumlu değilse, doğru şekilde oynatılmayabilir.", + "play_transcoded_video": "Kodlanmış videoyu oynat", "please_auth_to_access": "Erişim için lütfen kimliğinizi doğrulayın", "port": "Port", "preferences_settings_subtitle": "Uygulama tercihlerini düzenle", "preferences_settings_title": "Tercihler", + "preparing": "Hazırlanıyor", "preset": "Ön ayar", "preview": "Önizleme", "previous": "Önceki", @@ -1539,13 +1572,9 @@ "privacy": "Gizlilik", "profile": "Profil", "profile_drawer_app_logs": "Günlükler", - "profile_drawer_client_out_of_date_major": "Mobil uygulama güncel değil. Lütfen en son ana sürüme güncelleyin.", - "profile_drawer_client_out_of_date_minor": "Mobil uygulama güncel değil. Lütfen en son sürüme güncelleyin.", "profile_drawer_client_server_up_to_date": "Uygulama ve sunucu güncel", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Salt okunur mod etkinleştirildi. Çıkmak için kullanıcı avatar simgesine uzun basın.", - "profile_drawer_server_out_of_date_major": "Sunucu güncel değil. Lütfen en son ana sürüme güncelleyin.", - "profile_drawer_server_out_of_date_minor": "Sunucu güncel değil. Lütfen en son sürüme güncelleyin.", "profile_image_of_user": "{user} kullanıcısının profil resmi", "profile_picture_set": "Profil resmi ayarlandı.", "public_album": "Herkese açık albüm", @@ -1592,6 +1621,7 @@ "read_changelog": "Değişiklik günlüğünü oku", "readonly_mode_disabled": "Salt okunur mod devre dışı", "readonly_mode_enabled": "Salt okunur mod etkin", + "ready_for_upload": "Yüklemeye hazır", "reassign": "Yeniden ata", "reassigned_assets_to_existing_person": "{count, plural, one {# öğe} other {# öğeler}} {name, select, null {mevcut bir kişiye} other {{name}}} atandı", "reassigned_assets_to_new_person": "{count, plural, one {# öğe} other {# öğeler}} yeni bir kişiye atandı", @@ -1661,6 +1691,7 @@ "reset_sqlite_confirmation": "SQLite veritabanını sıfırlamak istediğinizden emin misiniz? Verileri yeniden eşzamanlamak için oturumu kapatıp tekrar oturum açmanız gerekecektir", "reset_sqlite_success": "SQLite veritabanını başarıyla sıfırladınız", "reset_to_default": "Varsayılana sıfırla", + "resolution": "Çözünürlük", "resolve_duplicates": "Çiftleri çöz", "resolved_all_duplicates": "Tüm çiftler çözüldü", "restore": "Geri yükle", @@ -1671,8 +1702,8 @@ "resume": "Devam et", "resume_paused_jobs": "Sürdür {count, plural, one {# duraklatılmış iş} other {# duraklatılmış işler}}", "retry_upload": "Yeniden yüklemeyi dene", - "review_duplicates": "Çiftleri gözden geçir", - "review_large_files": "Büyük dosyaları inceleyin", + "review_duplicates": "Kopyaları gözden geçir", + "review_large_files": "Büyük dosyaları incele", "role": "Rol", "role_editor": "Düzenleyici", "role_viewer": "Görüntüleyici", @@ -1695,6 +1726,7 @@ "search_by_description_example": "Sapa'da yürüyüş günü", "search_by_filename": "Dosya adına veya uzantısına göre ara", "search_by_filename_example": "Örn. IMG_1234.JPG veya PNG", + "search_camera_lens_model": "Lens modelini ara...", "search_camera_make": "Kamera markasına göre ara...", "search_camera_model": "Kamera modeline göre ara...", "search_city": "Şehre göre ara...", @@ -1773,6 +1805,7 @@ "server_online": "Sunucu çevrimiçi", "server_privacy": "Sunucu Gizliliği", "server_stats": "Sunucu istatistikleri", + "server_update_available": "Sunucu güncellemesi mevcut", "server_version": "Sunucu Sürümü", "set": "Ayarla", "set_as_album_cover": "Albüm resmi olarak ayarla", @@ -1783,7 +1816,7 @@ "set_slideshow_to_fullscreen": "Slayt gösterisini tam ekran yap", "set_stack_primary_asset": "Birincil öğe olarak ayarla", "setting_image_viewer_help": "Görüntüleyici önce küçük resmi gösterir, ardından orta boy önizlemeyi (etkinleştirilmişse) ve son olarak orijinali (etkinleştirilmişse) gösterir.", - "setting_image_viewer_original_subtitle": "Orijinal tam çözünürlüklü görüntüyü göstermek için etkinleştirin. Veri kullanımını azaltmak için devre dışı bırakın (hem ağ hem de cihaz önbelleği).", + "setting_image_viewer_original_subtitle": "Orijinal tam çözünürlüklü görüntüyü (büyük!) yüklemek için etkinleştirin. Veri kullanımını azaltmak için devre dışı bırakın (hem ağ hem de cihaz önbelleği).", "setting_image_viewer_original_title": "Orijinal görüntüyü göster", "setting_image_viewer_preview_subtitle": "Orta çözünürlüklü bir görüntü göstermek için etkinleştirin. Orijinali doğrudan göstermek veya yalnızca küçük resmi kullanmak için devre dışı bırakın.", "setting_image_viewer_preview_title": "Önizleme görüntüsü göster", @@ -1801,6 +1834,8 @@ "setting_notifications_subtitle": "Bildirim tercihlerinizi düzenleyin", "setting_notifications_total_progress_subtitle": "Toplam yükleme ilerlemesi (tamamlanan/toplam)", "setting_notifications_total_progress_title": "Arkaplan yedeklemesi toplam ilerlemesini göster", + "setting_video_viewer_auto_play_subtitle": "Videolar açıldığında otomatik olarak oynatmaya başla", + "setting_video_viewer_auto_play_title": "Videoları otomatik oynat", "setting_video_viewer_looping_title": "Döngü", "setting_video_viewer_original_video_subtitle": "Sunucudan video aktarılırken, transcode (dönüştürülmüş) sürüm mevcut olsa bile orijinal dosya oynatılır. Bu durum, arabelleğe alma (buffering) sorunlarına yol açabilir. Videolar yerel olarak mevcutsa, bu ayardan bağımsız olarak orijinal kalitede oynatılır.", "setting_video_viewer_original_video_title": "Orijinal videoyu zorla", @@ -1863,14 +1898,14 @@ "shared_photos_and_videos_count": "{assetCount, plural, other {# paylaşılan fotoğraflar & videolar.}}", "shared_with_me": "Benimle paylaşılanlar", "shared_with_partner": "{partner} ile paylaşıldı", - "sharing": "Paylaşılıyor", + "sharing": "Paylaşım", "sharing_enter_password": "Bu sayfayı görebilmek için lütfen şifreyi giriniz.", "sharing_page_album": "Paylaşılan albümler", "sharing_page_description": "Ağınızdaki kişilerle fotoğraf ve video paylaşmak için paylaşımlı albümler oluşturun.", "sharing_page_empty_list": "LİSTEYİ BOŞALT", "sharing_sidebar_description": "Yan panelde paylaşılanlara kısa yol göster", "sharing_silver_appbar_create_shared_album": "Yeni paylaşılan albüm", - "sharing_silver_appbar_share_partner": "Partnerle paylaş", + "sharing_silver_appbar_share_partner": "Ortakla paylaş", "shift_to_permanent_delete": "Öğeyi kalıcı olarak silmek için ⇧ tuşuna basın", "show_album_options": "Albüm ayarlarını göster", "show_albums": "Albümleri göster", @@ -1976,10 +2011,11 @@ "theme_setting_system_primary_color_title": "Sistem rengini kullan", "theme_setting_system_theme_switch": "Otomatik (sistem ayarına göre)", "theme_setting_theme_subtitle": "Uygulama teması seç", - "theme_setting_three_stage_loading_subtitle": "Üç aşamalı yükleme, yükleme performansını artırabilir ancak önemli ölçüde daha yüksek ağ yüküne sebep olur", + "theme_setting_three_stage_loading_subtitle": "Üç aşamalı yükleme, yükleme performansını artırabilir ancak ağ yükünü önemli ölçüde artırır", "theme_setting_three_stage_loading_title": "Üç aşamalı yüklemeyi etkinleştir", "they_will_be_merged_together": "Birlikte birleştirilecekler", "third_party_resources": "Üçüncü taraf kaynaklar", + "time": "Zaman", "time_based_memories": "Zaman bazlı anılar", "timeline": "Zaman Çizelgesi", "timezone": "Zaman dilimi", @@ -2012,6 +2048,7 @@ "troubleshoot": "Sorun giderme", "type": "Tür", "unable_to_change_pin_code": "PIN kodu değiştirilemedi", + "unable_to_check_version": "Uygulama veya sunucu sürümü kontrol edilemiyor", "unable_to_setup_pin_code": "PIN kodu ayarlanamadı", "unarchive": "Arşivden çıkar", "unarchive_action_prompt": "{count} Arşivden kaldırıldı", @@ -2077,11 +2114,11 @@ "user_role_set": "{user}, {role} olarak ayarlandı", "user_usage_detail": "Kullanıcı kullanım detayı", "user_usage_stats": "Hesap kullanım istatistikleri", - "user_usage_stats_description": "hesap kullanım istatistiklerini göster", + "user_usage_stats_description": "Hesap kullanım istatistiklerini göster", "username": "Kullanıcı adı", "users": "Kullanıcılar", "users_added_to_album_count": "Albüme {count, plural, one {# user} other {# users}} eklendi", - "utilities": "Yardımcılar", + "utilities": "Yardımcı Programlar", "validate": "Doğrula", "validate_endpoint_error": "Lütfen geçerli bir URL girin", "variables": "Değişkenler", @@ -2091,7 +2128,7 @@ "version_history": "Sürüm Geçmişi", "version_history_item": "{version}, {date} tarihinde kuruldu", "video": "Video", - "video_hover_setting": "Üzerinde durulduğunda video önizlemesi oynat", + "video_hover_setting": "Üzerinde durulduğunda video ön izlemesi oynat", "video_hover_setting_description": "Öğe üzerinde fareyle durulduğunda video küçük resmini oynatır. Bu özellik devre dışıyken, oynatma simgesine fareyle gidilerek oynatma başlatılabilir.", "videos": "Videolar", "videos_count": "{count, plural, one {# video} other {# video}}", diff --git a/i18n/uk.json b/i18n/uk.json index d0b29b5bae..a34e6b8350 100644 --- a/i18n/uk.json +++ b/i18n/uk.json @@ -28,10 +28,12 @@ "add_to_album": "Додати у альбом", "add_to_album_bottom_sheet_added": "Додано до {album}", "add_to_album_bottom_sheet_already_exists": "Вже є в {album}", + "add_to_album_bottom_sheet_some_local_assets": "Деякі локальні ресурси не вдалося додати до альбому", "add_to_album_toggle": "Перемикання вибору для {album}", "add_to_albums": "Додати до альбомів", "add_to_albums_count": "Додати до альбомів ({count})", "add_to_shared_album": "Додати у спільний альбом", + "add_upload_to_stack": "Додати завантаження до стеку", "add_url": "Додати URL", "added_to_archive": "Додано до архіву", "added_to_favorites": "Додано до обраного", @@ -118,7 +120,7 @@ "library_settings_description": "Керування налаштуваннями зовнішніх бібліотек", "library_tasks_description": "Сканувати зовнішні бібліотеки на наявність нових і/або змінених ресурсів", "library_watching_enable_description": "Слідкуйте за змінами файлів у зовнішніх бібліотеках", - "library_watching_settings": "Спостереження за бібліотекою (ЕКСПЕРИМЕНТАЛЬНЕ)", + "library_watching_settings": "Спостереження за бібліотекою [ЕКСПЕРИМЕНТАЛЬНЕ]", "library_watching_settings_description": "Автоматичне спостереження за зміненими файлами", "logging_enable_description": "Увімкнути ведення журналу", "logging_level_description": "Коли увімкнено, який рівень журналювання використовувати.", @@ -209,6 +211,8 @@ "notification_email_ignore_certificate_errors_description": "Ігнорувати помилки перевірки сертифікатів TLS (не рекомендується)", "notification_email_password_description": "Пароль для аутентифікації на поштовому сервері", "notification_email_port_description": "Порт поштового сервера (наприклад, 25, 465 або 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "Використовувати SMTPS (SMTP через TLS)", "notification_email_sent_test_email_button": "Надіслати тестовий лист і зберегти", "notification_email_setting_description": "Налаштування для надсилання email-повідомлень", "notification_email_test_email": "Надіслати тестовий лист", @@ -265,7 +269,7 @@ "server_welcome_message": "Вітальне повідомлення", "server_welcome_message_description": "Повідомлення, яке відображається на сторінці входу.", "sidecar_job": "Метадані з sidecar-файлів", - "sidecar_job_description": "Виявлення або синхронізація метаданих додатків з файлової системи", + "sidecar_job_description": "Пошук або синхронізація сайдкар-метаданих з файлової системи", "slideshow_duration_description": "Кількість секунд для відображення кожного зображення", "smart_search_job_description": "Запуск машинного навчання для ресурсів для підтримки розумного пошуку", "storage_template_date_time_description": "Позначка часу створення ресурсу використовується для інформації про дату й час", @@ -331,7 +335,7 @@ "transcoding_max_b_frames": "Максимальна кількість проміжних кадрів", "transcoding_max_b_frames_description": "Вищі значення покращують ефективність стиснення, але збільшують час кодування. Можуть бути несумісні з апаратним прискоренням на старих пристроях. Значення 0 вимикає B-фрейми, а -1 автоматично налаштовує це значення.", "transcoding_max_bitrate": "Максимальний бітрейт", - "transcoding_max_bitrate_description": "Встановлення максимального бітрейту дозволяє зробити розміри файлів більш передбачуваними за мінорну вартість якості. Наприклад, для 720p типові значення: 2600 кбіт/с для VP9 або HEVC, або 4500 кбіт/с для H.264. Вимикається, якщо встановлено 0.", + "transcoding_max_bitrate_description": "Встановлення максимальної швидкості передачі даних може зробити розміри файлів більш передбачуваними за незначної втрати якості. При роздільній здатності 720p типові значення становлять 2600 кбіт/с для VP9 або HEVC, або 4500 кбіт/с для H.264. Вимкнено, якщо встановлено значення 0. Якщо одиниця виміру не вказана, приймається k (для кбіт/с); отже, 5000, 5000k і 5M (для Мбіт/с) є еквівалентними.", "transcoding_max_keyframe_interval": "Максимальний інтервал ключових кадрів", "transcoding_max_keyframe_interval_description": "Встановлює максимальну відстань між ключовими кадрами. Нижчі значення погіршують ефективність стиснення, але покращують час пошуку і можуть покращити якість в сценах з швидкими рухами. Значення 0 автоматично встановлює це значення.", "transcoding_optimal_description": "Відео з роздільною здатністю вище цільової або не в прийнятому форматі", @@ -347,9 +351,9 @@ "transcoding_settings": "Налаштування транскодування відео", "transcoding_settings_description": "Керування які відео транскодувати і як їх обробляти", "transcoding_target_resolution": "Роздільна здатність", - "transcoding_target_resolution_description": "Вищі роздільні здатності можуть зберігати більше деталей, але займають більше часу на кодування, мають більші розміри файлів і можуть зменшити швидкість роботи додатку.", + "transcoding_target_resolution_description": "Вищі роздільні здатності можуть зберігати більше деталей, але займають більше часу на кодування, мають більші розміри файлів і можуть зменшити швидкість роботи застосунку.", "transcoding_temporal_aq": "Тимчасове AQ", - "transcoding_temporal_aq_description": "Це застосовується лише до NVENC. Підвищує якість сцен з великою деталізацією та низьким рухом. Може бути несумісним зі старими пристроями.", + "transcoding_temporal_aq_description": "Стосується лише NVENC. Часова адаптивна квантизація підвищує якість сцен з високою деталізацією та низьким рівнем руху. Може бути несумісним зі старими пристроями.", "transcoding_threads": "Потоки", "transcoding_threads_description": "Вищі значення прискорюють кодування, але залишають менше місця для обробки інших завдань сервером під час активності. Це значення не повинно бути більше кількості ядер процесора. Максимізує використання, якщо встановлено на 0.", "transcoding_tone_mapping": "Тонова картографія", @@ -400,11 +404,11 @@ "advanced_settings_prefer_remote_subtitle": "Деякі пристрої вельми повільно завантажують мініатюри із елементів на пристрої. Активуйте цей параметр, щоб завантажувати зображення з серверу.", "advanced_settings_prefer_remote_title": "Перевага віддаленим зображенням", "advanced_settings_proxy_headers_subtitle": "Визначте заголовки проксі-сервера, які Immich має надсилати з кожним мережевим запитом", - "advanced_settings_proxy_headers_title": "Проксі-заголовки", + "advanced_settings_proxy_headers_title": "Користувацькі проксі-заголовки [ЕКСПЕРИМЕНТАЛЬНА ВЕРСІЯ]", "advanced_settings_readonly_mode_subtitle": "Увімкнення режиму тільки для читання, в якому фотографії можна тільки переглядати, а такі функції, як вибір декількох зображень, спільний доступ, передача, видалення, вимкнені. Увімкнення/вимкнення режиму тільки для читання за допомогою аватара користувача на головному екрані", "advanced_settings_readonly_mode_title": "Режим лише для читання", "advanced_settings_self_signed_ssl_subtitle": "Пропускає перевірку SSL-сертифіката сервера. Потрібне для самопідписаних сертифікатів.", - "advanced_settings_self_signed_ssl_title": "Дозволити самопідписані SSL-сертифікати", + "advanced_settings_self_signed_ssl_title": "Дозволити самопідписані SSL-сертифікати [ЕКСПЕРИМЕНТАЛЬНА ВЕРСІЯ]", "advanced_settings_sync_remote_deletions_subtitle": "Автоматично видаляти або відновлювати ресурс на цьому пристрої, коли ця дія виконується в веб-інтерфейсі", "advanced_settings_sync_remote_deletions_title": "Синхронізація віддалених видалень [ЕКСПЕРИМЕНТАЛЬНО]", "advanced_settings_tile_subtitle": "Розширені користувацькі налаштування", @@ -464,10 +468,14 @@ "api_key_description": "Це значення буде показане лише один раз. Будь ласка, обов'язково скопіюйте його перед закриттям вікна.", "api_key_empty": "Назва вашого ключа API не може бути порожньою", "api_keys": "Ключі API", + "app_architecture_variant": "Варіант (Архітектура)", "app_bar_signout_dialog_content": "Ви впевнені, що бажаєте вийти з аккаунта?", "app_bar_signout_dialog_ok": "Так", "app_bar_signout_dialog_title": "Вийти з аккаунта", + "app_download_links": "Посилання для завантаження додатків", "app_settings": "Налаштування програми", + "app_stores": "Магазини додатків", + "app_update_available": "Оновлення програми доступне", "appears_in": "З'являється в", "apply_count": "Застосувати ({count, number})", "archive": "Архівувати", @@ -551,6 +559,7 @@ "backup_albums_sync": "Синхронізація резервних копій альбомів", "backup_all": "Усі", "backup_background_service_backup_failed_message": "Не вдалося зробити резервну копію елементів. Повторюю…", + "backup_background_service_complete_notification": "Резервне копіювання активів завершено", "backup_background_service_connection_failed_message": "Не вдалося зв'язатися із сервером. Повторюю…", "backup_background_service_current_upload_notification": "Завантажується {filename}", "backup_background_service_default_notification": "Перевіряю наявність нових елементів…", @@ -598,6 +607,7 @@ "backup_controller_page_turn_on": "Увімкнути резервне копіювання в активному режимі", "backup_controller_page_uploading_file_info": "Завантажую інформацію про файл", "backup_err_only_album": "Не можу видалити єдиний альбом", + "backup_error_sync_failed": "Помилка синхронізації. Не вдається обробити резервну копію.", "backup_info_card_assets": "елементи", "backup_manual_cancelled": "Скасовано", "backup_manual_in_progress": "Завантаження вже відбувається. Спробуйте згодом", @@ -685,8 +695,8 @@ "client_cert_import_success_msg": "Клієнтський сертифікат імпортовано", "client_cert_invalid_msg": "Недійсний файл сертифіката або неправильний пароль", "client_cert_remove_msg": "Клієнтський сертифікат видалено", - "client_cert_subtitle": "Підтримується лише формат PKCS12 (.p12, .pfx). Імпорт/видалення сертифіката доступні лише до входу в систему", - "client_cert_title": "Клієнтський SSL-сертифікат", + "client_cert_subtitle": "Підтримує лише формат PKCS12 (.p12, .pfx). Імпорт/видалення сертифіката доступне лише перед входом у систему", + "client_cert_title": "SSL-сертифікат клієнта [ЕКСПЕРИМЕНТАЛЬНИЙ]", "clockwise": "По годинниковій стрілці", "close": "Закрити", "collapse": "Згорнути", @@ -698,7 +708,6 @@ "comments_and_likes": "Коментарі та лайки", "comments_are_disabled": "Коментарі вимкнено", "common_create_new_album": "Створити новий альбом", - "common_server_error": "Будь ласка, перевірте з'єднання, переконайтеся, що сервер доступний і версія програми/сервера сумісна.", "completed": "Завершено", "confirm": "Підтвердіть", "confirm_admin_password": "Підтвердити пароль адміністратора", @@ -737,6 +746,7 @@ "create": "Створити", "create_album": "Створити альбом", "create_album_page_untitled": "Без назви", + "create_api_key": "Створити ключ API", "create_library": "Створити бібліотеку", "create_link": "Створити посилання", "create_link_to_share": "Створити посилання спільного доступу", @@ -880,7 +890,6 @@ "edit_tag": "Редагувати тег", "edit_title": "Редагувати заголовок", "edit_user": "Редагувати користувача", - "edited": "Відредаговано", "editor": "Редактор", "editor_close_without_save_prompt": "Зміни не будуть збережені", "editor_close_without_save_title": "Закрити редактор?", @@ -1036,6 +1045,7 @@ "exif_bottom_sheet_description_error": "Помилка під час оновлення опису", "exif_bottom_sheet_details": "ПОДРОБИЦІ", "exif_bottom_sheet_location": "МІСЦЕ", + "exif_bottom_sheet_no_description": "Без опису", "exif_bottom_sheet_people": "ЛЮДИ", "exif_bottom_sheet_person_add_person": "Додати ім'я", "exit_slideshow": "Вийти зі слайд-шоу", @@ -1071,7 +1081,7 @@ "feature_photo_updated": "Вибране фото оновлено", "features": "Додаткові можливості", "features_in_development": "Функції в розробці", - "features_setting_description": "Керування додатковими можливостями додатка", + "features_setting_description": "Керування додатковими можливостями застосунку", "file_name": "Ім'я файлу", "file_name_or_extension": "Ім'я файлу або розширення", "filename": "Ім'я файлу", @@ -1111,13 +1121,12 @@ "haptic_feedback_title": "Тактильна віддача", "has_quota": "Квота", "hash_asset": "Гешувати файл", - "hashed_assets": "Гешовані фото та відео", + "hashed_assets": "Хеши", "hashing": "Хешування", "header_settings_add_header_tip": "Додати заголовок", "header_settings_field_validator_msg": "Значення не може бути порожнім", "header_settings_header_name_input": "Ім'я заголовку", "header_settings_header_value_input": "Значення заголовку", - "headers_settings_tile_subtitle": "Визначте заголовки проксі, які програма має надсилати з кожним мережевим запитом", "headers_settings_tile_title": "Користувальницькі заголовки проксі", "hi_user": "Привіт {name} ({email})", "hide_all_people": "Сховати всіх", @@ -1137,7 +1146,7 @@ "home_page_delete_remote_err_local": "Локальні елемент(и) вже в процесі видалення з сервера, пропущено", "home_page_favorite_err_local": "Поки що не можна додати до улюблених локальні елементи, пропущено", "home_page_favorite_err_partner": "Поки що не можна додати до улюблених елементи партнера, пропущено", - "home_page_first_time_notice": "Якщо ви користуєтеся додатком вперше, будь ласка, оберіть альбом для резервного копіювання, щоб на шкалі часу з’явилися фото та відео", + "home_page_first_time_notice": "Якщо ви користуєтеся застосунком вперше, будь ласка, оберіть альбом для резервного копіювання, щоб на шкалі часу з’явилися фото та відео", "home_page_locked_error_local": "Не вдається перемістити локальні файли до особистої папки, пропускається", "home_page_locked_error_partner": "Не вдається перемістити партнерські файли до особистої папки, пропускається", "home_page_share_err_local": "Неможливо поділитися локальними елементами через посилання, пропущено", @@ -1342,6 +1351,8 @@ "minute": "Хвилинку", "minutes": "Хвилини", "missing": "Відсутні", + "mobile_app": "Мобільний додаток", + "mobile_app_download_onboarding_note": "Завантажте супутній мобільний додаток, скориставшись наведеними нижче опціями", "model": "Модель", "month": "Місяць", "monthly_title_text_date_format": "ММММ р", @@ -1360,6 +1371,8 @@ "my_albums": "Мої альбоми", "name": "Ім'я", "name_or_nickname": "Ім'я або псевдонім", + "navigate": "Навігація", + "navigate_to_time": "Перейти до Часу", "network_requirement_photos_upload": "Використовувати стільникові дані для резервного копіювання фото", "network_requirement_videos_upload": "Використовувати стільникові дані для резервного копіювання відео", "network_requirements": "Вимоги до мережі", @@ -1369,6 +1382,7 @@ "never": "ніколи", "new_album": "Новий альбом", "new_api_key": "Новий ключ API", + "new_date_range": "Новий діапазон дат", "new_password": "Новий пароль", "new_person": "Нова людина", "new_pin_code": "Новий PIN-код", @@ -1419,6 +1433,8 @@ "notifications": "Сповіщення", "notifications_setting_description": "Керування сповіщеннями", "oauth": "OAuth", + "obtainium_configurator": "Конфігуратор Obtainium", + "obtainium_configurator_instructions": "Використовуйте Obtainium для встановлення та оновлення програми Android безпосередньо з релізу Immich на GitHub. Створіть ключ API та виберіть варіант, щоб створити посилання на конфігурацію Obtainium", "official_immich_resources": "Офіційні ресурси Immich", "offline": "Офлайн", "offset": "Зсув", @@ -1499,7 +1515,7 @@ "permission_onboarding_permission_denied": "Доступ заборонено. Для використання Immich надайте дозволи до \"Фото та відео\" в налаштуваннях.", "permission_onboarding_permission_granted": "Доступ надано! Все готово.", "permission_onboarding_permission_limited": "Доступ обмежено. Щоби дозволити Immich створювати резервні копії та керувати всією галереєю, надайте дозволи на фото й відео в налаштуваннях.", - "permission_onboarding_request": "Додатку Immich потрібен дозвіл для перегляду ваших фото та відео.", + "permission_onboarding_request": "Застосунку Immich потрібен дозвіл для перегляду ваших фото та відео.", "person": "Людина", "person_age_months": "{months, plural, one {# місяць} other {# місяці}}", "person_age_year_months": "1 year , {months, plural, one {# місяць} other {# місяці}}", @@ -1523,10 +1539,14 @@ "play_memories": "Відтворити спогади", "play_motion_photo": "Відтворювати рухомі фото", "play_or_pause_video": "Відтворення або призупинення відео", + "play_original_video": "Відтворювати оригінальне відео", + "play_original_video_setting_description": "Надавати перевагу відтворенню оригінальних відео, а не перекодованих. Якщо оригінальний файл несумісний, відтворення може бути некоректним.", + "play_transcoded_video": "Відтворювати перекодоване відео", "please_auth_to_access": "Будь ласка, пройдіть автентифікацію", "port": "Порт", - "preferences_settings_subtitle": "Керування налаштуваннями додатку", + "preferences_settings_subtitle": "Керування налаштуваннями застосунку", "preferences_settings_title": "Параметри", + "preparing": "Підготовка", "preset": "Передвстановлення", "preview": "Прев'ю", "previous": "Попереднє", @@ -1539,13 +1559,9 @@ "privacy": "Конфіденційність", "profile": "Профіль", "profile_drawer_app_logs": "Журнал", - "profile_drawer_client_out_of_date_major": "Мобільний застосунок застарів. Будь ласка, оновіть до останньої мажорної версії.", - "profile_drawer_client_out_of_date_minor": "Мобільний застосунок застарів. Будь ласка, оновіть до останньої мінорної версії.", "profile_drawer_client_server_up_to_date": "Клієнт та сервер — актуальні", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Режим лише для читання ввімкнено. Щоб вийти, довго натисніть значок аватара користувача.", - "profile_drawer_server_out_of_date_major": "Сервер застарів. Будь ласка, оновіть до останньої мажорної версії.", - "profile_drawer_server_out_of_date_minor": "Сервер застарів. Будь ласка, оновіть до останньої мінорної версії.", "profile_image_of_user": "Зображення профілю {user}", "profile_picture_set": "Зображення профілю встановлено.", "public_album": "Публічний альбом", @@ -1592,6 +1608,7 @@ "read_changelog": "Прочитати зміни в оновленні", "readonly_mode_disabled": "Режим лише для читання вимкнено", "readonly_mode_enabled": "Режим лише для читання ввімкнено", + "ready_for_upload": "Готово до завантаження", "reassign": "Перепризначити", "reassigned_assets_to_existing_person": "Перепризначено {count, plural, one {# ресурс} few {# ресурси} many {# ресурсів} other {# ресурсів}} {name, select, null {існуючій особі} other {{name}}}", "reassigned_assets_to_new_person": "Перепризначено {count, plural, one {# ресурс} other {# ресурси}} новій особі", @@ -1695,6 +1712,7 @@ "search_by_description_example": "Похідний день у Сапі", "search_by_filename": "Пошук за назвою або розширенням файлу", "search_by_filename_example": "Наприклад, IMG_1234.JPG або PNG", + "search_camera_lens_model": "Пошук моделі об’єктива…", "search_camera_make": "Пошук виробника камери...", "search_camera_model": "Пошук моделі камери...", "search_city": "Пошук міста...", @@ -1767,12 +1785,13 @@ "send_message": "Надіслати повідомлення", "send_welcome_email": "Надішліть вітальний лист", "server_endpoint": "Кінцева точка сервера", - "server_info_box_app_version": "Версія додатка", + "server_info_box_app_version": "Версія застосунку", "server_info_box_server_url": "URL сервера", "server_offline": "Сервер офлайн", "server_online": "Сервер онлайн", "server_privacy": "Конфіденційність сервера", "server_stats": "Статистика сервера", + "server_update_available": "Оновлення сервера доступне", "server_version": "Версія сервера", "set": "Встановіть", "set_as_album_cover": "Встановити як обкладинку альбому", @@ -1789,7 +1808,7 @@ "setting_image_viewer_preview_title": "Завантажувати зображення попереднього перегляду", "setting_image_viewer_title": "Зображення", "setting_languages_apply": "Застосувати", - "setting_languages_subtitle": "Змінити мову додатку", + "setting_languages_subtitle": "Змінити мову застосунку", "setting_notifications_notify_failures_grace_period": "Повідомити про помилки фонового резервного копіювання: {duration}", "setting_notifications_notify_hours": "{count} годин", "setting_notifications_notify_immediately": "негайно", @@ -1801,6 +1820,8 @@ "setting_notifications_subtitle": "Налаштування параметрів сповіщень", "setting_notifications_total_progress_subtitle": "Загальний прогрес (виконано/загалом)", "setting_notifications_total_progress_title": "Показати загальний хід фонового резервного копіювання", + "setting_video_viewer_auto_play_subtitle": "Автоматично починати відтворення відео під час їх відкриття", + "setting_video_viewer_auto_play_title": "Автоматичне відтворення відео", "setting_video_viewer_looping_title": "Циклічне відтворення", "setting_video_viewer_original_video_subtitle": "При трансляції відео з сервера відтворювати оригінал, навіть якщо доступна транскодування. Може призвести до буферизації. Відео, доступні локально, відтворюються в оригінальній якості, незважаючи на це налаштування.", "setting_video_viewer_original_video_title": "Примусово відтворювати оригінальне відео", @@ -1975,7 +1996,7 @@ "theme_setting_primary_color_title": "Основний колір", "theme_setting_system_primary_color_title": "Використовувати колір системи", "theme_setting_system_theme_switch": "Автоматично (як у системі)", - "theme_setting_theme_subtitle": "Налаштування теми додатка", + "theme_setting_theme_subtitle": "Налаштування теми застосунку", "theme_setting_three_stage_loading_subtitle": "Триетапне завантаження може підвищити продуктивність завантаження, але спричинить значно більше навантаження на мережу", "theme_setting_three_stage_loading_title": "Увімкнути триетапне завантаження", "they_will_be_merged_together": "Вони будуть об'єднані разом", @@ -2012,6 +2033,7 @@ "troubleshoot": "Виправлення неполадок", "type": "Тип", "unable_to_change_pin_code": "Неможливо змінити PIN-код", + "unable_to_check_version": "Не вдається перевірити версію програми або сервера", "unable_to_setup_pin_code": "Неможливо налаштувати PIN-код", "unarchive": "Розархівувати", "unarchive_action_prompt": "{count} вилучено з архіву", diff --git a/i18n/vi.json b/i18n/vi.json index b9ee8cfcc9..ad584d7155 100644 --- a/i18n/vi.json +++ b/i18n/vi.json @@ -643,7 +643,6 @@ "comments_and_likes": "Bình luận & lượt thích", "comments_are_disabled": "Bình luận đã bị tắt", "common_create_new_album": "Tạo album mới", - "common_server_error": "Vui lòng kiểm tra kết nối mạng của bạn, đảm bảo máy chủ có thể truy cập được và phiên bản ứng dụng/máy chủ tương thích với nhau.", "completed": "Hoàn tất", "confirm": "Xác nhận", "confirm_admin_password": "Xác nhận mật khẩu quản trị viên", @@ -809,7 +808,6 @@ "edit_tag": "Chỉnh sửa thẻ", "edit_title": "Chỉnh sửa tiêu đề", "edit_user": "Chỉnh sửa người dùng", - "edited": "Đã chỉnh sửa", "editor": "Trình chỉnh sửa", "editor_close_without_save_prompt": "Những thay đổi sẽ không được lưu", "editor_close_without_save_title": "Đóng trình chỉnh sửa?", @@ -1028,7 +1026,6 @@ "header_settings_field_validator_msg": "Trường này không được để trống", "header_settings_header_name_input": "Tên Header", "header_settings_header_value_input": "Giá trị Header", - "headers_settings_tile_subtitle": "Thiết lập tiêu đề proxy gửi kèm mỗi yêu cầu mạng", "headers_settings_tile_title": "Tiêu đề proxy tùy chỉnh", "hi_user": "Chào {name} ({email})", "hide_all_people": "Ẩn tất cả mọi người", @@ -1418,12 +1415,8 @@ "privacy": "Bảo mật", "profile": "Hồ sơ", "profile_drawer_app_logs": "Nhật ký", - "profile_drawer_client_out_of_date_major": "Ứng dụng đã lỗi thời. Vui lòng cập nhật lên phiên bản chính mới nhất.", - "profile_drawer_client_out_of_date_minor": "Ứng dụng đã lỗi thời. Vui lòng cập nhật lên phiên bản phụ mới nhất.", "profile_drawer_client_server_up_to_date": "Máy khách và máy chủ đã cập nhật", "profile_drawer_github": "GitHub", - "profile_drawer_server_out_of_date_major": "Máy chủ đã lỗi thời. Vui lòng cập nhật lên phiên bản chính mới nhất.", - "profile_drawer_server_out_of_date_minor": "Máy chủ đã lỗi thời. Vui lòng cập nhật lên phiên bản phụ mới nhất.", "profile_image_of_user": "Ảnh đại diệncủa {user}", "profile_picture_set": "Ảnh đại diện đã được đặt.", "public_album": "Album công khai", diff --git a/i18n/zh_Hant.json b/i18n/zh_Hant.json index b972639153..8baeaec7d8 100644 --- a/i18n/zh_Hant.json +++ b/i18n/zh_Hant.json @@ -10,7 +10,7 @@ "activity": "動態", "activity_changed": "動態已{enabled, select, true {開啟} other {關閉}}", "add": "加入", - "add_a_description": "加入文字說明", + "add_a_description": "新增描述", "add_a_location": "新增地點", "add_a_name": "加入姓名", "add_a_title": "新增標題", @@ -28,18 +28,20 @@ "add_to_album": "加入到相簿", "add_to_album_bottom_sheet_added": "新增到 {album}", "add_to_album_bottom_sheet_already_exists": "已在 {album} 中", + "add_to_album_bottom_sheet_some_local_assets": "無法將某些本機資產新增到相簿", "add_to_album_toggle": "選擇相簿{album}", "add_to_albums": "加入相簿", - "add_to_albums_count": "將({count})個項目加入相簿", + "add_to_albums_count": "將 ({count}) 個項目加入相簿", "add_to_shared_album": "加到共享相簿", - "add_url": "建立連結", + "add_upload_to_stack": "新增上傳到堆疊", + "add_url": "新增 URL", "added_to_archive": "移至封存", "added_to_favorites": "加入收藏", "added_to_favorites_count": "將 {count, number} 個項目加入收藏", "admin": { "add_exclusion_pattern_description": "新增排除條件。支援使用「*」、「 **」、「?」來找尋符合規則的字串。如果要在任何名為「Raw」的目錄內排除所有符合條件的檔案,請使用「**/Raw/**」。如果要排除所有「.tif」結尾的檔案,請使用「**/*.tif」。如果要排除某個絕對路徑,請使用「/path/to/ignore/**」。", "admin_user": "管理員", - "asset_offline_description": "此外部媒體庫項目已無法在磁碟上找到,並已移至垃圾桶。若該檔案是在媒體庫內移動,請在時間軸中查看新的對應項目。若要還原此項目,請確保下方的檔案路徑可供 Immich 存取,並重新掃描媒體庫。", + "asset_offline_description": "此外部媒體庫項目已無法在磁碟上找到,並已移至垃圾桶。若該檔案是在媒體庫內移動,請在時間軸中檢視新的對應項目。若要還原此項目,請確保下方的檔案路徑可供 Immich 存取,並重新掃描媒體庫。", "authentication_settings": "驗證設定", "authentication_settings_description": "管理密碼、OAuth 與其他驗證設定", "authentication_settings_disable_all": "確定要停用所有登入方式嗎?這樣會完全無法登入。", @@ -49,8 +51,8 @@ "backup_database_enable_description": "啟用資料庫備份", "backup_keep_last_amount": "保留先前備份的數量", "backup_onboarding_1_description": "在雲端或其他實體位置的異地備份副本。", - "backup_onboarding_2_description": "儲存在不同裝置上的本地副本。這包括主要檔案及其本地備份。", - "backup_onboarding_3_description": "您資料的總備份份數,包括原始檔案在內。這包括 1 份異地備份與 2 份本地副本。", + "backup_onboarding_2_description": "儲存在不同裝置上的本機副本。這包括主要檔案及其本機備份。", + "backup_onboarding_3_description": "您資料的總備份份數,包括原始檔案在內。這包括 1 份異地備份與 2 份本機副本。", "backup_onboarding_description": "建議採用 3-2-1 備份策略 來保護您的資料。您應保留已上傳的照片/影片副本,以及 Immich 資料庫,以建立完整的備份方案。", "backup_onboarding_footer": "更多備份 Immich 資訊,請參考說明文件。", "backup_onboarding_parts_title": "遵從備份原則 3-2-1:", @@ -81,7 +83,7 @@ "image_format": "格式", "image_format_description": "WebP 能產生相對於 JPEG 更小的檔案,但編碼速度較慢。", "image_fullsize_description": "移除中繼資料的大尺寸影像,在放大圖片時使用", - "image_fullsize_enabled": "啟用大尺寸影像生成", + "image_fullsize_enabled": "啟用大尺寸影像產生", "image_fullsize_enabled_description": "產生非網頁友善格式的大尺寸影像。啟用「偏好嵌入的預覽」時,會直接使用內嵌預覽而不進行轉換。不會影響 JPEG 等網頁友善格式。", "image_fullsize_quality_description": "大尺寸影像品質,範圍為 1 到 100。數值越高品質越好,但檔案也會越大。", "image_fullsize_title": "大尺寸影像設定", @@ -118,11 +120,18 @@ "library_settings_description": "管理外部媒體庫設定", "library_tasks_description": "掃描外部媒體庫以尋找新增或變更的項目", "library_watching_enable_description": "監控外部媒體庫的檔案變化", - "library_watching_settings": "媒體庫監控(實驗性)", + "library_watching_settings": "媒體庫監控[實驗性]", "library_watching_settings_description": "自動監控檔案的變化", "logging_enable_description": "啟用日誌記錄", "logging_level_description": "啟用時的日誌層級。", "logging_settings": "日誌", + "machine_learning_availability_checks": "可用性檢查", + "machine_learning_availability_checks_description": "自動偵測並優先選擇可用的機器學習伺服器", + "machine_learning_availability_checks_enabled": "啟用可用性檢查", + "machine_learning_availability_checks_interval": "檢查間隔", + "machine_learning_availability_checks_interval_description": "可用性檢查之間的間隔(毫秒)", + "machine_learning_availability_checks_timeout": "請求超時", + "machine_learning_availability_checks_timeout_description": "可用性檢查超時(毫秒)", "machine_learning_clip_model": "CLIP 模型", "machine_learning_clip_model_description": "這裡有份 CLIP 模型名單。注意:更換模型後須對所有圖片重新執行「智慧搜尋」任務。", "machine_learning_duplicate_detection": "重複項目偵測", @@ -145,20 +154,22 @@ "machine_learning_min_detection_score_description": "臉孔偵測的最低信心分數,範圍為 0 至 1。數值較低時會偵測到更多臉孔,但可能導致誤判。", "machine_learning_min_recognized_faces": "最低臉部辨識數量", "machine_learning_min_recognized_faces_description": "建立新人物所需的最低已辨識臉孔數量。提高此數值可讓臉孔辨識更精確,但同時會增加臉孔未被指派給任何人物的可能性。", + "machine_learning_ocr": "文字辨識(OCR)", + "machine_learning_ocr_description": "使用機器學習來識別圖片中的文字", "machine_learning_settings": "機器學習設定", "machine_learning_settings_description": "管理機器學習的功能和設定", "machine_learning_smart_search": "智慧搜尋", "machine_learning_smart_search_description": "使用 CLIP 嵌入向量以語意方式搜尋影像", "machine_learning_smart_search_enabled": "啟用智慧搜尋", "machine_learning_smart_search_enabled_description": "如果停用,影像將不會被編碼以進行智慧搜尋。", - "machine_learning_url_description": "機器學習伺服器的 URL。若提供多個 URL,系統會依序逐一嘗試,直到其中一台成功回應為止(由前到後)。未回應的伺服器將被暫時忽略,直到其重新上線。", + "machine_learning_url_description": "機器學習伺服器的 URL。若提供多個 URL,系統會依序逐一嘗試,直到其中一臺成功回應為止(由前到後)。未回應的伺服器將被暫時忽略,直到其重新上線。", "manage_concurrency": "管理併發", "manage_log_settings": "管理日誌設定", "map_dark_style": "深色樣式", "map_enable_description": "啟用地圖功能", "map_gps_settings": "地圖與 GPS 設定", "map_gps_settings_description": "管理地圖與 GPS(反向地理編碼)設定", - "map_implications": "地圖功能依賴外部圖磚服務(tiles.immich.cloud)", + "map_implications": "地圖功能仰賴外部圖磚服務(tiles.immich.cloud)", "map_light_style": "淺色樣式", "map_manage_reverse_geocoding_settings": "管理逆向地理編碼設定", "map_reverse_geocoding": "反向地理編碼", @@ -191,7 +202,7 @@ "nightly_tasks_start_time_setting_description": "伺服器開始執行夜間任務的時間", "nightly_tasks_sync_quota_usage_setting": "同步配額使用情況", "nightly_tasks_sync_quota_usage_setting_description": "根據目前的使用量更新使用者的儲存配額", - "no_paths_added": "沒有已添加的路徑", + "no_paths_added": "沒有已新增的路徑", "no_pattern_added": "尚未新增排除規則", "note_apply_storage_label_previous_assets": "提示:若要將儲存標籤套用到先前上傳的媒體檔案,請執行", "note_cannot_be_changed_later": "注意:此設定日後無法變更!", @@ -202,15 +213,17 @@ "notification_email_ignore_certificate_errors_description": "忽略 TLS 憑證驗證錯誤(不建議)", "notification_email_password_description": "用於與電子郵件伺服器驗證的密碼", "notification_email_port_description": "電子郵件伺服器埠口(例如 25、465 或 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "使用SMTPS(基於TLS的SMTP)", "notification_email_sent_test_email_button": "傳送測試電子郵件並儲存", "notification_email_setting_description": "寄送電子郵件通知的設定", "notification_email_test_email": "傳送測試電子郵件", - "notification_email_test_email_failed": "無法發送測試電子郵件,請檢查您的設定資訊", - "notification_email_test_email_sent": "測試電子郵件已發送至 {email}。請檢查您的收件匣。", + "notification_email_test_email_failed": "無法傳送測試電子郵件,請檢查您的設定資訊", + "notification_email_test_email_sent": "測試電子郵件已傳送至 {email}。請檢查您的收件匣。", "notification_email_username_description": "用於與電子郵件伺服器驗證的使用者名稱", "notification_enable_email_notifications": "啟用電子郵件通知", "notification_settings": "通知設定", - "notification_settings_description": "管理通知設置,包括電子郵件", + "notification_settings_description": "管理通知設定,包括電子郵件", "oauth_auto_launch": "自動啟動", "oauth_auto_launch_description": "進入登入頁面時,自動啟動 OAuth 登入流程", "oauth_auto_register": "自動註冊", @@ -234,6 +247,7 @@ "oauth_storage_quota_default_description": "未提供宣告時所使用的配額(GiB)。", "oauth_timeout": "請求逾時", "oauth_timeout_description": "請求的逾時時間(毫秒)", + "ocr_job_description": "使用機器學習來識別圖像中的文字", "password_enable_description": "使用電子郵件和密碼登入", "password_settings": "密碼登入", "password_settings_description": "管理密碼登入設定", @@ -252,7 +266,7 @@ "server_external_domain_settings": "外部網域", "server_external_domain_settings_description": "公開分享連結的網域,包含 http(s)://", "server_public_users": "公開使用者", - "server_public_users_description": "在將使用者新增到共享相簿時,會列出所有使用者的姓名與電子郵件。停用此功能後,使用者清單將僅供系統管理員查看。", + "server_public_users_description": "在將使用者新增到共享相簿時,會列出所有使用者的姓名與電子郵件。停用此功能後,使用者清單將僅供系統管理員檢視。", "server_settings": "伺服器設定", "server_settings_description": "管理伺服器設定", "server_welcome_message": "歡迎訊息", @@ -262,10 +276,10 @@ "slideshow_duration_description": "每張圖片放映的秒數", "smart_search_job_description": "執行機器學習有助於智慧搜尋", "storage_template_date_time_description": "檔案的建立時間戳會用於日期與時間資訊", - "storage_template_date_time_sample": "採樣時間 {date}", + "storage_template_date_time_sample": "取樣時間 {date}", "storage_template_enable_description": "啟用儲存範本引擎", - "storage_template_hash_verification_enabled": "雜湊函数驗證已啟用", - "storage_template_hash_verification_enabled_description": "啟用雜湊函数驗證,除非您很清楚地知道這個選項的作用,否則請勿停用此功能", + "storage_template_hash_verification_enabled": "雜湊函式驗證已啟用", + "storage_template_hash_verification_enabled_description": "啟用雜湊函式驗證,除非您很清楚地知道這個選項的作用,否則請勿停用此功能", "storage_template_migration": "儲存範本遷移", "storage_template_migration_description": "將目前的 {template} 套用到先前上傳的項目", "storage_template_migration_info": "儲存範本會將所有副檔名轉換為小寫。範本變更只會套用到新的項目。若要將範本追溯套用到先前上傳的項目,請執行 {job}。", @@ -286,15 +300,15 @@ "template_email_update_album": "相簿更新範本", "template_email_welcome": "歡迎郵件範本", "template_settings": "通知範本", - "template_settings_description": "管理通知的自定義範本", - "theme_custom_css_settings": "自定義 CSS", + "template_settings_description": "管理通知的自訂範本", + "theme_custom_css_settings": "自訂 CSS", "theme_custom_css_settings_description": "可以用層疊樣式表(CSS)來自訂 Immich 的設計。", "theme_settings": "主題設定", - "theme_settings_description": "自訂 Immich 的網頁界面", + "theme_settings_description": "自訂 Immich 的網頁介面", "thumbnail_generation_job": "產生縮圖", "thumbnail_generation_job_description": "為每個檔案產生大、小及模糊縮圖,也為每位人物產生縮圖", "transcoding_acceleration_api": "加速 API", - "transcoding_acceleration_api_description": "此 API 會使用您的硬體以加速轉碼流程。此設定採「盡力而為」模式——若轉碼失敗,將會回退至軟體轉碼。VP9 是否能運作,取決於您的硬體配置。", + "transcoding_acceleration_api_description": "此 API 會使用您的硬體以加速轉碼流程。此設定採「盡力而為」模式——若轉碼失敗,將會回退至軟體轉碼。VP9 是否能運作,取決於您的硬體設定。", "transcoding_acceleration_nvenc": "NVENC(需要 NVIDIA GPU)", "transcoding_acceleration_qsv": "Quick Sync(需要第 7 代或更新的 Intel 處理器)", "transcoding_acceleration_rkmpp": "RKMPP(僅適用於 Rockchip SOCs)", @@ -314,7 +328,7 @@ "transcoding_constant_quality_mode_description": "ICQ 的效果優於 CQP,但部分硬體加速裝置不支援此模式。設定此選項時,在使用以品質為基準的編碼時會優先採用所指定的模式。NVENC 不支援 ICQ,因此此設定在 NVENC 下會被忽略。", "transcoding_constant_rate_factor": "恆定速率因子(-crf)", "transcoding_constant_rate_factor_description": "視訊品質等級。典型值為 H.264 的 23、HEVC 的 28、VP9 的 31 和 AV1 的 35。數值越低,品質越好,但會產生較大的檔案。", - "transcoding_disabled_description": "不對任何影片進行轉碼,可能會導致部分客戶端無法正常播放", + "transcoding_disabled_description": "不對任何影片進行轉碼,可能會導致部分用戶端無法正常播放", "transcoding_encoding_options": "編碼選項", "transcoding_encoding_options_description": "設定編碼影片的編解碼器、解析度、品質和其他選項", "transcoding_hardware_acceleration": "硬體加速", @@ -324,13 +338,13 @@ "transcoding_max_b_frames": "最大 B 幀數", "transcoding_max_b_frames_description": "較高的數值可提升壓縮效率,但會降低編碼速度。在較舊的裝置上,可能與硬體加速不相容。0 代表停用 B 幀,而 -1 則會自動設定此數值。", "transcoding_max_bitrate": "最大位元速率", - "transcoding_max_bitrate_description": "設定最大位元率可以在輕微犧牲品質的情況下,讓檔案大小更容易預測。在 720p 解析度下,VP9 或 HEVC 的典型值為 2600 kbit/s,H.264 則為 4500 kbit/s。設為 0 則停用此功能。", + "transcoding_max_bitrate_description": "設定最大位元率可以在輕微犧牲品質的情況下,讓檔案大小更容易預測。在 720p 解析度下,VP9 或 HEVC 的典型值為 2600 kbit/s,H.264 則為 4500 kbit/s。設為 0 則停用此功能。當沒有指定組織時,假設k(代表kbit/s); 囙此,5000、5000k和5M(Mbit/s)是等效的。", "transcoding_max_keyframe_interval": "最大關鍵幀間隔", - "transcoding_max_keyframe_interval_description": "設置關鍵幀之間的最大幀距。較低的值會降低壓縮效率,但可以改善搜尋時間,並有可能會改善快速變動場景的品質。0 會自動設置此值。", + "transcoding_max_keyframe_interval_description": "設定關鍵幀之間的最大幀距。較低的值會降低壓縮效率,但可以改善搜尋時間,並有可能會改善快速變動場景的品質。0 會自動設定此值。", "transcoding_optimal_description": "高於目標解析度或格式不在可接受範圍的影片", "transcoding_policy": "轉碼策略", "transcoding_policy_description": "設定影片進行轉碼的條件", - "transcoding_preferred_hardware_device": "首選硬體設備", + "transcoding_preferred_hardware_device": "首選硬體裝置", "transcoding_preferred_hardware_device_description": "僅適用於 VAAPI 和 QSV。設定用於硬體轉碼的 dri 節點。", "transcoding_preset_preset": "預設值(-preset)", "transcoding_preset_preset_description": "壓縮速度。較慢的預設值會產生較小的檔案,並在鎖定位元率時提升品質。VP9 在速度高於「faster」時將忽略設定。", @@ -342,10 +356,10 @@ "transcoding_target_resolution": "目標解析度", "transcoding_target_resolution_description": "較高的解析度可以保留更多細節,但編碼時間較長,檔案也較大,且可能降低應用程式的回應速度。", "transcoding_temporal_aq": "時間自適應量化(Temporal AQ)", - "transcoding_temporal_aq_description": "僅適用於 NVENC,可提升高細節、低動態場景的畫質。可能與較舊的設備不相容。", + "transcoding_temporal_aq_description": "僅適用於 NVENC,時域自我調整量化可提升高細節、低動態場景的畫質。可能與較舊的裝置不相容。", "transcoding_threads": "執行緒數量", - "transcoding_threads_description": "較高的值會加快編碼速度,但會減少伺服器在運行過程中處理其他任務的空間。此值不應超過 CPU 核心數。設定為 0 可以最大化利用率。", - "transcoding_tone_mapping": "色調映射", + "transcoding_threads_description": "較高的值會加快編碼速度,但會減少伺服器在執行過程中處理其他任務的空間。此值不應超過 CPU 核心數。設定為 0 可以最大化利用率。", + "transcoding_tone_mapping": "色調對映", "transcoding_tone_mapping_description": "在將 HDR 影片轉換為 SDR 時,盡量維持原始觀感。每種演算法在色彩、細節和亮度方面都有不同的權衡。Hable 保留細節,Mobius 保留色彩,Reinhard 保留亮度。", "transcoding_transcode_policy": "轉碼策略", "transcoding_transcode_policy_description": "影片何時應進行轉碼的策略。HDR 影片一定會轉碼(除非停用轉碼)。", @@ -360,7 +374,7 @@ "trash_settings_description": "管理垃圾桶設定", "unlink_all_oauth_accounts": "解除所有 OAuth 帳號的連結", "unlink_all_oauth_accounts_description": "在遷移至新的服務提供者前,請不要忘記要先解除所有與 OAuth 帳戶的連結。", - "unlink_all_oauth_accounts_prompt": "您是否確認要解除所有與 OAuth 帳戶的連結? 所有相關的使用者身份會被重設,並且不能被還原。", + "unlink_all_oauth_accounts_prompt": "您是否確認要解除所有與 OAuth 帳戶的連結?所有相關的使用者身份會被重設,並且不能被還原。", "user_cleanup_job": "清理使用者", "user_delete_delay": "{user} 的帳號和項目會在 {delay, plural, one {# 天} other {# 天}} 後永久刪除。", "user_delete_delay_settings": "延後刪除", @@ -390,17 +404,17 @@ "advanced_settings_enable_alternate_media_filter_subtitle": "使用此選項可在同步時依其他條件篩選媒體。僅在應用程式無法偵測到所有相簿時再嘗試使用。", "advanced_settings_enable_alternate_media_filter_title": "[實驗性] 使用替代的裝置相簿同步篩選器", "advanced_settings_log_level_title": "日誌等級:{level}", - "advanced_settings_prefer_remote_subtitle": "部分裝置從本機ˊ體庫載入縮圖的速度非常慢。啟用此設定可改為載入遠端圖片。", + "advanced_settings_prefer_remote_subtitle": "部分裝置從本機媒體庫載入縮圖的速度非常慢。啟用此設定可改為載入遠端圖片。", "advanced_settings_prefer_remote_title": "偏好遠端影像", - "advanced_settings_proxy_headers_subtitle": "定義 Immich 在每次網路請求時應該發送的代理標頭", - "advanced_settings_proxy_headers_title": "代理標頭", + "advanced_settings_proxy_headers_subtitle": "定義 Immich 在每次網路請求時應該傳送的代理標頭", + "advanced_settings_proxy_headers_title": "自定義代理標頭[實驗性]", "advanced_settings_readonly_mode_subtitle": "開啟唯讀模式後,照片只能瀏覽,像是多選影像、分享、投放、刪除等功能都會關閉。可在主畫面透過使用者頭像來開啟/關閉唯讀模式", "advanced_settings_readonly_mode_title": "唯讀模式", "advanced_settings_self_signed_ssl_subtitle": "略過伺服器端點的 SSL 憑證驗證。自簽憑證時必須啟用此設定。", - "advanced_settings_self_signed_ssl_title": "允許自簽的 SSL 憑證", + "advanced_settings_self_signed_ssl_title": "允許自簽的 SSL 憑證[實驗性]", "advanced_settings_sync_remote_deletions_subtitle": "當在網頁端執行刪除或還原操作時,自動在此裝置上刪除或還原該媒體", "advanced_settings_sync_remote_deletions_title": "同步遠端刪除 [實驗性]", - "advanced_settings_tile_subtitle": "進階用戶設定", + "advanced_settings_tile_subtitle": "進階使用者設定", "advanced_settings_troubleshooting_subtitle": "啟用額外功能以進行疑難排解", "advanced_settings_troubleshooting_title": "疑難排解", "age_months": "{months, plural, one {# 個月} other {# 個月}}", @@ -423,6 +437,7 @@ "album_remove_user_confirmation": "確定要移除 {user} 嗎?", "album_search_not_found": "找不到符合搜尋條件的相簿", "album_share_no_users": "看來您與所有使用者共享了這本相簿,或沒有其他使用者可供分享。", + "album_summary": "相簿摘要", "album_updated": "更新相簿時", "album_updated_setting_description": "當共享相簿有新項目時用電子郵件通知我", "album_user_left": "離開 {album}", @@ -435,12 +450,12 @@ "album_viewer_appbar_share_leave": "離開相簿", "album_viewer_appbar_share_to": "分享給", "album_viewer_page_share_add_users": "邀請其他人", - "album_with_link_access": "任何擁有連結的人都能查看此相簿中的照片與使用者。", + "album_with_link_access": "任何擁有連結的人都能檢視此相簿中的照片與人物。", "albums": "相簿", "albums_count": "{count, plural, one {{count, number} 個相簿} other {{count, number} 個相簿}}", "albums_default_sort_order": "預設相簿排序", "albums_default_sort_order_description": "建立新相簿時要初始化項目排序方式。", - "albums_feature_description": "一系列可以分享給其他用戶的項目。", + "albums_feature_description": "一系列可以分享給其他使用者的項目。", "albums_on_device_count": "此裝置有 ({count}) 個相簿", "all": "全部", "all_albums": "所有相簿", @@ -456,18 +471,23 @@ "api_key_description": "此金鑰僅顯示一次。請在關閉前複製它。", "api_key_empty": "您的 API 金鑰名稱不能為空值", "api_keys": "API 金鑰", + "app_architecture_variant": "變體(架構)", "app_bar_signout_dialog_content": "您確定要登出嗎?", "app_bar_signout_dialog_ok": "是", "app_bar_signout_dialog_title": "登出", + "app_download_links": "應用下載連結", "app_settings": "應用程式設定", + "app_stores": "應用商店", + "app_update_available": "應用程序更新可用", "appears_in": "出現於", + "apply_count": "應用 ({count, number})", "archive": "封存", "archive_action_prompt": "已將 ({count}) 個加入進封存", "archive_or_unarchive_photo": "封存或取消封存照片", "archive_page_no_archived_assets": "未找到封存媒體", "archive_page_title": "封存 ({count})", "archive_size": "封存大小", - "archive_size_description": "設定要下載的封存檔案大小 (單位: GiB)", + "archive_size_description": "設定要下載的封存檔案大小 (單位:GiB)", "archived": "已封存", "archived_count": "{count, plural, other {已封存 # 個項目}}", "are_these_the_same_person": "同一位人物?", @@ -481,18 +501,20 @@ "asset_has_unassigned_faces": "媒體有未分配的臉孔", "asset_hashing": "正在計算雜湊…", "asset_list_group_by_sub_title": "分類方式", - "asset_list_layout_settings_dynamic_layout_title": "動態布局", + "asset_list_layout_settings_dynamic_layout_title": "動態版面", "asset_list_layout_settings_group_automatically": "自動", "asset_list_layout_settings_group_by": "媒體分類方式", "asset_list_layout_settings_group_by_month_day": "月份和日期", - "asset_list_layout_sub_title": "布局", - "asset_list_settings_subtitle": "相片格狀布局設定", + "asset_list_layout_sub_title": "版面", + "asset_list_settings_subtitle": "相片格狀版面設定", "asset_list_settings_title": "相片格狀檢視", "asset_offline": "媒體離線", "asset_offline_description": "此外部媒體已無法在磁碟中找到。請聯絡您的 Immich 管理員以取得協助。", "asset_restored_successfully": "媒體復原成功", "asset_skipped": "已跳過", "asset_skipped_in_trash": "已在垃圾桶", + "asset_trashed": "資產被丟棄", + "asset_troubleshoot": "資產故障排除", "asset_uploaded": "已上傳", "asset_uploading": "上傳中…", "asset_viewer_settings_subtitle": "管理您的媒體庫檢視器設定", @@ -521,13 +543,15 @@ "assets_were_part_of_album_count": "{count, plural, one {該媒體已} other {這些媒體已}}在相簿中", "assets_were_part_of_albums_count": "{count, plural, one {個} other {個}}項目已被儲存在相簿中", "authorized_devices": "已授權裝置", - "automatic_endpoint_switching_subtitle": "當可用時,透過指定的 Wi-Fi 在本地連線,其他情況則使用替代連線", + "automatic_endpoint_switching_subtitle": "當可用時,透過指定的 Wi-Fi 在本機連線,其他情況則使用替代連線", "automatic_endpoint_switching_title": "自動 URL 切換", "autoplay_slideshow": "自動播放幻燈片", - "back": "返回", - "back_close_deselect": "返回、關閉及取消選取", + "back": "上一頁", + "back_close_deselect": "回上一頁、關閉並取消選取", + "background_backup_running_error": "後臺備份目前正在執行,無法啟動手動備份", "background_location_permission": "背景存取位置權限", "background_location_permission_content": "為了在背景執行時切換網路,Immich 必須始終具有精確位置存取權限,才能讀取 Wi-Fi 網路名稱", + "background_options": "背景選項", "backup": "備份", "backup_album_selection_page_albums_device": "裝置上的相簿({count})", "backup_album_selection_page_albums_tap": "點一下以選取,點兩下以排除", @@ -535,9 +559,11 @@ "backup_album_selection_page_select_albums": "選取相簿", "backup_album_selection_page_selection_info": "選取資訊", "backup_album_selection_page_total_assets": "總不重複媒體數", + "backup_albums_sync": "備份相簿同步", "backup_all": "全部", "backup_background_service_backup_failed_message": "備份媒體失敗。正在重試…", - "backup_background_service_connection_failed_message": "連線伺服器失敗。正在重試…", + "backup_background_service_complete_notification": "資產備份完成", + "backup_background_service_connection_failed_message": "連線至伺服器失敗。正在重試…", "backup_background_service_current_upload_notification": "正在上傳 {filename}", "backup_background_service_default_notification": "正在檢查新媒體…", "backup_background_service_error_title": "備份錯誤", @@ -554,7 +580,7 @@ "backup_controller_page_background_charging": "僅在充電時", "backup_controller_page_background_configure_error": "背景服務設定失敗", "backup_controller_page_background_delay": "新媒體備份延遲:{duration}", - "backup_controller_page_background_description": "開啟背景服務,即可在不需打開 App 的情況下,自動備份所有新媒體", + "backup_controller_page_background_description": "開啟背景服務,即可在不需開啟 App 的情況下,自動備份所有新媒體", "backup_controller_page_background_is_off": "背景自動備份已關閉", "backup_controller_page_background_is_on": "背景自動備份已開啟", "backup_controller_page_background_turn_off": "關閉背景服務", @@ -564,7 +590,7 @@ "backup_controller_page_backup_selected": "已選中: ", "backup_controller_page_backup_sub": "已備份的照片和影片", "backup_controller_page_created": "建立時間:{date}", - "backup_controller_page_desc_backup": "開啟前台備份,在打開 App 時自動將新媒體上傳至伺服器。", + "backup_controller_page_desc_backup": "開啟前臺備份,在開啟 App 時自動將新媒體上傳至伺服器。", "backup_controller_page_excluded": "已排除: ", "backup_controller_page_failed": "失敗({count})", "backup_controller_page_filename": "檔案名稱:{filename} [{size}]", @@ -575,15 +601,16 @@ "backup_controller_page_remainder_sub": "選取項目中尚未備份的照片與影片", "backup_controller_page_server_storage": "伺服器儲存空間", "backup_controller_page_start_backup": "開始備份", - "backup_controller_page_status_off": "前台自動備份已關閉", - "backup_controller_page_status_on": "前台自動備份已開啟", + "backup_controller_page_status_off": "前臺自動備份已關閉", + "backup_controller_page_status_on": "前臺自動備份已開啟", "backup_controller_page_storage_format": "{used} / {total} 已使用", "backup_controller_page_to_backup": "要備份的相簿", "backup_controller_page_total_sub": "已選取相簿中的所有不重複的照片與影片", - "backup_controller_page_turn_off": "關閉前台備份", - "backup_controller_page_turn_on": "開啟前台備份", + "backup_controller_page_turn_off": "關閉前臺備份", + "backup_controller_page_turn_on": "開啟前臺備份", "backup_controller_page_uploading_file_info": "上傳中的檔案資訊", "backup_err_only_album": "不能移除唯一的相簿", + "backup_error_sync_failed": "同步失敗,無法處理備份。", "backup_info_card_assets": "個媒體", "backup_manual_cancelled": "已取消", "backup_manual_in_progress": "上傳正在進行中,請稍後再試", @@ -591,20 +618,20 @@ "backup_manual_title": "上傳狀態", "backup_options": "備份選項", "backup_options_page_title": "備份選項", - "backup_setting_subtitle": "管理背景與前台上傳設定", + "backup_setting_subtitle": "管理背景與前臺上傳設定", "backup_settings_subtitle": "管理上傳設定", "backward": "由舊至新", "biometric_auth_enabled": "生物辨識驗證已啟用", "biometric_locked_out": "您已被鎖定無法使用生物辨識驗證", "biometric_no_options": "沒有生物辨識選項可用", - "biometric_not_available": "此設備上無法使用生物辨識驗證", + "biometric_not_available": "此裝置上無法使用生物辨識驗證", "birthdate_saved": "出生日期儲存成功", "birthdate_set_description": "出生日期用於計算此人在照片拍攝時的年齡。", "blurred_background": "背景模糊", "bugs_and_feature_requests": "錯誤及功能請求", "build": "建置編號", "build_image": "建置映像", - "bulk_delete_duplicates_confirmation": "您確定要批量刪除 {count, plural, one {# 個重複媒體} other {# 個重複媒體}} 嗎?系統將保留每組中大小最大的媒體,並永久刪除所有其他重複項目。此操作無法復原!", + "bulk_delete_duplicates_confirmation": "您確定要批次刪除 {count, plural, one {# 個重複媒體} other {# 個重複媒體}} 嗎?系統將保留每組中大小最大的媒體,並永久刪除所有其他重複項目。此操作無法復原!", "bulk_keep_duplicates_confirmation": "您確定要保留 {count, plural, one {# 個重複媒體} other {# 個重複媒體}} 嗎?這將在不刪除任何項目的情況下解決所有重複群組。", "bulk_trash_duplicates_confirmation": "您確定要批次將 {count, plural, one {# 個重複媒體} other {# 個重複媒體}}移至垃圾桶嗎?系統將保留每組中大小最大的媒體,並將所有其他重複項目移至垃圾桶。", "buy": "購買 Immich", @@ -621,7 +648,7 @@ "cache_settings_subtitle": "控制 Immich 行動應用程式的快取行為", "cache_settings_tile_subtitle": "設定本機儲存行為", "cache_settings_tile_title": "本機儲存空間", - "cache_settings_title": "緩存設定", + "cache_settings_title": "快取設定", "camera": "相機", "camera_brand": "相機品牌", "camera_model": "相機型號", @@ -632,7 +659,7 @@ "cannot_merge_people": "無法合併人物", "cannot_undo_this_action": "此操作無法復原!", "cannot_update_the_description": "無法更新描述", - "cast": "投影", + "cast": "投放", "cast_description": "設定可用的投放裝置", "change_date": "變更日期", "change_description": "變更描述", @@ -651,26 +678,28 @@ "change_pin_code": "變更 PIN 碼", "change_your_password": "變更您的密碼", "changed_visibility_successfully": "已成功變更可見性", + "charging": "充電", + "charging_requirement_mobile_backup": "後臺備份要求裝置正在充電", "check_corrupt_asset_backup": "檢查損毀的備份項目", "check_corrupt_asset_backup_button": "執行檢查", - "check_corrupt_asset_backup_description": "僅在連接 Wi-Fi 且所有媒體已完成備份後執行此檢查。此程序可能需要數分鐘。", + "check_corrupt_asset_backup_description": "僅在已連線至 Wi-Fi 且所有媒體已完成備份後執行此檢查。此程式可能需要數分鐘。", "check_logs": "檢查日誌", "choose_matching_people_to_merge": "選擇要合併的相符人物", "city": "城市", "clear": "清空", "clear_all": "全部清除", "clear_all_recent_searches": "清除所有最近的搜尋", - "clear_file_cache": "清除文件快取", + "clear_file_cache": "清除檔案快取", "clear_message": "清除訊息", "clear_value": "清除值", "client_cert_dialog_msg_confirm": "確定", "client_cert_enter_password": "輸入密碼", "client_cert_import": "匯入", - "client_cert_import_success_msg": "已匯入客戶端證書", - "client_cert_invalid_msg": "無效的證書文件或密碼錯誤", - "client_cert_remove_msg": "客戶端證書已移除", - "client_cert_subtitle": "僅支持PKCS12 (.p12, .pfx)格式。僅可在登入前進行證書的匯入和移除", - "client_cert_title": "SSL 客戶端證書", + "client_cert_import_success_msg": "已匯入用戶端憑證", + "client_cert_invalid_msg": "無效的憑證檔案或密碼錯誤", + "client_cert_remove_msg": "用戶端憑證已移除", + "client_cert_subtitle": "僅支援 PKCS12 (.p12, .pfx) 格式。僅可在登入前進行憑證的匯入和移除", + "client_cert_title": "SSL 用戶端憑證[實驗性]", "clockwise": "順時針", "close": "關閉", "collapse": "折疊", @@ -682,7 +711,6 @@ "comments_and_likes": "留言與喜歡", "comments_are_disabled": "留言已停用", "common_create_new_album": "建立新相簿", - "common_server_error": "請檢查您的網路連線,確保伺服器可連線,並確認 App 與伺服器版本相容。", "completed": "已完成", "confirm": "確認", "confirm_admin_password": "確認管理員密碼", @@ -693,10 +721,10 @@ "confirm_password": "確認密碼", "confirm_tag_face": "您想要將此臉孔標籤為 {name} 嗎?", "confirm_tag_face_unnamed": "您想標籤這張臉嗎?", - "connected_device": "已連結裝置", - "connected_to": "已連接到", - "contain": "等比置入", - "context": "內容上下文", + "connected_device": "已連線裝置", + "connected_to": "已連線至", + "contain": "等比內縮", + "context": "脈絡", "continue": "繼續", "control_bottom_app_bar_create_new_album": "建立新相簿", "control_bottom_app_bar_delete_from_immich": "從 Immich 伺服器中刪除", @@ -721,10 +749,11 @@ "create": "建立", "create_album": "建立相簿", "create_album_page_untitled": "未命名", + "create_api_key": "創建API金鑰", "create_library": "建立媒體庫", "create_link": "建立連結", "create_link_to_share": "建立共享連結", - "create_link_to_share_description": "任何持有連結的人都允許查看所選相片", + "create_link_to_share_description": "任何持有連結的人都允許檢視所選相片", "create_new": "新增", "create_new_person": "建立新人物", "create_new_person_hint": "將選定的媒體分配給新人物", @@ -737,6 +766,7 @@ "create_user": "建立使用者", "created": "建立於", "created_at": "建立於", + "creating_linked_albums": "建立連結相簿 ...", "crop": "裁剪", "curated_object_page_title": "事物", "current_device": "目前裝置", @@ -744,7 +774,7 @@ "current_server_address": "目前的伺服器位址", "custom_locale": "自訂地區設定", "custom_locale_description": "根據語言與地區格式化日期與數字", - "custom_url": "自定義 URL", + "custom_url": "自訂 URL", "daily_title_text_date": "E, MMM dd", "daily_title_text_date_year": "YYYY 年 M 月 D 日 (E)", "dark": "深色", @@ -765,7 +795,7 @@ "default_locale": "預設地區", "default_locale_description": "依照您的瀏覽器地區設定格式化日期與數字", "delete": "刪除", - "delete_action_confirmation_message": "您確定要刪除此媒體嗎?此操作會將該媒體移至伺服器的垃圾桶,並會提示您是否要在本地同時刪除", + "delete_action_confirmation_message": "您確定要刪除此媒體嗎?此操作會將該媒體移至伺服器的垃圾桶,並會提示您是否要在本機同時刪除", "delete_action_prompt": "{count} 個已刪除", "delete_album": "刪除相簿", "delete_api_key_prompt": "您確定要刪除這個 API 金鑰嗎?", @@ -778,9 +808,9 @@ "delete_duplicates_confirmation": "您確定要永久刪除這些重複項目嗎?", "delete_face": "刪除臉孔", "delete_key": "刪除金鑰", - "delete_library": "刪除圖庫", + "delete_library": "刪除相簿", "delete_link": "刪除連結", - "delete_local_action_prompt": "已在本地刪除 {count} 個項目", + "delete_local_action_prompt": "已在本機刪除 {count} 個項目", "delete_local_dialog_ok_backed_up_only": "僅刪除已備份的項目", "delete_local_dialog_ok_force": "確認刪除", "delete_others": "刪除其他", @@ -803,7 +833,7 @@ "disallow_edits": "不允許編輯", "discord": "Discord", "discover": "探索", - "discovered_devices": "已探索的設備", + "discovered_devices": "已探索的裝置", "dismiss_all_errors": "忽略所有錯誤", "dismiss_error": "忽略錯誤", "display_options": "顯示選項", @@ -834,7 +864,7 @@ "downloading": "下載中", "downloading_asset_filename": "正在下載媒體 {filename}", "downloading_media": "正在下載媒體", - "drop_files_to_upload": "將文件拖放到任何位置以上傳", + "drop_files_to_upload": "將檔案拖放到任何位置以上傳", "duplicates": "重複項目", "duplicates_description": "逐一檢查每個群組,並標示其中是否有重複媒體", "duration": "顯示時長", @@ -863,7 +893,6 @@ "edit_tag": "編輯標籤", "edit_title": "編輯標題", "edit_user": "編輯使用者", - "edited": "己編輯", "editor": "編輯器", "editor_close_without_save_prompt": "此變更將不會被儲存", "editor_close_without_save_title": "要關閉編輯器嗎?", @@ -886,7 +915,9 @@ "error": "錯誤", "error_change_sort_album": "變更相簿排序失敗", "error_delete_face": "從媒體刪除臉孔時失敗", + "error_getting_places": "取得位置時出錯", "error_loading_image": "圖片載入錯誤", + "error_loading_partners": "載入合作夥伴時出錯:{error}", "error_saving_image": "錯誤:{error}", "error_tag_face_bounding_box": "標記臉部錯誤 - 無法取得邊界框坐標", "error_title": "錯誤 - 發生錯誤", @@ -906,7 +937,7 @@ "error_deleting_shared_user": "刪除共享使用者時發生錯誤", "error_downloading": "下載 {filename} 時發生錯誤", "error_hiding_buy_button": "隱藏購買按鈕時發生錯誤", - "error_removing_assets_from_album": "從相簿移除媒體時發生錯誤,請檢查主控台以取得更多詳細資訊", + "error_removing_assets_from_album": "從相簿移除媒體時發生錯誤,請檢查主控臺以取得更多詳細資訊", "error_selecting_all_assets": "選取所有檔案時發生錯誤", "exclusion_pattern_already_exists": "此排除模式已存在。", "failed_to_create_album": "相簿建立失敗", @@ -919,22 +950,22 @@ "failed_to_load_notifications": "載入通知失敗", "failed_to_load_people": "載入人物失敗", "failed_to_remove_product_key": "移除產品金鑰失敗", - "failed_to_reset_pin_code": "重置 PIN 碼失敗", + "failed_to_reset_pin_code": "重設 PIN 碼失敗", "failed_to_stack_assets": "無法媒體堆疊", "failed_to_unstack_assets": "解除媒體堆疊失敗", "failed_to_update_notification_status": "無法更新通知狀態", "import_path_already_exists": "此匯入路徑已存在。", "incorrect_email_or_password": "電子郵件或密碼錯誤", "paths_validation_failed": "{paths, plural, one {# 個路徑} other {# 個路徑}} 驗證失敗", - "profile_picture_transparent_pixels": "個人資料圖片不能有透明像素。請放大並/或移動影像。", + "profile_picture_transparent_pixels": "個人資料圖片不能有透明畫素。請放大並/或移動影像。", "quota_higher_than_disk_size": "您所設定的配額大於磁碟大小", "something_went_wrong": "發生錯誤", "unable_to_add_album_users": "無法將使用者加入相簿", "unable_to_add_assets_to_shared_link": "無法加入媒體到共享連結", "unable_to_add_comment": "無法新增留言", - "unable_to_add_exclusion_pattern": "無法添加篩選條件", - "unable_to_add_import_path": "無法添加匯入路徑", - "unable_to_add_partners": "無法添加親朋好友", + "unable_to_add_exclusion_pattern": "無法新增篩選條件", + "unable_to_add_import_path": "無法新增匯入路徑", + "unable_to_add_partners": "無法新增親朋好友", "unable_to_add_remove_archive": "無法{archived, select, true {從封存中移除媒體} other {將檔案加入媒體}}", "unable_to_add_remove_favorites": "無法將媒體{favorite, select, true {加入收藏} other {從收藏中移除}}", "unable_to_archive_unarchive": "無法{archived, select, true {封存} other {取消封存}}", @@ -946,7 +977,7 @@ "unable_to_change_password": "無法變更密碼", "unable_to_change_visibility": "無法變更 {count, plural, one {# 位人物} other {# 位人物}} 的可見性", "unable_to_complete_oauth_login": "無法完成 OAuth 登入", - "unable_to_connect": "無法連接", + "unable_to_connect": "無法連線", "unable_to_copy_to_clipboard": "無法複製到剪貼簿,請確保您是以 https 存取本頁面", "unable_to_create_admin_account": "無法建立管理員帳號", "unable_to_create_api_key": "無法建立新的 API 金鑰", @@ -964,12 +995,12 @@ "unable_to_edit_import_path": "無法編輯匯入路徑", "unable_to_empty_trash": "無法清空垃圾桶", "unable_to_enter_fullscreen": "無法進入全螢幕", - "unable_to_exit_fullscreen": "無法退出全螢幕", + "unable_to_exit_fullscreen": "無法結束全螢幕", "unable_to_get_comments_number": "無法取得留言數量", "unable_to_get_shared_link": "取得共享連結失敗", "unable_to_hide_person": "無法隱藏人物", "unable_to_link_motion_video": "無法連結動態影片", - "unable_to_link_oauth_account": "無法連接 OAuth 帳號", + "unable_to_link_oauth_account": "無法連結 OAuth 帳號", "unable_to_log_out_all_devices": "無法登出所有裝置", "unable_to_log_out_device": "無法登出裝置", "unable_to_login_with_oauth": "無法使用 OAuth 登入", @@ -984,7 +1015,7 @@ "unable_to_remove_partner": "無法移除親朋好友", "unable_to_remove_reaction": "無法移除反應", "unable_to_reset_password": "無法重設密碼", - "unable_to_reset_pin_code": "無法重置 PIN 碼", + "unable_to_reset_pin_code": "無法重設 PIN 碼", "unable_to_resolve_duplicate": "無法解決重複項目", "unable_to_restore_assets": "無法還原媒體", "unable_to_restore_trash": "無法還原垃圾桶", @@ -1001,8 +1032,8 @@ "unable_to_set_profile_picture": "無法設定個人資料圖片", "unable_to_submit_job": "無法提交任務", "unable_to_trash_asset": "無法將媒體丟進垃圾桶", - "unable_to_unlink_account": "無法取消帳號的連接", - "unable_to_unlink_motion_video": "無法取消連接動態影片", + "unable_to_unlink_account": "無法解除帳號連結", + "unable_to_unlink_motion_video": "無法解除連結動態影片", "unable_to_update_album_cover": "無法更新相簿封面", "unable_to_update_album_info": "無法更新相簿資訊", "unable_to_update_library": "無法更新媒體庫", @@ -1012,17 +1043,18 @@ "unable_to_update_user": "無法更新使用者", "unable_to_upload_file": "無法上傳檔案" }, - "exif": "EXIF 可交換圖像文件格式", + "exif": "EXIF 可交換影像檔格式", "exif_bottom_sheet_description": "新增描述...", "exif_bottom_sheet_description_error": "更新描述時發生錯誤", "exif_bottom_sheet_details": "詳細資料", "exif_bottom_sheet_location": "位置", + "exif_bottom_sheet_no_description": "無描述", "exif_bottom_sheet_people": "人物", "exif_bottom_sheet_person_add_person": "新增姓名", - "exit_slideshow": "退出幻燈片", + "exit_slideshow": "結束幻燈片", "expand_all": "展開全部", "experimental_settings_new_asset_list_subtitle": "正在處理", - "experimental_settings_new_asset_list_title": "啟用實驗性相片格狀布局", + "experimental_settings_new_asset_list_title": "啟用實驗性相片格狀版面", "experimental_settings_subtitle": "使用風險自負!", "experimental_settings_title": "實驗性功能", "expire_after": "失效時間", @@ -1038,7 +1070,7 @@ "external": "外部", "external_libraries": "外部媒體庫", "external_network": "外部網路", - "external_network_sheet_info": "若未連接偏好的 Wi-Fi,將依列表從上到下選擇可連線的伺服器網址", + "external_network_sheet_info": "若未連線至偏好的 Wi-Fi,將依列表從上到下選擇可連線的伺服器網址", "face_unassigned": "未指定", "failed": "失敗", "failed_to_authenticate": "身份驗證失敗", @@ -1051,6 +1083,7 @@ "favorites_page_no_favorites": "未找到收藏項目", "feature_photo_updated": "特色照片已更新", "features": "功能", + "features_in_development": "發展中的特點", "features_setting_description": "管理應用程式功能", "file_name": "檔案名稱", "file_name_or_extension": "檔案名稱或副檔名", @@ -1071,13 +1104,15 @@ "gcast_enabled": "Google Cast", "gcast_enabled_description": "此功能需要從 Google 載入外部資源才能正常運作。", "general": "一般", - "geolocation_instruction_location": "點擊具有 GPS 座標的項目以使用其位置,或直接從地圖中選擇地點", - "get_help": "線上求助", - "get_wifiname_error": "無法取得 Wi-Fi 名稱。請確認您已授予必要的權限,並已連接至 Wi-Fi 網路", + "geolocation_instruction_location": "點選具有 GPS 座標的項目以使用其位置,或直接從地圖中選擇地點", + "get_help": "取得協助", + "get_wifiname_error": "無法取得 Wi-Fi 名稱。請確認您已授予必要的權限,並已連線至 Wi-Fi 網路", "getting_started": "開始使用", - "go_back": "返回", + "go_back": "上一頁", "go_to_folder": "前往資料夾", "go_to_search": "前往搜尋", + "gps": "GPS", + "gps_missing": "無 GPS", "grant_permission": "授予權限", "group_albums_by": "分類群組的方式...", "group_country": "按照國家分類", @@ -1095,8 +1130,7 @@ "header_settings_field_validator_msg": "值不可為空", "header_settings_header_name_input": "標頭名稱", "header_settings_header_value_input": "標頭值", - "headers_settings_tile_subtitle": "定義應用程式在每次網路請求時應附帶的代理標頭", - "headers_settings_tile_title": "自定義代理標頭", + "headers_settings_tile_title": "自訂代理標頭", "hi_user": "嗨!{name}({email})", "hide_all_people": "隱藏所有人物", "hide_gallery": "隱藏媒體庫", @@ -1105,21 +1139,21 @@ "hide_person": "隱藏人物", "hide_unnamed_people": "隱藏未命名的人物", "home_page_add_to_album_conflicts": "已將 {added} 個媒體新增到相簿 {album}。{failed} 個媒體已在該相簿中。", - "home_page_add_to_album_err_local": "暫時不能將本地媒體新增到相簿,已略過", + "home_page_add_to_album_err_local": "暫時不能將本機媒體新增到相簿,已略過", "home_page_add_to_album_success": "已在 {album} 相簿中新增 {added} 個媒體。", "home_page_album_err_partner": "暫時不能無法將親朋好友的媒體新增到相簿,已略過", - "home_page_archive_err_local": "暫時不能封存本地媒體,已略過", + "home_page_archive_err_local": "暫時不能封存本機媒體,已略過", "home_page_archive_err_partner": "無法封存親朋好友的媒體,已略過", "home_page_building_timeline": "正在建立時間軸", "home_page_delete_err_partner": "無法刪除親朋好友的媒體,已略過", "home_page_delete_remote_err_local": "刪除遠端媒體的選取中包含本機媒體,已略過", - "home_page_favorite_err_local": "暫不能收藏本地項目,略過", + "home_page_favorite_err_local": "暫不能收藏本機項目,略過", "home_page_favorite_err_partner": "暫無法收藏親朋好友的項目,略過", "home_page_first_time_notice": "如果這是您第一次使用本程式,請確保選擇一個要備份的相簿,以將照片與影片加入時間軸", "home_page_locked_error_local": "無法移動本機檔案至鎖定的資料夾,已略過", "home_page_locked_error_partner": "無法移動親朋好友分享的媒體至鎖定的資料夾,已略過", - "home_page_share_err_local": "無法通過連結共享本地媒體,已略過", - "home_page_upload_err_limit": "一次最多只能上傳 30 個媒體,已略過", + "home_page_share_err_local": "無法透過連結共享本機媒體,已略過", + "home_page_upload_err_limit": "一次最多隻能上傳 30 個媒體,已略過", "host": "主機", "hour": "小時", "hours": "小時", @@ -1142,7 +1176,7 @@ "image_viewer_page_state_provider_download_started": "下載已啟動", "image_viewer_page_state_provider_download_success": "下載成功", "image_viewer_page_state_provider_share_error": "分享時發生錯誤", - "immich_logo": "Immich 標誌", + "immich_logo": "Immich Logo", "immich_web_interface": "Immich 網頁介面", "import_from_json": "從 JSON 匯入", "import_path": "匯入路徑", @@ -1166,9 +1200,9 @@ "invite_to_album": "邀請至相簿", "ios_debug_info_fetch_ran_at": "抓取已於 {dateTime} 執行", "ios_debug_info_last_sync_at": "上次同步於 {dateTime}", - "ios_debug_info_no_processes_queued": "無排程中的背景程序", + "ios_debug_info_no_processes_queued": "無排程中的背景程式", "ios_debug_info_no_sync_yet": "尚未執行任何背景同步任務", - "ios_debug_info_processes_queued": "{count, plural, one {{count} 個背景程序已排程} other {{count} 個背景程序已排程}}", + "ios_debug_info_processes_queued": "{count, plural, one {{count} 個背景程式已排程} other {{count} 個背景程式已排程}}", "ios_debug_info_processing_ran_at": "於 {dateTime} 執行處理", "items_count": "{count, plural, one {# 個項目} other {# 個項目}}", "jobs": "任務", @@ -1192,7 +1226,7 @@ "lens_model": "鏡頭型號", "let_others_respond": "允許他人回覆", "level": "等級", - "library": "圖庫", + "library": "相簿", "library_options": "資料庫選項", "library_page_device_albums": "裝置上的相簿", "library_page_new_album": "新增相簿", @@ -1203,20 +1237,21 @@ "licenses": "授權", "light": "淺色", "like": "喜歡", - "like_deleted": "已刪除的收藏", - "link_motion_video": "鏈結動態影片", - "link_to_oauth": "連接 OAuth", - "linked_oauth_account": "已連接 OAuth 帳號", + "like_deleted": "已取消喜歡", + "link_motion_video": "連結動態影片", + "link_to_oauth": "連結 OAuth", + "linked_oauth_account": "已連結 OAuth 帳號", "list": "列表", "loading": "載入中", "loading_search_results_failed": "載入搜尋結果失敗", - "local": "本地", - "local_asset_cast_failed": "無法轉換未上傳至伺服器的項目", - "local_assets": "本地項目", - "local_network": "本地網路", + "local": "本機", + "local_asset_cast_failed": "無法投放未上傳至伺服器的項目", + "local_assets": "本機項目", + "local_media_summary": "本機媒體摘要", + "local_network": "本機網路", "local_network_sheet_info": "當使用指定的 Wi-Fi 網路時,應用程式將透過此網址連線至伺服器", "location_permission": "位置權限", - "location_permission_content": "使用自動切換功能,Immich 需要精確位置權限,以取得連接的 Wi-Fi 網路名稱", + "location_permission_content": "使用自動切換功能,Immich 需要精確位置權限,以取得已連線的 Wi-Fi 網路名稱", "location_picker_choose_on_map": "在地圖上選擇", "location_picker_latitude_error": "輸入有效的緯度值", "location_picker_latitude_hint": "請在此處輸入您的緯度值", @@ -1224,42 +1259,44 @@ "location_picker_longitude_hint": "請在此處輸入您的經度值", "lock": "鎖定", "locked_folder": "鎖定的資料夾", + "log_detail_title": "日誌詳細資訊", "log_out": "登出", "log_out_all_devices": "登出所有裝置", "logged_in_as": "以{user}身分登入", "logged_out_all_devices": "已登出所有裝置", "logged_out_device": "已登出裝置", "login": "登入", - "login_disabled": "已禁用登入", - "login_form_api_exception": "API 異常,請檢查伺服器地址並重試。", - "login_form_back_button_text": "後退", + "login_disabled": "已停用登入", + "login_form_api_exception": "API 發生例外,請檢查伺服器位址後再試。", + "login_form_back_button_text": "上一頁", "login_form_email_hint": "電子郵件地址", - "login_form_endpoint_hint": "http://您的伺服器地址:端口", - "login_form_endpoint_url": "伺服器鏈接地址", - "login_form_err_http": "請注明 http:// 或 https://", - "login_form_err_invalid_email": "電郵無效", - "login_form_err_invalid_url": "無效的地址", + "login_form_endpoint_hint": "http://您的伺服器位址:連接埠", + "login_form_endpoint_url": "伺服器端點 URL", + "login_form_err_http": "請註明 http:// 或 https://", + "login_form_err_invalid_email": "電子郵件地址無效", + "login_form_err_invalid_url": "無效的 URL", "login_form_err_leading_whitespace": "帶有前導空格", "login_form_err_trailing_whitespace": "帶有尾隨空格", - "login_form_failed_get_oauth_server_config": "使用 OAuth 登入時錯誤,請檢查伺服器地址", - "login_form_failed_get_oauth_server_disable": "OAuth 功能在此伺服器上不可用", - "login_form_failed_login": "登入失敗,請檢查伺服器地址、電郵和密碼", - "login_form_handshake_exception": "與伺服器通信時出現握手異常。如果您使用的是自簽名證書,請在設定中啓用自簽名證書支持。", + "login_form_failed_get_oauth_server_config": "使用 OAuth 登入時錯誤,請檢查伺服器位址", + "login_form_failed_get_oauth_server_disable": "OAuth 功能在此伺服器上無法使用", + "login_form_failed_login": "登入失敗,請檢查伺服器位址、電子郵件地址與密碼", + "login_form_handshake_exception": "與伺服器通訊時出現握手異常。若使用自簽名憑證,請在設定中啟用自簽名憑證支援。", "login_form_password_hint": "密碼", "login_form_save_login": "保持登入", - "login_form_server_empty": "輸入伺服器連結。", - "login_form_server_error": "無法連接到伺服器。", + "login_form_server_empty": "請輸入伺服器網址。", + "login_form_server_error": "無法連線至伺服器。", "login_has_been_disabled": "已停用登入功能。", "login_password_changed_error": "密碼更新失敗", "login_password_changed_success": "密碼更新成功", "logout_all_device_confirmation": "您確定要登出所有裝置嗎?", "logout_this_device_confirmation": "要登出這臺裝置嗎?", + "logs": "日誌", "longitude": "經度", "look": "樣貌", "loop_videos": "重播影片", "loop_videos_description": "啟用後,影片結束會自動重播。", "main_branch_warning": "您現在使用的是開發版本;我們強烈您建議使用正式發行版!", - "main_menu": "主頁面", + "main_menu": "主選單", "make": "製造商", "manage_geolocation": "管理位置", "manage_shared_links": "管理共享連結", @@ -1268,34 +1305,35 @@ "manage_your_account": "管理您的帳號", "manage_your_api_keys": "管理您的 API 金鑰", "manage_your_devices": "管理已登入的裝置", - "manage_your_oauth_connection": "管理您的 OAuth 連接", + "manage_your_oauth_connection": "管理您的 OAuth 連結", "map": "地圖", "map_assets_in_bounds": "{count, plural, one {# 張照片} other {# 張照片}}", "map_cannot_get_user_location": "無法取得使用者位置", "map_location_dialog_yes": "確定", "map_location_picker_page_use_location": "使用此位置", - "map_location_service_disabled_content": "需要啓用定位服務才能顯示當前位置相關的項目。要現在啓用嗎?", - "map_location_service_disabled_title": "定位服務已禁用", - "map_marker_for_images": "在 {city}、{country} 拍攝圖像的地圖標記", - "map_marker_with_image": "帶有圖像的地圖標記", - "map_no_location_permission_content": "需要位置權限才能顯示與當前位置。要現在就授予位置權限嗎?", + "map_location_service_disabled_content": "需要啟用定位服務才能顯示目前位置相關的項目。要現在啟用嗎?", + "map_location_service_disabled_title": "定位服務已停用", + "map_marker_for_images": "在 {city}、{country} 拍攝影像的地圖示記", + "map_marker_with_image": "帶有影像的地圖示記", + "map_no_location_permission_content": "需要位置權限才能顯示與目前位置。要現在就授予位置權限嗎?", "map_no_location_permission_title": "沒有位置權限", "map_settings": "地圖設定", "map_settings_dark_mode": "深色模式", - "map_settings_date_range_option_day": "過去24小時", + "map_settings_date_range_option_day": "過去 24 小時", "map_settings_date_range_option_days": "{days} 天前", - "map_settings_date_range_option_year": "1年前", + "map_settings_date_range_option_year": "1 年前", "map_settings_date_range_option_years": "{years} 年前", "map_settings_dialog_title": "地圖設定", "map_settings_include_show_archived": "包括已封存項目", "map_settings_include_show_partners": "包含親朋好友", "map_settings_only_show_favorites": "僅顯示收藏的項目", "map_settings_theme_settings": "地圖主題", - "map_zoom_to_see_photos": "縮小以查看項目", + "map_zoom_to_see_photos": "縮小以檢視項目", "mark_all_as_read": "全部標記為已讀", "mark_as_read": "標記為已讀", "marked_all_as_read": "已全部標記為已讀", "matches": "相符", + "matching_assets": "匹配資產", "media_type": "媒體類型", "memories": "回憶", "memories_all_caught_up": "已全部看完", @@ -1308,7 +1346,7 @@ "menu": "選單", "merge": "合併", "merge_people": "合併人物", - "merge_people_limit": "一次最多只能合併 5 張臉孔", + "merge_people_limit": "一次最多隻能合併 5 張臉孔", "merge_people_prompt": "您要合併這些人物嗎?此操作無法撤銷。", "merge_people_successfully": "成功合併人物", "merged_people_count": "合併了 {count, plural, one {# 位人士} other {# 位人士}}", @@ -1316,6 +1354,8 @@ "minute": "分", "minutes": "分鐘", "missing": "排入未處理", + "mobile_app": "移動應用程序", + "mobile_app_download_onboarding_note": "使用以下選項下載配套移動應用程序", "model": "型號", "month": "月", "monthly_title_text_date_format": "y MMMM", @@ -1334,18 +1374,23 @@ "my_albums": "我的相簿", "name": "名稱", "name_or_nickname": "名稱或暱稱", + "navigate": "導航", + "navigate_to_time": "導航到時間", "network_requirement_photos_upload": "使用行動網路流量備份照片", "network_requirement_videos_upload": "使用行動網路流量備份影片", - "network_requirements_updated": "網絡需求已變更,現重置備份佇列", + "network_requirements": "網路要求", + "network_requirements_updated": "網路需求已變更,現重設備份佇列", "networking_settings": "網路", "networking_subtitle": "管理伺服器端點設定", "never": "永不失效", "new_album": "新相簿", "new_api_key": "新的 API 金鑰", + "new_date_range": "新日期範圍", "new_password": "新密碼", "new_person": "新的人物", "new_pin_code": "新 PIN 碼", "new_pin_code_subtitle": "這是您第一次存取鎖定的資料夾。建立 PIN 碼以安全存取此頁面", + "new_timeline": "新時間軸", "new_user_created": "已建立新使用者", "new_version_available": "新版本已發布", "newest_first": "最新優先", @@ -1358,21 +1403,26 @@ "no_archived_assets_message": "將照片和影片封存,就不會顯示在「照片」中", "no_assets_message": "按這裡上傳您的第一張照片", "no_assets_to_show": "無項目展示", - "no_cast_devices_found": "沒有找到 Google Cast 裝置", + "no_cast_devices_found": "找不到 Google Cast 裝置", + "no_checksum_local": "沒有可用的校驗和 - 無法取得本機資產", + "no_checksum_remote": "沒有可用的校驗和 - 無法取得遠端資產", "no_duplicates_found": "沒發現重複項目。", "no_exif_info_available": "沒有可用的 Exif 資訊", "no_explore_results_message": "上傳更多照片以利探索。", "no_favorites_message": "加入收藏,加速尋找影像", - "no_libraries_message": "建立外部媒體庫以查看您的照片和影片", - "no_locked_photos_message": "鎖定的資料夾中的照片和影片會被隱藏,當您瀏覽或搜尋圖庫時不會顯示。", + "no_libraries_message": "建立外部媒體庫以檢視您的照片和影片", + "no_local_assets_found": "未找到具有此校驗和的本機資產", + "no_locked_photos_message": "鎖定的資料夾中的照片和影片會被隱藏,當您瀏覽或搜尋相簿時不會顯示。", "no_name": "無名", "no_notifications": "沒有通知", "no_people_found": "找不到符合的人物", "no_places": "沒有地點", + "no_remote_assets_found": "未找到具有此校驗和的遠端資產", "no_results": "沒有結果", "no_results_description": "試試同義詞或更通用的關鍵字吧", "no_shared_albums_message": "建立相簿分享照片和影片", "no_uploads_in_progress": "沒有正在上傳的項目", + "not_available": "不適用", "not_in_any_album": "不在任何相簿中", "not_selected": "未選擇", "note_apply_storage_label_to_previously_uploaded assets": "*註:執行套用儲存標籤前先上傳項目", @@ -1386,6 +1436,8 @@ "notifications": "通知", "notifications_setting_description": "管理通知", "oauth": "OAuth", + "obtainium_configurator": "Obtainium配寘器", + "obtainium_configurator_instructions": "使用Obtainium直接從Immich GitHub的版本安裝和更新Android應用程序。 創建一個API金鑰並選擇一個變體來創建您的Obtainium配寘連結", "official_immich_resources": "官方 Immich 資源", "offline": "離線", "offset": "移動", @@ -1394,12 +1446,12 @@ "on_this_device": "在此裝置", "onboarding": "入門指南", "onboarding_locale_description": "選擇您想要顯示的語言。設定完成之後生效。", - "onboarding_privacy_description": "以下(可選)功能依賴外部服務,可隨時在設定中停用。", + "onboarding_privacy_description": "以下(可選)功能仰賴外部服務,可隨時在設定中停用。", "onboarding_server_welcome_description": "讓我們為您的系統進行一些基本設定。", - "onboarding_theme_description": "幫實例選色彩主題。之後也可以在設定中變更。", - "onboarding_user_welcome_description": "讓我們開始吧!", + "onboarding_theme_description": "幫執行個體選色彩主題。之後也可以在設定中變更。", + "onboarding_user_welcome_description": "讓我們開始吧!", "onboarding_welcome_user": "歡迎,{user}", - "online": "在線", + "online": "線上", "only_favorites": "僅顯示己收藏", "open": "開啟", "open_in_map_view": "開啟地圖檢視", @@ -1407,7 +1459,9 @@ "open_the_search_filters": "開啟搜尋篩選器", "options": "選項", "or": "或", - "organize_your_library": "整理您的圖庫", + "organize_into_albums": "整理成相簿", + "organize_into_albums_description": "使用目前同步設定將現有照片放入相簿", + "organize_your_library": "整理您的相簿", "original": "原圖", "other": "其他", "other_devices": "其它裝置", @@ -1422,7 +1476,7 @@ "partner_list_user_photos": "{user} 的照片", "partner_list_view_all": "展示全部", "partner_page_empty_message": "您的照片尚未與任何親朋好友共享。", - "partner_page_no_more_users": "無需新增更多用戶", + "partner_page_no_more_users": "無需新增更多使用者", "partner_page_partner_add_failed": "新增親朋好友失敗", "partner_page_select_partner": "選擇親朋好友", "partner_page_shared_to_title": "共享給", @@ -1457,14 +1511,14 @@ "permanently_deleted_assets_count": "永久刪除的 {count, plural, one {# 個檔案} other {# 個檔案}}", "permission": "權限", "permission_empty": "權限不能為空", - "permission_onboarding_back": "返回", + "permission_onboarding_back": "上一頁", "permission_onboarding_continue_anyway": "確認繼續", "permission_onboarding_get_started": "開始使用", "permission_onboarding_go_to_settings": "前往設定", "permission_onboarding_permission_denied": "如要繼續,請允許 Immich 存取相片和影片權限。", "permission_onboarding_permission_granted": "已允許!一切就緒。", "permission_onboarding_permission_limited": "如要繼續,請允許 Immich 備份和管理您的相簿收藏,在設定中授予相片和影片權限。", - "permission_onboarding_request": "Immich 需要權限才能查看您的相片和短片。", + "permission_onboarding_request": "Immich 需要權限才能檢視您的相片和短片。", "person": "人物", "person_age_months": "{months, plural, one {# 個月} other {# 個月}}前", "person_age_year_months": "1 年 {months, plural, one {# 個月} other {# 個月}}前", @@ -1478,9 +1532,9 @@ "photos_from_previous_years": "往年的照片", "pick_a_location": "選擇位置", "pin_code_changed_successfully": "變更 PIN 碼成功", - "pin_code_reset_successfully": "重置 PIN 碼成功", + "pin_code_reset_successfully": "重設 PIN 碼成功", "pin_code_setup_successfully": "設定 PIN 碼成功", - "pin_verification": "PIN碼驗證", + "pin_verification": "PIN 碼驗證", "place": "地點", "places": "地點", "places_count": "{count, plural, one {{count, number} 個地點} other {{count, number} 個地點}}", @@ -1488,10 +1542,14 @@ "play_memories": "播放回憶", "play_motion_photo": "播放動態照片", "play_or_pause_video": "播放或暫停影片", + "play_original_video": "播放原始視頻", + "play_original_video_setting_description": "更喜歡播放原始視頻,而不是轉碼視頻。 如果原始資源不相容,則可能無法正確播放。", + "play_transcoded_video": "播放轉碼視頻", "please_auth_to_access": "請進行身份驗證才能存取", "port": "埠口", "preferences_settings_subtitle": "管理應用程式偏好設定", "preferences_settings_title": "偏好設定", + "preparing": "準備", "preset": "預設", "preview": "預覽", "previous": "上一張", @@ -1504,13 +1562,9 @@ "privacy": "隱私", "profile": "帳戶設定", "profile_drawer_app_logs": "日誌", - "profile_drawer_client_out_of_date_major": "客戶端有大版本升級,請盡快升級至最新版。", - "profile_drawer_client_out_of_date_minor": "客戶端有小版本升級,請盡快升級至最新版。", - "profile_drawer_client_server_up_to_date": "客戶端和服務端都是最新的", + "profile_drawer_client_server_up_to_date": "用戶端與伺服器端都是最新的", "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "唯讀模式已開啟。請長按使用者頭像圖示以退出。", - "profile_drawer_server_out_of_date_major": "服務端有大版本升級,請盡快升級至最新版。", - "profile_drawer_server_out_of_date_minor": "服務端有小版本升級,請盡快升級至最新版。", + "profile_drawer_readonly_mode": "唯讀模式已開啟。請長按使用者頭像圖示以結束。", "profile_image_of_user": "{user} 的個人資料圖片", "profile_picture_set": "已設定個人資料圖片。", "public_album": "公開相簿", @@ -1535,7 +1589,7 @@ "purchase_lifetime_description": "終身購置", "purchase_option_title": "購置選項", "purchase_panel_info_1": "開發 Immich 可不是件容易的事,花了我們不少功夫。好在有一群全職工程師在背後默默努力,為的就是把它做到最好。我們的目標很簡單:讓開放原始碼軟體和正當的商業模式能成為開發者的長期飯碗,同時打造出重視隱私的生態系統,讓大家有個不被限制的雲端服務新選擇。", - "purchase_panel_info_2": "我們承諾不設付費牆,所以購置 Immich 並不會讓您獲得額外的功能。我們是依賴使用者們的支援來開發 Immich 的。", + "purchase_panel_info_2": "我們承諾不設付費牆,所以購置 Immich 並不會讓您獲得額外的功能。我們仰賴使用者們的支援來開發 Immich。", "purchase_panel_title": "支援這項專案", "purchase_per_server": "每臺伺服器", "purchase_per_user": "每位使用者", @@ -1547,7 +1601,7 @@ "purchase_server_description_2": "擁護者狀態", "purchase_server_title": "伺服器", "purchase_settings_server_activated": "伺服器產品金鑰是由管理者管理的", - "query_asset_id": "査詢資產ID", + "query_asset_id": "査詢資產 ID", "queue_status": "處理中 {count}/{total}", "rating": "評星", "rating_clear": "清除評等", @@ -1557,6 +1611,7 @@ "read_changelog": "閱覽變更日誌", "readonly_mode_disabled": "唯讀模式已關閉", "readonly_mode_enabled": "唯讀模式已開啟", + "ready_for_upload": "已準備好上傳", "reassign": "重新指定", "reassigned_assets_to_existing_person": "已將 {count, plural, other {# 個檔案}}重新指定給{name, select, null {現有的人} other {{name}}}", "reassigned_assets_to_new_person": "已將 {count, plural, other {# 個檔案}}重新指定給一位新人物", @@ -1581,6 +1636,7 @@ "regenerating_thumbnails": "重新產生縮圖中", "remote": "遠端", "remote_assets": "遠端項目", + "remote_media_summary": "遠端媒體摘要", "remove": "移除", "remove_assets_album_confirmation": "確定要從相簿中移除 {count, plural, other {# 個檔案}}嗎?", "remove_assets_shared_link_confirmation": "確定刪除共享連結中{count, plural, other {# 個項目}}嗎?", @@ -1592,13 +1648,13 @@ "remove_from_favorites": "從收藏中移除", "remove_from_lock_folder_action_prompt": "已從鎖定的資料夾中移除了 {count} 個項目", "remove_from_locked_folder": "從鎖定的資料夾中移除", - "remove_from_locked_folder_confirmation": "您確定要將這些照片和影片移出鎖定的資料夾嗎?這些內容將會顯示在您的圖庫中。", + "remove_from_locked_folder_confirmation": "您確定要將這些照片和影片移出鎖定的資料夾嗎?這些內容將會顯示在您的相簿中。", "remove_from_shared_link": "從共享連結中移除", "remove_memory": "移除記憶", "remove_photo_from_memory": "將圖片從此記憶中移除", "remove_tag": "移除標籤", "remove_url": "移除 URL", - "remove_user": "移除用戶", + "remove_user": "移除使用者", "removed_api_key": "已移除 API 金鑰:{name}", "removed_from_archive": "從封存中移除", "removed_from_favorites": "已從收藏中移除", @@ -1617,9 +1673,9 @@ "reset": "重設", "reset_password": "重設密碼", "reset_people_visibility": "重設人物可見性", - "reset_pin_code": "重置 PIN 碼", - "reset_pin_code_description": "若忘記了PIN 碼,閣下可要求系統伺服器管理員為您重置", - "reset_pin_code_success": "閣下已成功重設PIN碼", + "reset_pin_code": "重設 PIN 碼", + "reset_pin_code_description": "若忘記了 PIN 碼,閣下可要求系統伺服器管理員為您重設", + "reset_pin_code_success": "閣下已成功重設 PIN 碼", "reset_pin_code_with_password": "您可隨時使用您的密碼來重設 PIN 碼", "reset_sqlite": "重設 SQLite 資料庫", "reset_sqlite_confirmation": "確定要重設 SQLite 資料庫嗎?閣下需登出並重新登入才能重新同步資料", @@ -1636,19 +1692,19 @@ "resume_paused_jobs": "恢復 {count, plural, one {# 暫停的任務} other {# 暫停的任務}}", "retry_upload": "重新上傳", "review_duplicates": "檢視重複項目", - "review_large_files": "檢視大型文件", + "review_large_files": "檢視大型檔案", "role": "角色", "role_editor": "編輯者", "role_viewer": "檢視者", - "running": "運行中", + "running": "執行中", "save": "儲存", - "save_to_gallery": "儲存到圖庫", + "save_to_gallery": "儲存到相簿", "saved_api_key": "已儲存 API 金鑰", "saved_profile": "已儲存個人資料", "saved_settings": "已儲存設定", "say_something": "說說您的想法吧", "scaffold_body_error_occurred": "發生錯誤", - "scan_all_libraries": "掃描所有圖庫", + "scan_all_libraries": "掃描所有相簿", "scan_library": "掃描", "scan_settings": "掃描設定", "scanning_for_album": "掃描相簿中……", @@ -1659,6 +1715,7 @@ "search_by_description_example": "在沙壩的健行之日", "search_by_filename": "以檔名或副檔名搜尋", "search_by_filename_example": "如 IMG_1234.JPG 或 PNG", + "search_camera_lens_model": "蒐索鏡頭型號...", "search_camera_make": "搜尋相機製造商…", "search_camera_model": "搜尋相機型號…", "search_city": "搜尋城市…", @@ -1687,11 +1744,11 @@ "search_page_motion_photos": "動態照片", "search_page_no_objects": "找不到物件資訊", "search_page_no_places": "找不到地點資訊", - "search_page_screenshots": "屏幕截圖", + "search_page_screenshots": "螢幕截圖", "search_page_search_photos_videos": "搜尋您的照片與影片", "search_page_selfies": "自拍", "search_page_things": "事物", - "search_page_view_all_button": "查看全部", + "search_page_view_all_button": "檢視全部", "search_page_your_activity": "您的活動", "search_page_your_map": "您的足跡", "search_people": "搜尋人物", @@ -1708,7 +1765,7 @@ "search_your_photos": "搜尋照片", "searching_locales": "搜尋區域…", "second": "秒", - "see_all_people": "查看所有人物", + "see_all_people": "檢視所有人物", "select": "選擇", "select_album_cover": "選擇相簿封面", "select_all": "選擇全部", @@ -1719,7 +1776,7 @@ "select_featured_photo": "選擇特色照片", "select_from_computer": "從電腦中選取", "select_keep_all": "全部保留", - "select_library_owner": "選擇圖庫擁有者", + "select_library_owner": "選擇相簿擁有者", "select_new_face": "選擇新臉孔", "select_person_to_tag": "選擇要標記的人物", "select_photos": "選照片", @@ -1727,29 +1784,30 @@ "select_user_for_sharing_page_err_album": "新增相簿失敗", "selected": "已選擇", "selected_count": "{count, plural, other {選了 # 項}}", - "selected_gps_coordinates": "已選擇的 GPS 座標", + "selected_gps_coordinates": "選定的 GPS 座標", "send_message": "傳訊息", "send_welcome_email": "傳送歡迎電子郵件", "server_endpoint": "伺服器端點", "server_info_box_app_version": "App 版本", - "server_info_box_server_url": "伺服器地址", + "server_info_box_server_url": "伺服器網址", "server_offline": "伺服器已離線", "server_online": "伺服器已上線", "server_privacy": "伺服器隱私", "server_stats": "伺服器統計", + "server_update_available": "服務器更新可用", "server_version": "目前版本", "set": "設定", "set_as_album_cover": "設為相簿封面", "set_as_featured_photo": "設為特色照片", "set_as_profile_picture": "設為個人資料圖片", "set_date_of_birth": "設定出生日期", - "set_profile_picture": "設置個人資料圖片", + "set_profile_picture": "設定個人資料圖片", "set_slideshow_to_fullscreen": "以全螢幕放映幻燈片", - "set_stack_primary_asset": "設置堆疊的首要項目", - "setting_image_viewer_help": "詳細資訊查看器首先載入小縮圖,然後載入中等大小的預覽圖(若啓用),最後載入原始圖片。", - "setting_image_viewer_original_subtitle": "啓用以載入原圖,禁用以減少數據使用量(包括網絡和裝置緩存)。", + "set_stack_primary_asset": "設定堆疊的首要項目", + "setting_image_viewer_help": "詳細資訊檢視器首先載入小縮圖,然後載入中等大小的預覽圖(若啟用),最後載入原始圖片。", + "setting_image_viewer_original_subtitle": "啟用以載入原圖,停用以減少資料使用量(包括網路和裝置快取)。", "setting_image_viewer_original_title": "載入原圖", - "setting_image_viewer_preview_subtitle": "啓用以載入中等質量的圖片,禁用以載入原圖或縮圖。", + "setting_image_viewer_preview_subtitle": "啟用以載入中等品質的圖片,停用以載入原圖或縮圖。", "setting_image_viewer_preview_title": "載入預覽圖", "setting_image_viewer_title": "圖片", "setting_languages_apply": "套用", @@ -1763,13 +1821,15 @@ "setting_notifications_single_progress_subtitle": "每項的詳細上傳進度資訊", "setting_notifications_single_progress_title": "顯示背景備份詳細進度", "setting_notifications_subtitle": "調整通知選項", - "setting_notifications_total_progress_subtitle": "總體上傳進度(已完成/總計)", + "setting_notifications_total_progress_subtitle": "總體上傳進度 (已完成/總計)", "setting_notifications_total_progress_title": "顯示背景備份總進度", - "setting_video_viewer_looping_title": "循環播放", + "setting_video_viewer_auto_play_subtitle": "打開視頻時自動開始播放", + "setting_video_viewer_auto_play_title": "自動播放視頻", + "setting_video_viewer_looping_title": "迴圈播放", "setting_video_viewer_original_video_subtitle": "從伺服器串流影片時,優先播放原始畫質(即使有轉檔的版本可用)。這可能會導致播放時出現緩衝情況。若影片已儲存在本機,則一律以原始畫質播放,與此設定無關。", "setting_video_viewer_original_video_title": "一律播放原始影片", "settings": "設定", - "settings_require_restart": "請重啓 Immich 以使設定生效", + "settings_require_restart": "請重啟 Immich 以使設定生效", "settings_saved": "設定已儲存", "setup_pin_code": "設定 PIN 碼", "share": "分享", @@ -1779,35 +1839,35 @@ "share_dialog_preparing": "正在準備...", "share_link": "分享連結", "shared": "共享", - "shared_album_activities_input_disable": "已禁用評論", + "shared_album_activities_input_disable": "已停用評論", "shared_album_activity_remove_content": "您確定要刪除此活動嗎?", "shared_album_activity_remove_title": "刪除活動", - "shared_album_section_people_action_error": "退出/刪除相簿失敗", - "shared_album_section_people_action_leave": "從相簿中刪除用戶", - "shared_album_section_people_action_remove_user": "從相簿中刪除用戶", + "shared_album_section_people_action_error": "結束/刪除相簿失敗", + "shared_album_section_people_action_leave": "從相簿中刪除使用者", + "shared_album_section_people_action_remove_user": "從相簿中刪除使用者", "shared_album_section_people_title": "人物", "shared_by": "共享自", "shared_by_user": "由 {user} 分享", "shared_by_you": "由您分享", "shared_from_partner": "來自 {partner} 的照片", "shared_intent_upload_button_progress_text": "{current} / {total} 已上傳", - "shared_link_app_bar_title": "共享鏈接", - "shared_link_clipboard_copied_massage": "複製到剪貼板", + "shared_link_app_bar_title": "共享連結", + "shared_link_clipboard_copied_massage": "複製到剪貼簿", "shared_link_clipboard_text": "連結: {link}\n密碼: {password}", "shared_link_create_error": "新增共享連結時發生錯誤", - "shared_link_custom_url_description": "使用自定義的 URL 連結", + "shared_link_custom_url_description": "使用自訂 URL", "shared_link_edit_description_hint": "編輯共享描述", - "shared_link_edit_expire_after_option_day": "1天", + "shared_link_edit_expire_after_option_day": "1 天", "shared_link_edit_expire_after_option_days": "{count} 天", - "shared_link_edit_expire_after_option_hour": "1小時", + "shared_link_edit_expire_after_option_hour": "1 小時", "shared_link_edit_expire_after_option_hours": "{count} 小時", - "shared_link_edit_expire_after_option_minute": "1分鐘", + "shared_link_edit_expire_after_option_minute": "1 分鐘", "shared_link_edit_expire_after_option_minutes": "{count} 分鐘", "shared_link_edit_expire_after_option_months": "{count} 個月", "shared_link_edit_expire_after_option_year": "{count} 年", "shared_link_edit_password_hint": "輸入共享密碼", "shared_link_edit_submit_button": "更新連結", - "shared_link_error_server_url_fetch": "無法取得伺服器地址", + "shared_link_error_server_url_fetch": "無法取得伺服器網址", "shared_link_expires_day": "{count} 天後過期", "shared_link_expires_days": "{count} 天後過期", "shared_link_expires_hour": "{count} 小時後過期", @@ -1819,18 +1879,18 @@ "shared_link_expires_seconds": "將在 {count} 秒後過期", "shared_link_individual_shared": "個人共享", "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "管理共享鏈接", + "shared_link_manage_links": "管理共享連結", "shared_link_options": "共享連結選項", - "shared_link_password_description": "要求在訪問此連結時提供密碼", + "shared_link_password_description": "要求在存取此連結時提供密碼", "shared_links": "共享連結", "shared_links_description": "以連結分享照片和影片", "shared_photos_and_videos_count": "{assetCount, plural, other {已分享 # 張照片及影片。}}", "shared_with_me": "與我共享", "shared_with_partner": "與 {partner} 共享", "sharing": "共享", - "sharing_enter_password": "要查看此頁面請輸入密碼。", + "sharing_enter_password": "要檢視此頁面請輸入密碼。", "sharing_page_album": "共享相簿", - "sharing_page_description": "新增共享相簿以與網絡中的人共享照片和短片。", + "sharing_page_description": "新增共享相簿以與網路中的人共享照片和短片。", "sharing_page_empty_list": "空白清單", "sharing_sidebar_description": "在側邊欄顯示共享連結", "sharing_silver_appbar_create_shared_album": "新增共享相簿", @@ -1840,7 +1900,7 @@ "show_albums": "顯示相簿", "show_all_people": "顯示所有人物", "show_and_hide_people": "顯示與隱藏人物", - "show_file_location": "顯示文件位置", + "show_file_location": "顯示檔案位置", "show_gallery": "顯示畫廊", "show_hidden_people": "顯示隱藏的人物", "show_in_timeline": "在時間軸中顯示", @@ -1856,9 +1916,10 @@ "show_slideshow_transition": "顯示幻燈片轉場", "show_supporter_badge": "擁護者徽章", "show_supporter_badge_description": "顯示擁護者徽章", + "show_text_search_menu": "顯示文字蒐索選單", "shuffle": "隨機排序", "sidebar": "側邊欄", - "sidebar_display_description": "在側邊欄中顯示鏈結", + "sidebar_display_description": "在側邊欄中顯示連結", "sign_out": "登出", "sign_up": "註冊", "size": "用量", @@ -1877,7 +1938,7 @@ "sort_recent": "最新的照片", "sort_title": "標題", "source": "來源", - "stack": "堆叠", + "stack": "堆疊", "stack_action_prompt": "已堆疊了{count} 個項目", "stack_duplicates": "堆疊重複項目", "stack_select_one_photo": "為堆疊選一張主要照片", @@ -1886,13 +1947,14 @@ "stacktrace": "堆疊追蹤", "start": "開始", "start_date": "開始日期", + "start_date_before_end_date": "開始日期必須早於結束日期", "state": "地區", "status": "狀態", - "stop_casting": "停止casting", + "stop_casting": "停止投放", "stop_motion_photo": "停止動態照片", "stop_photo_sharing": "要停止分享您的照片嗎?", - "stop_photo_sharing_description": "{partner} 將無法再訪問您的照片。", - "stop_sharing_photos_with_user": "停止與此用戶共享您的照片", + "stop_photo_sharing_description": "{partner} 將無法再存取您的照片。", + "stop_sharing_photos_with_user": "停止與此使用者共享您的照片", "storage": "儲存空間", "storage_label": "儲存標籤", "storage_quota": "儲存空間", @@ -1902,16 +1964,16 @@ "suggestions": "建議", "sunrise_on_the_beach": "日出的海灘", "support": "支援", - "support_and_feedback": "支持與回饋", - "support_third_party_description": "您安裝的 immich 是由第三方打包的。您遇到的問題可能是該軟體包造成的,所以請先使用下面的鏈結向他們提出問題。", + "support_and_feedback": "支援與回饋", + "support_third_party_description": "您安裝的 Immich 是由第三方打包的。您遇到的問題可能是該套件造成的,所以請先使用下面的連結向他們提出問題。", "swap_merge_direction": "交換合併方向", "sync": "同步", "sync_albums": "同步相簿", "sync_albums_manual_subtitle": "將所有上傳的短片和照片同步到選定的備份相簿", "sync_local": "同步本機", "sync_remote": "同步遠端", - "sync_status": "同步状态", - "sync_status_subtitle": "查看和管理同步系統", + "sync_status": "同步狀態", + "sync_status_subtitle": "檢視和管理同步系統", "sync_upload_album_setting_subtitle": "新增照片和短片並上傳到 Immich 上的選定相簿中", "tag": "標籤", "tag_assets": "標記檔案", @@ -1922,7 +1984,7 @@ "tag_updated": "已更新標籤:{tag}", "tagged_assets": "已標籤 {count, plural, one {# 個檔案} other {# 個檔案}}", "tags": "標籤", - "tap_to_run_job": "點擊以進行作業", + "tap_to_run_job": "點選以進行作業", "template": "模板", "theme": "主題", "theme_selection": "主題選項", @@ -1930,16 +1992,16 @@ "theme_setting_asset_list_storage_indicator_title": "在項目標題上顯示使用之儲存空間", "theme_setting_asset_list_tiles_per_row_title": "每行展示 {count} 項", "theme_setting_colorful_interface_subtitle": "套用主色調到背景。", - "theme_setting_colorful_interface_title": "彩色界面", - "theme_setting_image_viewer_quality_subtitle": "調整查看大圖時的圖片質量", - "theme_setting_image_viewer_quality_title": "圖片質量", + "theme_setting_colorful_interface_title": "彩色介面", + "theme_setting_image_viewer_quality_subtitle": "調整檢視大圖時的圖片品質", + "theme_setting_image_viewer_quality_title": "圖片品質", "theme_setting_primary_color_subtitle": "選擇顏色作為主色調。", "theme_setting_primary_color_title": "主色調", "theme_setting_system_primary_color_title": "使用系統顏色", "theme_setting_system_theme_switch": "自動(跟隨系統設定)", "theme_setting_theme_subtitle": "選擇套用主題", "theme_setting_three_stage_loading_subtitle": "三段式載入可能提升載入效能,但會大幅增加網路負載", - "theme_setting_three_stage_loading_title": "啓用三段式載入", + "theme_setting_three_stage_loading_title": "啟用三段式載入", "they_will_be_merged_together": "它們將會被合併在一起", "third_party_resources": "第三方資源", "time_based_memories": "依時間回憶", @@ -1951,7 +2013,7 @@ "to_login": "登入", "to_multi_select": "進行多選", "to_parent": "到上一級", - "to_select": "选择", + "to_select": "選擇", "to_trash": "垃圾桶", "toggle_settings": "切換設定", "total": "統計", @@ -1964,16 +2026,17 @@ "trash_emptied": "已清空回收桶", "trash_no_results_message": "垃圾桶中的照片和影片將顯示在這裡。", "trash_page_delete_all": "刪除全部", - "trash_page_empty_trash_dialog_content": "是否清空回收桶?這些項目將被從Immich中永久刪除", + "trash_page_empty_trash_dialog_content": "是否清空回收桶?這些項目將被從 Immich 中永久刪除", "trash_page_info": "回收桶中項目將在 {days} 天後永久刪除", "trash_page_no_assets": "暫無已刪除項目", "trash_page_restore_all": "恢復全部", "trash_page_select_assets_btn": "選擇項目", "trash_page_title": "垃圾桶 ({count})", "trashed_items_will_be_permanently_deleted_after": "垃圾桶中的項目會在 {days, plural, other {# 天}}後永久刪除。", - "troubleshoot": "疑难解答", + "troubleshoot": "疑難解答", "type": "類型", "unable_to_change_pin_code": "無法變更 PIN 碼", + "unable_to_check_version": "無法檢查應用程式或伺服器版本", "unable_to_setup_pin_code": "無法設定 PIN 碼", "unarchive": "取消封存", "unarchive_action_prompt": "已從封存的檔案中移除了 {count} 個項目", @@ -1984,11 +2047,11 @@ "unhide_person": "取消隱藏人物", "unknown": "未知", "unknown_country": "未知國家", - "unknown_year": "不知年份", + "unknown_year": "未知年份", "unlimited": "不限制", - "unlink_motion_video": "取消鏈結動態影片", - "unlink_oauth": "取消連接 OAuth", - "unlinked_oauth_account": "已解除連接 OAuth 帳號", + "unlink_motion_video": "解除連結動態影片", + "unlink_oauth": "解除連結 OAuth", + "unlinked_oauth_account": "已解除連結 OAuth 帳號", "unmute_memories": "取消靜音回憶", "unnamed_album": "未命名相簿", "unnamed_album_delete_confirmation": "確定要刪除這本相簿嗎?", @@ -1997,7 +2060,7 @@ "unselect_all": "取消全選", "unselect_all_duplicates": "取消選取所有的重複項目", "unselect_all_in": "{group} 全不選", - "unstack": "取消堆叠", + "unstack": "取消堆疊", "unstack_action_prompt": "{count} 個取消堆疊", "unstacked_assets_count": "已解除堆疊 {count, plural, other {# 個檔案}}", "untagged": "無標籤", @@ -2011,14 +2074,14 @@ "upload_details": "上傳詳細資訊", "upload_dialog_info": "是否要將所選項目備份到伺服器?", "upload_dialog_title": "上傳項目", - "upload_errors": "上傳完成,但有 {count, plural, other {# 處時發生錯誤}},要查看新上傳的檔案請重新整理頁面。", + "upload_errors": "上傳完成,但有 {count, plural, other {# 處時發生錯誤}},要檢視新上傳的檔案請重新整理頁面。", "upload_finished": "上傳完成", "upload_progress": "剩餘 {remaining, number} - 已處理 {processed, number}/{total, number}", "upload_skipped_duplicates": "已略過 {count, plural, other {# 個重複的檔案}}", "upload_status_duplicates": "重複項目", "upload_status_errors": "錯誤", "upload_status_uploaded": "已上傳", - "upload_success": "上傳成功,要查看新上傳的檔案請重新整理頁面。", + "upload_success": "上傳成功,要檢視新上傳的檔案請重新整理頁面。", "upload_to_immich": "上傳至 Immich ({count})", "uploading": "上傳中", "uploading_media": "媒體上傳中", @@ -2028,7 +2091,7 @@ "use_current_connection": "使用目前的連線", "use_custom_date_range": "改用自訂日期範圍", "user": "使用者", - "user_has_been_deleted": "此用戶已被刪除。", + "user_has_been_deleted": "此使用者已被刪除。", "user_id": "使用者 ID", "user_liked": "{user} 喜歡了 {type, select, photo {這張照片} video {這段影片} asset {這個檔案} other {它}}", "user_pin_code_settings": "PIN 碼", @@ -2039,13 +2102,13 @@ "user_role_set": "設 {user} 為{role}", "user_usage_detail": "使用者用量詳細資訊", "user_usage_stats": "帳號使用量統計", - "user_usage_stats_description": "查看帳號使用量", + "user_usage_stats_description": "檢視帳號使用量", "username": "使用者名稱", "users": "admin", "users_added_to_album_count": "已在此相簿中新增了 {count, plural, one {# 個} other {# 個}} 使用者", "utilities": "工具", "validate": "驗證", - "validate_endpoint_error": "請輸入有效的連結", + "validate_endpoint_error": "請輸入有效的 URL", "variables": "變數", "version": "版本", "version_announcement_closing": "敬祝順心,Alex", @@ -2053,24 +2116,24 @@ "version_history": "版本紀錄", "version_history_item": "{date} 安裝了 {version}", "video": "影片", - "video_hover_setting": "游標停留時播放影片縮圖", + "video_hover_setting": "遊標停留時播放影片縮圖", "video_hover_setting_description": "當滑鼠停在項目上時播放影片縮圖。即使停用,將滑鼠停在播放圖示上也可以播放。", "videos": "影片", "videos_count": "{count, plural, other {# 部影片}}", - "view": "查看", - "view_album": "查看相簿", + "view": "檢視", + "view_album": "檢視相簿", "view_all": "瀏覽全部", - "view_all_users": "查看所有使用者", + "view_all_users": "檢視所有使用者", "view_details": "檢視詳細資訊", - "view_in_timeline": "在時間軸中查看", - "view_link": "查看連結", - "view_links": "檢視鏈結", + "view_in_timeline": "在時間軸中檢視", + "view_link": "檢視連結", + "view_links": "檢視連結", "view_name": "檢視分類", - "view_next_asset": "查看下一項", - "view_previous_asset": "查看上一項", - "view_qr_code": "查看 QR code", - "view_similar_photos": "查看相似照片", - "view_stack": "查看堆疊", + "view_next_asset": "檢視下一項", + "view_previous_asset": "檢視上一項", + "view_qr_code": "檢視 QR code", + "view_similar_photos": "檢視相似照片", + "view_stack": "檢視堆疊", "view_user": "顯示使用者", "viewer_remove_from_stack": "從堆疊中移除", "viewer_stack_use_as_main_asset": "作為主項目使用", @@ -2088,5 +2151,6 @@ "yes": "是", "you_dont_have_any_shared_links": "您沒有任何共享連結", "your_wifi_name": "您的 Wi-Fi 名稱", - "zoom_image": "縮放圖片" + "zoom_image": "縮放圖片", + "zoom_to_bounds": "縮放到邊界" } diff --git a/i18n/zh_SIMPLIFIED.json b/i18n/zh_SIMPLIFIED.json index 2f8f80e15d..6d7ea6ae39 100644 --- a/i18n/zh_SIMPLIFIED.json +++ b/i18n/zh_SIMPLIFIED.json @@ -28,10 +28,12 @@ "add_to_album": "添加到相册", "add_to_album_bottom_sheet_added": "添加到相册 “{album}”", "add_to_album_bottom_sheet_already_exists": "已在相册“ {album} ” 中", + "add_to_album_bottom_sheet_some_local_assets": "某些本地资产无法添加到相册", "add_to_album_toggle": "选择相册 {album}", "add_to_albums": "添加到相册", "add_to_albums_count": "添加到相册({count}个)", "add_to_shared_album": "添加到共享相册", + "add_upload_to_stack": "上传项目至堆叠", "add_url": "添加 URL", "added_to_archive": "添加到归档", "added_to_favorites": "添加到收藏", @@ -118,7 +120,7 @@ "library_settings_description": "管理外部图库设置", "library_tasks_description": "扫描外部库,查找新增或修改的项目", "library_watching_enable_description": "监控外部图库文件变化", - "library_watching_settings": "监控图库(实验性)", + "library_watching_settings": "监控图库[实验性]", "library_watching_settings_description": "自动监控文件变化", "logging_enable_description": "启用日志记录", "logging_level_description": "启用时,要使用的日志级别。", @@ -127,7 +129,7 @@ "machine_learning_availability_checks_description": "自动检测并优先选择可用的机器学习服务器", "machine_learning_availability_checks_enabled": "启用可用性检查", "machine_learning_availability_checks_interval": "检查间隔", - "machine_learning_availability_checks_interval_description": "两次可用性检查之间的间隔(毫秒)", + "machine_learning_availability_checks_interval_description": "可用性检查之间的间隔(毫秒)", "machine_learning_availability_checks_timeout": "请求超时", "machine_learning_availability_checks_timeout_description": "用于可用性检查的超时时间(毫秒)", "machine_learning_clip_model": "CLIP 模型", @@ -152,6 +154,18 @@ "machine_learning_min_detection_score_description": "检测到人脸的最小置信分数为0-1。较低的值将检测到更多人脸,但可能导致误报。", "machine_learning_min_recognized_faces": "识别的最少人脸数", "machine_learning_min_recognized_faces_description": "创建一个人所需识别的最少人脸数量。提高这个值可以使人脸识别更精确,但也增加了人脸未能被分配到相对应人物的可能性。", + "machine_learning_ocr": "文本识别", + "machine_learning_ocr_description": "使用机器学习识别图片中的文本", + "machine_learning_ocr_enabled": "启用文本识别", + "machine_learning_ocr_enabled_description": "如果禁用,则不会对图像编码以用于文本识别。", + "machine_learning_ocr_max_resolution": "最高分辨率", + "machine_learning_ocr_max_resolution_description": "高于此分辨率的预览将调整大小,同时保持纵横比。更高的值更准确,但处理时间更长,占用更多内存。", + "machine_learning_ocr_min_detection_score": "最低检测分数", + "machine_learning_ocr_min_detection_score_description": "要检测的文本的最小置信度分数为0-1。较低的值将检测到更多的文本,但可能会导致误报。", + "machine_learning_ocr_min_recognition_score": "最低识别分数", + "machine_learning_ocr_min_score_recognition_description": "检测到的文本的最小置信度得分为0-1。较低的值将识别更多的文本,但可能会导致误报。", + "machine_learning_ocr_model": "文本识别模型", + "machine_learning_ocr_model_description": "服务器模型比移动模型更准确,但需要更长的时间来处理和使用更多的内存。", "machine_learning_settings": "机器学习设置", "machine_learning_settings_description": "管理机器学习功能和设置", "machine_learning_smart_search": "智能搜索", @@ -209,6 +223,8 @@ "notification_email_ignore_certificate_errors_description": "忽略 TLS 证书验证错误(不建议)", "notification_email_password_description": "与邮件服务器进行身份验证时使用的密码", "notification_email_port_description": "邮件服务器端口(例如 25、465 或 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "使用SMTPS(基于TLS的SMTP)", "notification_email_sent_test_email_button": "发送测试邮件并保存", "notification_email_setting_description": "发送邮件通知设置", "notification_email_test_email": "发送测试邮件", @@ -241,6 +257,7 @@ "oauth_storage_quota_default_description": "未提供声明时使用的配额(GiB)。", "oauth_timeout": "请求超时", "oauth_timeout_description": "请求超时(毫秒)", + "ocr_job_description": "使用机器学习识别图片中的文本", "password_enable_description": "使用邮箱和密码登录", "password_settings": "密码登录", "password_settings_description": "管理密码登录设置", @@ -331,7 +348,7 @@ "transcoding_max_b_frames": "最大B帧数", "transcoding_max_b_frames_description": "较高的值可以提高压缩效率,但会减慢编码速度。可能与旧设备上的硬件加速不兼容。0表示将禁用B帧,-1表示将自动设置此参数。", "transcoding_max_bitrate": "最高码率", - "transcoding_max_bitrate_description": "设置最大比特率可在对输出质量影响较小的情况下,使文件体积更为可控。720p 下,VP9 或 HEVC 普遍将其设为 2600 kbit/s,H.264 则为 4500 kbit/s。如果此项设置为 0,则不限制最大比特率。", + "transcoding_max_bitrate_description": "设置最大比特率可在对输出质量影响较小的情况下,使文件体积更为可控。720p 下,VP9 或 HEVC 普遍将其设为 2600 kbit/s,H.264 则为 4500 kbit/s。如果此项设置为 0,则不限制最大比特率。当没有指定单位时,假设k(代表kbit/s);因此,5000、5000k和5M(Mbit/s)是等效的。", "transcoding_max_keyframe_interval": "最大关键帧间隔", "transcoding_max_keyframe_interval_description": "设置关键帧之间的最大帧距离。较低的值会降低压缩效率,但可以提高搜索速度,并且可能在快速运动的场景中提高画质。0 表示将自动设置此参数。", "transcoding_optimal_description": "视频超过目标分辨率或格式不支持", @@ -349,7 +366,7 @@ "transcoding_target_resolution": "目标分辨率", "transcoding_target_resolution_description": "更高的分辨率可以保留更多细节,但编码时间更长,文件体积更大,且可能降低应用程序的响应速度。", "transcoding_temporal_aq": "时间自适应量化", - "transcoding_temporal_aq_description": "仅适用于 NVENC。提高高细节、低动态场景的质量。可能与旧设备不兼容。", + "transcoding_temporal_aq_description": "仅适用于 NVENC。时间自适应量化提高了高细节、低动态场景的质量。可能与旧设备不兼容。", "transcoding_threads": "线程数", "transcoding_threads_description": "设定值越高,编码速度越快,留给其它任务(Docker 外宿主机的任务等)的计算能力越少。此值不应大于 CPU 核心的数量。0 表示最大限度地提高利用率。", "transcoding_tone_mapping": "色调映射", @@ -395,18 +412,18 @@ "administration": "系统管理", "advanced": "高级", "advanced_settings_enable_alternate_media_filter_subtitle": "使用此选项可在同步过程中根据备用条件筛选项目。仅当您在应用程序检测所有相册均遇到问题时才尝试此功能。", - "advanced_settings_enable_alternate_media_filter_title": "[实验] 使用备用的设备相册同步筛选条件", + "advanced_settings_enable_alternate_media_filter_title": "使用备用的设备相册同步筛选条件[实验性]", "advanced_settings_log_level_title": "日志等级: {level}", "advanced_settings_prefer_remote_subtitle": "在某些设备上,从本地的项目加载缩略图的速度非常慢。启用此选项以加载远程项目。", "advanced_settings_prefer_remote_title": "优先远程项目", "advanced_settings_proxy_headers_subtitle": "定义代理标头,应用于 Immich 的每次网络请求", - "advanced_settings_proxy_headers_title": "代理标头", + "advanced_settings_proxy_headers_title": "自定义代理标头[实验性]", "advanced_settings_readonly_mode_subtitle": "启用只读模式,在该模式下只能查看照片,多选、共享、投屏、删除等操作都被禁用。从主屏幕通过用户头像启用/禁用只读", "advanced_settings_readonly_mode_title": "只读模式", "advanced_settings_self_signed_ssl_subtitle": "跳过对服务器 的 SSL 证书验证(该选项适用于使用自签名证书的服务器)。", - "advanced_settings_self_signed_ssl_title": "允许自签名 SSL 证书", + "advanced_settings_self_signed_ssl_title": "允许自签名 SSL 证书[实验性]", "advanced_settings_sync_remote_deletions_subtitle": "在网页上执行操作时,自动删除或还原该设备中的项目", - "advanced_settings_sync_remote_deletions_title": "远程同步删除 [实验]", + "advanced_settings_sync_remote_deletions_title": "远程同步删除 [实验性]", "advanced_settings_tile_subtitle": "高级用户设置", "advanced_settings_troubleshooting_subtitle": "启用用于故障排除的额外功能", "advanced_settings_troubleshooting_title": "故障排除", @@ -464,10 +481,14 @@ "api_key_description": "该应用密钥只会显示一次。请确保在关闭窗口前复制下来。", "api_key_empty": "API 密钥名称不可为空", "api_keys": "API 密钥", - "app_bar_signout_dialog_content": "确定退出登录?", + "app_architecture_variant": "变体(架构)", + "app_bar_signout_dialog_content": "您确定要退出吗?", "app_bar_signout_dialog_ok": "是", "app_bar_signout_dialog_title": "退出登录", + "app_download_links": "APP下载链接", "app_settings": "应用设置", + "app_stores": "应用商店", + "app_update_available": "应用程序更新可用", "appears_in": "出现于", "apply_count": "应用 ({count, number}个资产)", "archive": "归档", @@ -551,6 +572,7 @@ "backup_albums_sync": "备份相册同步", "backup_all": "全部", "backup_background_service_backup_failed_message": "备份失败,正在重试…", + "backup_background_service_complete_notification": "资产备份完成", "backup_background_service_connection_failed_message": "连接服务器失败,正在重试…", "backup_background_service_current_upload_notification": "正在上传 {filename}", "backup_background_service_default_notification": "正在检查新项目…", @@ -598,6 +620,7 @@ "backup_controller_page_turn_on": "开启前台备份", "backup_controller_page_uploading_file_info": "正在上传中的文件信息", "backup_err_only_album": "不能移除唯一的一个相册", + "backup_error_sync_failed": "同步失败。无法处理备份。", "backup_info_card_assets": "项", "backup_manual_cancelled": "已取消", "backup_manual_in_progress": "上传正在进行中,请稍后再试", @@ -659,6 +682,8 @@ "change_password_description": "这是您的第一次登录亦或有人要求更改您的密码。请在下面输入新密码。", "change_password_form_confirm_password": "确认密码", "change_password_form_description": "{name} 您好,\n\n这是您首次登录系统,或被管理员要求更改密码。请在下方输入新密码。", + "change_password_form_log_out": "注销所有其他设备", + "change_password_form_log_out_description": "建议退出所有其他设备", "change_password_form_new_password": "新密码", "change_password_form_password_mismatch": "密码不匹配", "change_password_form_reenter_new_password": "再次输入新密码", @@ -685,8 +710,8 @@ "client_cert_import_success_msg": "客户端证书已导入", "client_cert_invalid_msg": "无效的证书文件或密码错误", "client_cert_remove_msg": "客户端证书已移除", - "client_cert_subtitle": "仅支持 PKCS12 (.p12, .pfx) 格式。仅可在登录前进行证书的导入和移除", - "client_cert_title": "SSL 客户端证书", + "client_cert_subtitle": "仅支持PKCS12(.p12、.pfx)格式。证书导入/删除仅在登录前可用", + "client_cert_title": "SSL 客户端证书[实验性]", "clockwise": "顺时针", "close": "关闭", "collapse": "折叠", @@ -698,7 +723,6 @@ "comments_and_likes": "评论 & 点赞", "comments_are_disabled": "评论已禁用", "common_create_new_album": "新建相册", - "common_server_error": "请检查您的网络连接,确保服务器可访问且该应用程序与服务器版本兼容。", "completed": "已完成", "confirm": "确认", "confirm_admin_password": "确认管理员密码", @@ -737,6 +761,7 @@ "create": "创建", "create_album": "创建相册", "create_album_page_untitled": "未命名", + "create_api_key": "创建 API Key", "create_library": "创建图库", "create_link": "创建链接", "create_link_to_share": "创建共享链接", @@ -766,6 +791,7 @@ "daily_title_text_date_year": "YYYY年M月D日 (E)", "dark": "深色", "dark_theme": "切换深色主题", + "date": "日期", "date_after": "开始日期", "date_and_time": "日期与时间", "date_before": "结束日期", @@ -880,7 +906,6 @@ "edit_tag": "编辑标签", "edit_title": "编辑标题", "edit_user": "编辑用户", - "edited": "已编辑", "editor": "编辑器", "editor_close_without_save_prompt": "此更改不会被保存", "editor_close_without_save_title": "关闭编辑器?", @@ -1036,6 +1061,7 @@ "exif_bottom_sheet_description_error": "更新描述时出错", "exif_bottom_sheet_details": "详情", "exif_bottom_sheet_location": "位置", + "exif_bottom_sheet_no_description": "无描述", "exif_bottom_sheet_people": "人物", "exif_bottom_sheet_person_add_person": "添加姓名", "exit_slideshow": "退出幻灯片放映", @@ -1074,6 +1100,7 @@ "features_setting_description": "管理 App 功能", "file_name": "文件名", "file_name_or_extension": "文件名", + "file_size": "大小", "filename": "文件名", "filetype": "文件类型", "filter": "滤镜", @@ -1117,7 +1144,6 @@ "header_settings_field_validator_msg": "设置不可为空", "header_settings_header_name_input": "标头名称", "header_settings_header_value_input": "标头值", - "headers_settings_tile_subtitle": "定义代理标头,应用于每次网络请求", "headers_settings_tile_title": "自定义代理标头", "hi_user": "您好,{name}({email})", "hide_all_people": "隐藏所有人物", @@ -1238,6 +1264,7 @@ "local_media_summary": "本地媒体摘要", "local_network": "本地网络", "local_network_sheet_info": "当使用指定的 Wi-Fi 网络时,应用程序将通过此 URL 访问服务器", + "location": "位置", "location_permission": "定位权限", "location_permission_content": "使用自动切换功能,Immich 需要精确定位权限,以获取当前 Wi-Fi 网络名称", "location_picker_choose_on_map": "在地图上定位", @@ -1342,6 +1369,8 @@ "minute": "分", "minutes": "分钟", "missing": "缺失", + "mobile_app": "手机APP", + "mobile_app_download_onboarding_note": "下载移动应用以访问这些选项", "model": "型号", "month": "月", "monthly_title_text_date_format": "y MMMM", @@ -1360,6 +1389,8 @@ "my_albums": "我的相册", "name": "名称", "name_or_nickname": "名称或昵称", + "navigate": "导航", + "navigate_to_time": "导航至时间", "network_requirement_photos_upload": "使用蜂窝数据备份照片", "network_requirement_videos_upload": "使用蜂窝数据备份视频", "network_requirements": "网络要求", @@ -1369,11 +1400,12 @@ "never": "永不过期", "new_album": "新相册", "new_api_key": "新增 API 密钥", + "new_date_range": "新的日期范围", "new_password": "新密码", "new_person": "新人物", "new_pin_code": "新的PIN码", "new_pin_code_subtitle": "这是您第一次访问此锁定文件夹。创建一个PIN码以安全访问此页面", - "new_timeline": "新建时间轴", + "new_timeline": "切换到新版时间线", "new_user_created": "已创建新用户", "new_version_available": "有新版本发布啦", "newest_first": "最新优先", @@ -1419,6 +1451,9 @@ "notifications": "通知", "notifications_setting_description": "管理通知", "oauth": "OAuth", + "obtainium_configurator": "Obtainium配置器", + "obtainium_configurator_instructions": "使用 Obtainium 直接从 Immich GitHub 的发布区安装和更新 Android 应用程序。创建 API Key并选择一个变体来创建您的 Obtainium 配置链接", + "ocr": "文本识别", "official_immich_resources": "Immich 官方资源", "offline": "离线", "offset": "偏移量", @@ -1523,10 +1558,14 @@ "play_memories": "播放回忆", "play_motion_photo": "播放动态图片", "play_or_pause_video": "播放或暂停视频", + "play_original_video": "播放原始视频", + "play_original_video_setting_description": "更倾向于播放原始视频,而不是转码视频。如果原始资源不兼容,则可能无法正确播放。", + "play_transcoded_video": "播放转码视频", "please_auth_to_access": "请进行身份验证以访问", "port": "端口", "preferences_settings_subtitle": "管理应用的偏好设置", "preferences_settings_title": "偏好设置", + "preparing": "准备中", "preset": "预设", "preview": "预览", "previous": "上一个", @@ -1539,13 +1578,9 @@ "privacy": "隐私", "profile": "详情", "profile_drawer_app_logs": "日志", - "profile_drawer_client_out_of_date_major": "客户端有大版本升级,请尽快升级至最新版。", - "profile_drawer_client_out_of_date_minor": "客户端有小版本升级,请尽快升级至最新版。", "profile_drawer_client_server_up_to_date": "客户端和服务端都是最新的", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "只读模式已启用。长按用户头像图标退出。", - "profile_drawer_server_out_of_date_major": "服务端有大版本升级,请尽快升级至最新版。", - "profile_drawer_server_out_of_date_minor": "服务端有小版本升级,请尽快升级至最新版。", "profile_image_of_user": "{user}的个人资料图片", "profile_picture_set": "个人资料图片已设置。", "public_album": "公开相册", @@ -1592,6 +1627,7 @@ "read_changelog": "阅读更新日志", "readonly_mode_disabled": "只读模式已禁用", "readonly_mode_enabled": "只读模式已启用", + "ready_for_upload": "准备上传", "reassign": "重新指派", "reassigned_assets_to_existing_person": "重新指派{count, plural, one {#个项目} other {#个项目}}到{name, select, null {已存在的人物} other {{name}}}", "reassigned_assets_to_new_person": "重新指派{count, plural, one {#个项目} other {#个项目}}到新的人物", @@ -1661,6 +1697,7 @@ "reset_sqlite_confirmation": "您确定要重置 SQLite 数据库吗?您需要注销并重新登录才能重新同步数据", "reset_sqlite_success": "已成功重置 SQLite 数据库", "reset_to_default": "恢复默认值", + "resolution": "分辨率", "resolve_duplicates": "处理重复项", "resolved_all_duplicates": "处理所有重复项", "restore": "恢复", @@ -1679,6 +1716,7 @@ "running": "正在运行", "save": "保存", "save_to_gallery": "保存到图库", + "saved": "已保存", "saved_api_key": "已保存的 API 密钥", "saved_profile": "已保存资料", "saved_settings": "已保存设置", @@ -1693,8 +1731,11 @@ "search_by_context": "通过描述的场景查找", "search_by_description": "通过描述查找", "search_by_description_example": "在沙巴徒步的日子", - "search_by_filename": "按文件名或扩展名查找", + "search_by_filename": "通过文件名或扩展名查找", "search_by_filename_example": "如 IMG_1234.JPG 或 PNG", + "search_by_ocr": "通过文本识别查找", + "search_by_ocr_example": "拿铁咖啡", + "search_camera_lens_model": "按镜头型号查找...", "search_camera_make": "按相机品牌查找...", "search_camera_model": "按相机型号查找...", "search_city": "按城市查找...", @@ -1711,6 +1752,7 @@ "search_filter_location_title": "选择位置", "search_filter_media_type": "媒体类型", "search_filter_media_type_title": "选择媒体类型", + "search_filter_ocr": "通过文本识别搜索", "search_filter_people_title": "选择人物", "search_for": "查找", "search_for_existing_person": "查找已有人物", @@ -1773,6 +1815,7 @@ "server_online": "服务器在线", "server_privacy": "服务器隐私", "server_stats": "服务器状态", + "server_update_available": "服务器更新可用", "server_version": "服务器版本", "set": "设置", "set_as_album_cover": "设为相册封面", @@ -1801,6 +1844,8 @@ "setting_notifications_subtitle": "调整通知首选项", "setting_notifications_total_progress_subtitle": "总体上传进度(已完成/总计)", "setting_notifications_total_progress_title": "显示后台备份总进度", + "setting_video_viewer_auto_play_subtitle": "打开视频时自动开始播放", + "setting_video_viewer_auto_play_title": "自动播放视频", "setting_video_viewer_looping_title": "循环播放", "setting_video_viewer_original_video_subtitle": "从服务器流式传输视频时,即使有转码,也播放原始视频。可能会导致缓冲。本地视频则以原始质量播放,与此设置无关。", "setting_video_viewer_original_video_title": "强制播放原始视频", @@ -1896,7 +1941,7 @@ "shuffle": "随机", "sidebar": "侧边栏", "sidebar_display_description": "在侧边栏中显示链接", - "sign_out": "注销", + "sign_out": "退出登录", "sign_up": "注册", "size": "大小", "skip_to_content": "跳转到内容", @@ -1980,6 +2025,7 @@ "theme_setting_three_stage_loading_title": "启用三段式加载", "they_will_be_merged_together": "项目将会合并到一起", "third_party_resources": "第三方资源", + "time": "时间", "time_based_memories": "那年今日", "timeline": "时间线", "timezone": "时区", @@ -2012,6 +2058,7 @@ "troubleshoot": "故障排除", "type": "类型", "unable_to_change_pin_code": "无法修改PIN码", + "unable_to_check_version": "无法检查应用程序或服务器版本", "unable_to_setup_pin_code": "无法设置PIN码", "unarchive": "取消归档", "unarchive_action_prompt": "已从归档中移除 {count} 项", diff --git a/machine-learning/Dockerfile b/machine-learning/Dockerfile index e4ed643375..4b8e79b469 100644 --- a/machine-learning/Dockerfile +++ b/machine-learning/Dockerfile @@ -70,7 +70,8 @@ RUN if [ "$DEVICE" = "rocm" ]; then \ FROM python:3.11-slim-bookworm@sha256:873f91540d53b36327ed4fb018c9669107a4e2a676719720edb4209c4b15d029 AS prod-cpu -ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 +ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 \ + MACHINE_LEARNING_MODEL_ARENA=false FROM python:3.11-slim-bookworm@sha256:873f91540d53b36327ed4fb018c9669107a4e2a676719720edb4209c4b15d029 AS prod-openvino @@ -88,10 +89,12 @@ RUN apt-get update && \ FROM nvidia/cuda:12.2.2-runtime-ubuntu22.04@sha256:94c1577b2cd9dd6c0312dc04dff9cb2fdce2b268018abc3d7c2dbcacf1155000 AS prod-cuda -ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 +ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 \ + MACHINE_LEARNING_MODEL_ARENA=false RUN apt-get update && \ - apt-get install --no-install-recommends -yqq libcudnn9-cuda-12 && \ + # Pascal support was dropped in 9.11 + apt-get install --no-install-recommends -yqq libcudnn9-cuda-12=9.10.2.21-1 && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* @@ -104,7 +107,8 @@ FROM rocm/dev-ubuntu-22.04:6.4.3-complete@sha256:1f7e92ca7e3a3785680473329ed1091 FROM prod-cpu AS prod-armnn ENV LD_LIBRARY_PATH=/opt/armnn \ - LD_PRELOAD=/usr/lib/libmimalloc.so.2 + LD_PRELOAD=/usr/lib/libmimalloc.so.2 \ + MACHINE_LEARNING_MODEL_ARENA=false RUN apt-get update && apt-get install -y --no-install-recommends ocl-icd-libopencl1 mesa-opencl-icd libgomp1 && \ rm -rf /var/lib/apt/lists/* && \ @@ -127,7 +131,8 @@ FROM prod-cpu AS prod-rknn # renovate: datasource=github-tags depName=airockchip/rknn-toolkit2 ARG RKNN_TOOLKIT_VERSION="v2.3.0" -ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 +ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 \ + MACHINE_LEARNING_MODEL_ARENA=false ADD --checksum=sha256:73993ed4b440460825f21611731564503cc1d5a0c123746477da6cd574f34885 "https://github.com/airockchip/rknn-toolkit2/raw/refs/tags/${RKNN_TOOLKIT_VERSION}/rknpu2/runtime/Linux/librknn_api/aarch64/librknnrt.so" /usr/lib/ @@ -136,7 +141,7 @@ FROM prod-${DEVICE} AS prod ARG DEVICE RUN apt-get update && \ - apt-get install -y --no-install-recommends tini $(if ! [ "$DEVICE" = "openvino" ] && ! [ "$DEVICE" = "rocm" ]; then echo "libmimalloc2.0"; fi) && \ + apt-get install -y --no-install-recommends tini ccache libgl1 libglib2.0-0 libgomp1 $(if ! [ "$DEVICE" = "openvino" ] && ! [ "$DEVICE" = "rocm" ]; then echo "libmimalloc2.0"; fi) && \ apt-get autoremove -yqq && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* diff --git a/machine-learning/immich_ml/__main__.py b/machine-learning/immich_ml/__main__.py index d15b0fb321..8d575a58d5 100644 --- a/machine-learning/immich_ml/__main__.py +++ b/machine-learning/immich_ml/__main__.py @@ -1,6 +1,7 @@ import os import signal import subprocess +from ipaddress import ip_address from pathlib import Path from .config import log, non_prefixed_settings, settings @@ -12,6 +13,19 @@ else: module_dir = Path(__file__).parent + +def is_ipv6(host: str) -> bool: + try: + return ip_address(host).version == 6 + except ValueError: + return False + + +bind_host = non_prefixed_settings.immich_host +if is_ipv6(bind_host): + bind_host = f"[{bind_host}]" +bind_address = f"{bind_host}:{non_prefixed_settings.immich_port}" + try: with subprocess.Popen( [ @@ -24,7 +38,7 @@ try: "-c", module_dir / "gunicorn_conf.py", "-b", - f"{non_prefixed_settings.immich_host}:{non_prefixed_settings.immich_port}", + bind_address, "-w", str(settings.workers), "-t", diff --git a/machine-learning/immich_ml/config.py b/machine-learning/immich_ml/config.py index 939afbc98b..19fd5300df 100644 --- a/machine-learning/immich_ml/config.py +++ b/machine-learning/immich_ml/config.py @@ -13,6 +13,8 @@ from rich.logging import RichHandler from uvicorn import Server from uvicorn.workers import UvicornWorker +from .schemas import ModelPrecision + class ClipSettings(BaseModel): textual: str | None = None @@ -24,6 +26,11 @@ class FacialRecognitionSettings(BaseModel): detection: str | None = None +class OcrSettings(BaseModel): + recognition: str | None = None + detection: str | None = None + + class PreloadModelData(BaseModel): clip_fallback: str | None = os.getenv("MACHINE_LEARNING_PRELOAD__CLIP", None) facial_recognition_fallback: str | None = os.getenv("MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION", None) @@ -37,10 +44,12 @@ class PreloadModelData(BaseModel): del os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION"] clip: ClipSettings = ClipSettings() facial_recognition: FacialRecognitionSettings = FacialRecognitionSettings() + ocr: OcrSettings = OcrSettings() class MaxBatchSize(BaseModel): facial_recognition: int | None = None + text_recognition: int | None = None class Settings(BaseSettings): @@ -61,6 +70,7 @@ class Settings(BaseSettings): request_threads: int = os.cpu_count() or 4 model_inter_op_threads: int = 0 model_intra_op_threads: int = 0 + model_arena: bool = True ann: bool = True ann_fp16_turbo: bool = False ann_tuning_level: int = 2 @@ -68,6 +78,7 @@ class Settings(BaseSettings): rknn_threads: int = 1 preload: PreloadModelData | None = None max_batch_size: MaxBatchSize | None = None + openvino_precision: ModelPrecision = ModelPrecision.FP32 @property def device_id(self) -> str: diff --git a/machine-learning/immich_ml/main.py b/machine-learning/immich_ml/main.py index e884af0e69..3d34d9bf9d 100644 --- a/machine-learning/immich_ml/main.py +++ b/machine-learning/immich_ml/main.py @@ -103,6 +103,20 @@ async def preload_models(preload: PreloadModelData) -> None: ModelTask.FACIAL_RECOGNITION, ) + if preload.ocr.detection is not None: + await load_models( + preload.ocr.detection, + ModelType.DETECTION, + ModelTask.OCR, + ) + + if preload.ocr.recognition is not None: + await load_models( + preload.ocr.recognition, + ModelType.RECOGNITION, + ModelTask.OCR, + ) + if preload.clip_fallback is not None: log.warning( "Deprecated env variable: 'MACHINE_LEARNING_PRELOAD__CLIP'. " @@ -183,7 +197,9 @@ async def run_inference(payload: Image | str, entries: InferenceEntries) -> Infe response: InferenceResponse = {} async def _run_inference(entry: InferenceEntry) -> None: - model = await model_cache.get(entry["name"], entry["type"], entry["task"], ttl=settings.model_ttl) + model = await model_cache.get( + entry["name"], entry["type"], entry["task"], ttl=settings.model_ttl, **entry["options"] + ) inputs = [payload] for dep in model.depends: try: diff --git a/machine-learning/immich_ml/models/__init__.py b/machine-learning/immich_ml/models/__init__.py index d52a0b8e00..6c3a74c963 100644 --- a/machine-learning/immich_ml/models/__init__.py +++ b/machine-learning/immich_ml/models/__init__.py @@ -3,6 +3,8 @@ from typing import Any from immich_ml.models.base import InferenceModel from immich_ml.models.clip.textual import MClipTextualEncoder, OpenClipTextualEncoder from immich_ml.models.clip.visual import OpenClipVisualEncoder +from immich_ml.models.ocr.detection import TextDetector +from immich_ml.models.ocr.recognition import TextRecognizer from immich_ml.schemas import ModelSource, ModelTask, ModelType from .constants import get_model_source @@ -28,6 +30,12 @@ def get_model_class(model_name: str, model_type: ModelType, model_task: ModelTas case ModelSource.INSIGHTFACE, ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION: return FaceRecognizer + case ModelSource.PADDLE, ModelType.DETECTION, ModelTask.OCR: + return TextDetector + + case ModelSource.PADDLE, ModelType.RECOGNITION, ModelTask.OCR: + return TextRecognizer + case _: raise ValueError(f"Unknown model combination: {source}, {model_type}, {model_task}") diff --git a/machine-learning/immich_ml/models/base.py b/machine-learning/immich_ml/models/base.py index 3ee701fae0..00790ce744 100644 --- a/machine-learning/immich_ml/models/base.py +++ b/machine-learning/immich_ml/models/base.py @@ -38,9 +38,8 @@ class InferenceModel(ABC): def download(self) -> None: if not self.cached: - log.info( - f"Downloading {self.model_type.replace('-', ' ')} model '{self.model_name}'. This may take a while." - ) + model_type = self.model_type.replace("-", " ") + log.info(f"Downloading {model_type} model '{self.model_name}' to {self.model_path}. This may take a while.") self._download() def load(self) -> None: @@ -58,7 +57,7 @@ class InferenceModel(ABC): self.load() if model_kwargs: self.configure(**model_kwargs) - return self._predict(*inputs, **model_kwargs) + return self._predict(*inputs) @abstractmethod def _predict(self, *inputs: Any, **model_kwargs: Any) -> Any: ... diff --git a/machine-learning/immich_ml/models/clip/textual.py b/machine-learning/immich_ml/models/clip/textual.py index c1b3a9eba4..7e1908e120 100644 --- a/machine-learning/immich_ml/models/clip/textual.py +++ b/machine-learning/immich_ml/models/clip/textual.py @@ -19,7 +19,7 @@ class BaseCLIPTextualEncoder(InferenceModel): depends = [] identity = (ModelType.TEXTUAL, ModelTask.SEARCH) - def _predict(self, inputs: str, language: str | None = None, **kwargs: Any) -> str: + def _predict(self, inputs: str, language: str | None = None) -> str: tokens = self.tokenize(inputs, language=language) res: NDArray[np.float32] = self.session.run(None, tokens)[0][0] return serialize_np_array(res) diff --git a/machine-learning/immich_ml/models/clip/visual.py b/machine-learning/immich_ml/models/clip/visual.py index 48ae8877cf..16993f9c01 100644 --- a/machine-learning/immich_ml/models/clip/visual.py +++ b/machine-learning/immich_ml/models/clip/visual.py @@ -26,7 +26,7 @@ class BaseCLIPVisualEncoder(InferenceModel): depends = [] identity = (ModelType.VISUAL, ModelTask.SEARCH) - def _predict(self, inputs: Image.Image | bytes, **kwargs: Any) -> str: + def _predict(self, inputs: Image.Image | bytes) -> str: image = decode_pil(inputs) res: NDArray[np.float32] = self.session.run(None, self.transform(image))[0][0] return serialize_np_array(res) diff --git a/machine-learning/immich_ml/models/constants.py b/machine-learning/immich_ml/models/constants.py index 41b0990f71..db9e7cfa4d 100644 --- a/machine-learning/immich_ml/models/constants.py +++ b/machine-learning/immich_ml/models/constants.py @@ -75,10 +75,24 @@ _INSIGHTFACE_MODELS = { } +_PADDLE_MODELS = { + "PP-OCRv5_server", + "PP-OCRv5_mobile", + "CH__PP-OCRv5_server", + "CH__PP-OCRv5_mobile", + "EL__PP-OCRv5_mobile", + "EN__PP-OCRv5_mobile", + "ESLAV__PP-OCRv5_mobile", + "KOREAN__PP-OCRv5_mobile", + "LATIN__PP-OCRv5_mobile", + "TH__PP-OCRv5_mobile", +} + SUPPORTED_PROVIDERS = [ "CUDAExecutionProvider", "ROCMExecutionProvider", "OpenVINOExecutionProvider", + "CoreMLExecutionProvider", "CPUExecutionProvider", ] @@ -158,4 +172,7 @@ def get_model_source(model_name: str) -> ModelSource | None: if cleaned_name in _OPENCLIP_MODELS: return ModelSource.OPENCLIP + if cleaned_name in _PADDLE_MODELS: + return ModelSource.PADDLE + return None diff --git a/machine-learning/immich_ml/models/facial_recognition/detection.py b/machine-learning/immich_ml/models/facial_recognition/detection.py index 5e5015574c..26c9208e48 100644 --- a/machine-learning/immich_ml/models/facial_recognition/detection.py +++ b/machine-learning/immich_ml/models/facial_recognition/detection.py @@ -24,7 +24,7 @@ class FaceDetector(InferenceModel): return session - def _predict(self, inputs: NDArray[np.uint8] | bytes, **kwargs: Any) -> FaceDetectionOutput: + def _predict(self, inputs: NDArray[np.uint8] | bytes) -> FaceDetectionOutput: inputs = decode_cv2(inputs) bboxes, landmarks = self._detect(inputs) diff --git a/machine-learning/immich_ml/models/facial_recognition/recognition.py b/machine-learning/immich_ml/models/facial_recognition/recognition.py index eaf0172270..759992a600 100644 --- a/machine-learning/immich_ml/models/facial_recognition/recognition.py +++ b/machine-learning/immich_ml/models/facial_recognition/recognition.py @@ -44,7 +44,7 @@ class FaceRecognizer(InferenceModel): return session def _predict( - self, inputs: NDArray[np.uint8] | bytes | Image.Image, faces: FaceDetectionOutput, **kwargs: Any + self, inputs: NDArray[np.uint8] | bytes | Image.Image, faces: FaceDetectionOutput ) -> FacialRecognitionOutput: if faces["boxes"].shape[0] == 0: return [] diff --git a/machine-learning/immich_ml/models/ocr/detection.py b/machine-learning/immich_ml/models/ocr/detection.py new file mode 100644 index 0000000000..4101a5c6f7 --- /dev/null +++ b/machine-learning/immich_ml/models/ocr/detection.py @@ -0,0 +1,124 @@ +from typing import Any + +import cv2 +import numpy as np +from numpy.typing import NDArray +from PIL import Image +from rapidocr.ch_ppocr_det.utils import DBPostProcess +from rapidocr.inference_engine.base import FileInfo, InferSession +from rapidocr.utils.download_file import DownloadFile, DownloadFileInput +from rapidocr.utils.typings import EngineType, LangDet, OCRVersion, TaskType +from rapidocr.utils.typings import ModelType as RapidModelType + +from immich_ml.config import log +from immich_ml.models.base import InferenceModel +from immich_ml.schemas import ModelFormat, ModelSession, ModelTask, ModelType +from immich_ml.sessions.ort import OrtSession + +from .schemas import TextDetectionOutput + + +class TextDetector(InferenceModel): + depends = [] + identity = (ModelType.DETECTION, ModelTask.OCR) + + def __init__(self, model_name: str, **model_kwargs: Any) -> None: + super().__init__(model_name.split("__")[-1], **model_kwargs, model_format=ModelFormat.ONNX) + self.max_resolution = 736 + self.mean = np.array([0.5, 0.5, 0.5], dtype=np.float32) + self.std_inv = np.float32(1.0) / (np.array([0.5, 0.5, 0.5], dtype=np.float32) * 255.0) + self._empty: TextDetectionOutput = { + "boxes": np.empty(0, dtype=np.float32), + "scores": np.empty(0, dtype=np.float32), + } + self.postprocess = DBPostProcess( + thresh=0.3, + box_thresh=model_kwargs.get("minScore", 0.5), + max_candidates=1000, + unclip_ratio=1.6, + use_dilation=True, + score_mode="fast", + ) + + def _download(self) -> None: + model_info = InferSession.get_model_url( + FileInfo( + engine_type=EngineType.ONNXRUNTIME, + ocr_version=OCRVersion.PPOCRV5, + task_type=TaskType.DET, + lang_type=LangDet.CH, + model_type=RapidModelType.MOBILE if "mobile" in self.model_name else RapidModelType.SERVER, + ) + ) + download_params = DownloadFileInput( + file_url=model_info["model_dir"], + sha256=model_info["SHA256"], + save_path=self.model_path, + logger=log, + ) + DownloadFile.run(download_params) + + def _load(self) -> ModelSession: + # TODO: support other runtime sessions + return OrtSession(self.model_path) + + # partly adapted from RapidOCR + def _predict(self, inputs: Image.Image) -> TextDetectionOutput: + w, h = inputs.size + if w < 32 or h < 32: + return self._empty + out = self.session.run(None, {"x": self._transform(inputs)})[0] + boxes, scores = self.postprocess(out, (h, w)) + if len(boxes) == 0: + return self._empty + return { + "boxes": self.sorted_boxes(boxes), + "scores": np.array(scores, dtype=np.float32), + } + + # adapted from RapidOCR + def _transform(self, img: Image.Image) -> NDArray[np.float32]: + if img.height < img.width: + ratio = float(self.max_resolution) / img.height + else: + ratio = float(self.max_resolution) / img.width + + resize_h = int(img.height * ratio) + resize_w = int(img.width * ratio) + + resize_h = int(round(resize_h / 32) * 32) + resize_w = int(round(resize_w / 32) * 32) + resized_img = img.resize((int(resize_w), int(resize_h)), resample=Image.Resampling.LANCZOS) + + img_np: NDArray[np.float32] = cv2.cvtColor(np.array(resized_img, dtype=np.float32), cv2.COLOR_RGB2BGR) # type: ignore + img_np -= self.mean + img_np *= self.std_inv + img_np = np.transpose(img_np, (2, 0, 1)) + return np.expand_dims(img_np, axis=0) + + def sorted_boxes(self, dt_boxes: NDArray[np.float32]) -> NDArray[np.float32]: + if len(dt_boxes) == 0: + return dt_boxes + + # Sort by y, then identify lines, then sort by (line, x) + y_order = np.argsort(dt_boxes[:, 0, 1], kind="stable") + sorted_y = dt_boxes[y_order, 0, 1] + + line_ids = np.empty(len(dt_boxes), dtype=np.int32) + line_ids[0] = 0 + np.cumsum(np.abs(np.diff(sorted_y)) >= 10, out=line_ids[1:]) + + # Create composite sort key for final ordering + # Shift line_ids by large factor, add x for tie-breaking + sort_key = line_ids[y_order] * 1e6 + dt_boxes[y_order, 0, 0] + final_order = np.argsort(sort_key, kind="stable") + sorted_boxes: NDArray[np.float32] = dt_boxes[y_order[final_order]] + return sorted_boxes + + def configure(self, **kwargs: Any) -> None: + if (max_resolution := kwargs.get("maxResolution")) is not None: + self.max_resolution = max_resolution + if (min_score := kwargs.get("minScore")) is not None: + self.postprocess.box_thresh = min_score + if (score_mode := kwargs.get("scoreMode")) is not None: + self.postprocess.score_mode = score_mode diff --git a/machine-learning/immich_ml/models/ocr/recognition.py b/machine-learning/immich_ml/models/ocr/recognition.py new file mode 100644 index 0000000000..e968392881 --- /dev/null +++ b/machine-learning/immich_ml/models/ocr/recognition.py @@ -0,0 +1,153 @@ +from typing import Any + +import numpy as np +from numpy.typing import NDArray +from PIL import Image +from rapidocr.ch_ppocr_rec import TextRecInput +from rapidocr.ch_ppocr_rec import TextRecognizer as RapidTextRecognizer +from rapidocr.inference_engine.base import FileInfo, InferSession +from rapidocr.utils.download_file import DownloadFile, DownloadFileInput +from rapidocr.utils.typings import EngineType, LangRec, OCRVersion, TaskType +from rapidocr.utils.typings import ModelType as RapidModelType +from rapidocr.utils.vis_res import VisRes + +from immich_ml.config import log, settings +from immich_ml.models.base import InferenceModel +from immich_ml.models.transforms import pil_to_cv2 +from immich_ml.schemas import ModelFormat, ModelSession, ModelTask, ModelType +from immich_ml.sessions.ort import OrtSession + +from .schemas import OcrOptions, TextDetectionOutput, TextRecognitionOutput + + +class TextRecognizer(InferenceModel): + depends = [(ModelType.DETECTION, ModelTask.OCR)] + identity = (ModelType.RECOGNITION, ModelTask.OCR) + + def __init__(self, model_name: str, **model_kwargs: Any) -> None: + self.language = LangRec[model_name.split("__")[0]] if "__" in model_name else LangRec.CH + self.min_score = model_kwargs.get("minScore", 0.9) + self._empty: TextRecognitionOutput = { + "box": np.empty(0, dtype=np.float32), + "boxScore": np.empty(0, dtype=np.float32), + "text": [], + "textScore": np.empty(0, dtype=np.float32), + } + VisRes.__init__ = lambda self, **kwargs: None # pyright: ignore[reportAttributeAccessIssue] + super().__init__(model_name, **model_kwargs, model_format=ModelFormat.ONNX) + + def _download(self) -> None: + model_info = InferSession.get_model_url( + FileInfo( + engine_type=EngineType.ONNXRUNTIME, + ocr_version=OCRVersion.PPOCRV5, + task_type=TaskType.REC, + lang_type=self.language, + model_type=RapidModelType.MOBILE if "mobile" in self.model_name else RapidModelType.SERVER, + ) + ) + download_params = DownloadFileInput( + file_url=model_info["model_dir"], + sha256=model_info["SHA256"], + save_path=self.model_path, + logger=log, + ) + DownloadFile.run(download_params) + + def _load(self) -> ModelSession: + # TODO: support other runtimes + session = OrtSession(self.model_path) + self.model = RapidTextRecognizer( + OcrOptions( + session=session.session, + rec_batch_num=settings.max_batch_size.text_recognition if settings.max_batch_size is not None else 6, + rec_img_shape=(3, 48, 320), + lang_type=self.language, + ) + ) + return session + + def _predict(self, img: Image.Image, texts: TextDetectionOutput) -> TextRecognitionOutput: + boxes, box_scores = texts["boxes"], texts["scores"] + if boxes.shape[0] == 0: + return self._empty + rec = self.model(TextRecInput(img=self.get_crop_img_list(img, boxes))) + if rec.txts is None: + return self._empty + + boxes[:, :, 0] /= img.width + boxes[:, :, 1] /= img.height + + text_scores = np.array(rec.scores) + valid_text_score_idx = text_scores > self.min_score + valid_score_idx_list = valid_text_score_idx.tolist() + return { + "box": boxes.reshape(-1, 8)[valid_text_score_idx].reshape(-1), + "text": [rec.txts[i] for i in range(len(rec.txts)) if valid_score_idx_list[i]], + "boxScore": box_scores[valid_text_score_idx], + "textScore": text_scores[valid_text_score_idx], + } + + def get_crop_img_list(self, img: Image.Image, boxes: NDArray[np.float32]) -> list[NDArray[np.uint8]]: + img_crop_width = np.maximum( + np.linalg.norm(boxes[:, 1] - boxes[:, 0], axis=1), np.linalg.norm(boxes[:, 2] - boxes[:, 3], axis=1) + ).astype(np.int32) + img_crop_height = np.maximum( + np.linalg.norm(boxes[:, 0] - boxes[:, 3], axis=1), np.linalg.norm(boxes[:, 1] - boxes[:, 2], axis=1) + ).astype(np.int32) + pts_std = np.zeros((img_crop_width.shape[0], 4, 2), dtype=np.float32) + pts_std[:, 1:3, 0] = img_crop_width[:, None] + pts_std[:, 2:4, 1] = img_crop_height[:, None] + + img_crop_sizes = np.stack([img_crop_width, img_crop_height], axis=1) + all_coeffs = self._get_perspective_transform(pts_std, boxes) + imgs: list[NDArray[np.uint8]] = [] + for coeffs, dst_size in zip(all_coeffs, img_crop_sizes): + dst_img = img.transform( + size=tuple(dst_size), + method=Image.Transform.PERSPECTIVE, + data=tuple(coeffs), + resample=Image.Resampling.BICUBIC, + ) + + dst_width, dst_height = dst_img.size + if dst_height * 1.0 / dst_width >= 1.5: + dst_img = dst_img.rotate(90, expand=True) + imgs.append(pil_to_cv2(dst_img)) + + return imgs + + def _get_perspective_transform(self, src: NDArray[np.float32], dst: NDArray[np.float32]) -> NDArray[np.float32]: + N = src.shape[0] + x, y = src[:, :, 0], src[:, :, 1] + u, v = dst[:, :, 0], dst[:, :, 1] + A = np.zeros((N, 8, 9), dtype=np.float32) + + # Fill even rows (0, 2, 4, 6): [x, y, 1, 0, 0, 0, -u*x, -u*y, -u] + A[:, ::2, 0] = x + A[:, ::2, 1] = y + A[:, ::2, 2] = 1 + A[:, ::2, 6] = -u * x + A[:, ::2, 7] = -u * y + A[:, ::2, 8] = -u + + # Fill odd rows (1, 3, 5, 7): [0, 0, 0, x, y, 1, -v*x, -v*y, -v] + A[:, 1::2, 3] = x + A[:, 1::2, 4] = y + A[:, 1::2, 5] = 1 + A[:, 1::2, 6] = -v * x + A[:, 1::2, 7] = -v * y + A[:, 1::2, 8] = -v + + # Solve using SVD for all matrices at once + _, _, Vt = np.linalg.svd(A) + H = Vt[:, -1, :].reshape(N, 3, 3) + H = H / H[:, 2:3, 2:3] + + # Extract the 8 coefficients for each transformation + return np.column_stack( + [H[:, 0, 0], H[:, 0, 1], H[:, 0, 2], H[:, 1, 0], H[:, 1, 1], H[:, 1, 2], H[:, 2, 0], H[:, 2, 1]] + ) # pyright: ignore[reportReturnType] + + def configure(self, **kwargs: Any) -> None: + self.min_score = kwargs.get("minScore", self.min_score) diff --git a/machine-learning/immich_ml/models/ocr/schemas.py b/machine-learning/immich_ml/models/ocr/schemas.py new file mode 100644 index 0000000000..78e8619a0b --- /dev/null +++ b/machine-learning/immich_ml/models/ocr/schemas.py @@ -0,0 +1,27 @@ +from typing import Any, Iterable + +import numpy as np +import numpy.typing as npt +from rapidocr.utils.typings import EngineType, LangRec +from typing_extensions import TypedDict + + +class TextDetectionOutput(TypedDict): + boxes: npt.NDArray[np.float32] + scores: npt.NDArray[np.float32] + + +class TextRecognitionOutput(TypedDict): + box: npt.NDArray[np.float32] + boxScore: npt.NDArray[np.float32] + text: Iterable[str] + textScore: npt.NDArray[np.float32] + + +# RapidOCR expects `engine_type`, `lang_type`, and `font_path` to be attributes +class OcrOptions(dict[str, Any]): + def __init__(self, lang_type: LangRec | None = None, **options: Any) -> None: + super().__init__(**options) + self.engine_type = EngineType.ONNXRUNTIME + self.lang_type = lang_type + self.font_path = None diff --git a/machine-learning/immich_ml/schemas.py b/machine-learning/immich_ml/schemas.py index 7ad1e215d6..41706180de 100644 --- a/machine-learning/immich_ml/schemas.py +++ b/machine-learning/immich_ml/schemas.py @@ -23,6 +23,7 @@ class BoundingBox(TypedDict): class ModelTask(StrEnum): FACIAL_RECOGNITION = "facial-recognition" SEARCH = "clip" + OCR = "ocr" class ModelType(StrEnum): @@ -42,6 +43,12 @@ class ModelSource(StrEnum): INSIGHTFACE = "insightface" MCLIP = "mclip" OPENCLIP = "openclip" + PADDLE = "paddle" + + +class ModelPrecision(StrEnum): + FP16 = "FP16" + FP32 = "FP32" ModelIdentity = tuple[ModelType, ModelTask] diff --git a/machine-learning/immich_ml/sessions/ort.py b/machine-learning/immich_ml/sessions/ort.py index e7d8635876..6c52936722 100644 --- a/machine-learning/immich_ml/sessions/ort.py +++ b/machine-learning/immich_ml/sessions/ort.py @@ -14,6 +14,8 @@ from ..config import log, settings class OrtSession: + session: ort.InferenceSession + def __init__( self, model_path: Path | str, @@ -91,10 +93,20 @@ class OrtSession: case "CUDAExecutionProvider" | "ROCMExecutionProvider": options = {"arena_extend_strategy": "kSameAsRequested", "device_id": settings.device_id} case "OpenVINOExecutionProvider": + openvino_dir = self.model_path.parent / "openvino" + device = f"GPU.{settings.device_id}" options = { - "device_type": f"GPU.{settings.device_id}", - "precision": "FP32", - "cache_dir": (self.model_path.parent / "openvino").as_posix(), + "device_type": device, + "precision": settings.openvino_precision.value, + "cache_dir": openvino_dir.as_posix(), + } + case "CoreMLExecutionProvider": + options = { + "ModelFormat": "MLProgram", + "MLComputeUnits": "ALL", + "SpecializationStrategy": "FastPrediction", + "AllowLowPrecisionAccumulationOnGPU": "1", + "ModelCacheDirectory": (self.model_path.parent / "coreml").as_posix(), } case _: options = {} @@ -115,7 +127,7 @@ class OrtSession: @property def _sess_options_default(self) -> ort.SessionOptions: sess_options = ort.SessionOptions() - sess_options.enable_cpu_mem_arena = False + sess_options.enable_cpu_mem_arena = settings.model_arena # avoid thread contention between models if settings.model_inter_op_threads > 0: diff --git a/machine-learning/pyproject.toml b/machine-learning/pyproject.toml index f0f08b20b6..a93ab1c2af 100644 --- a/machine-learning/pyproject.toml +++ b/machine-learning/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "immich-ml" -version = "1.129.0" +version = "2.2.3" description = "" authors = [{ name = "Hau Tran", email = "alex.tran1502@gmail.com" }] requires-python = ">=3.10,<4.0" @@ -22,6 +22,7 @@ dependencies = [ "rich>=13.4.2", "tokenizers>=0.15.0,<1.0", "uvicorn[standard]>=0.22.0,<1.0", + "rapidocr>=3.1.0", ] [dependency-groups] diff --git a/machine-learning/scripts/healthcheck.py b/machine-learning/scripts/healthcheck.py index 82c6cad790..38c0a522f1 100644 --- a/machine-learning/scripts/healthcheck.py +++ b/machine-learning/scripts/healthcheck.py @@ -1,12 +1,22 @@ import os import sys +from ipaddress import ip_address import requests port = os.getenv("IMMICH_PORT", 3003) host = os.getenv("IMMICH_HOST", "0.0.0.0") + +def is_ipv6(host: str) -> bool: + try: + return ip_address(host).version == 6 + except ValueError: + return False + + host = "localhost" if host == "0.0.0.0" else host +host = f"[{host}]" if is_ipv6(host) else host try: response = requests.get(f"http://{host}:{port}/ping", timeout=2) diff --git a/machine-learning/test_main.py b/machine-learning/test_main.py index eeafd01062..eb8706fc19 100644 --- a/machine-learning/test_main.py +++ b/machine-learning/test_main.py @@ -26,7 +26,7 @@ from immich_ml.models.clip.textual import MClipTextualEncoder, OpenClipTextualEn from immich_ml.models.clip.visual import OpenClipVisualEncoder from immich_ml.models.facial_recognition.detection import FaceDetector from immich_ml.models.facial_recognition.recognition import FaceRecognizer -from immich_ml.schemas import ModelFormat, ModelTask, ModelType +from immich_ml.schemas import ModelFormat, ModelPrecision, ModelTask, ModelType from immich_ml.sessions.ann import AnnSession from immich_ml.sessions.ort import OrtSession from immich_ml.sessions.rknn import RknnSession, run_inference @@ -180,6 +180,7 @@ class TestOrtSession: CUDA_EP_OUT_OF_ORDER = ["CPUExecutionProvider", "CUDAExecutionProvider"] TRT_EP = ["TensorrtExecutionProvider", "CUDAExecutionProvider", "CPUExecutionProvider"] ROCM_EP = ["ROCMExecutionProvider", "CPUExecutionProvider"] + COREML_EP = ["CoreMLExecutionProvider", "CPUExecutionProvider"] @pytest.mark.providers(CPU_EP) def test_sets_cpu_provider(self, providers: list[str]) -> None: @@ -225,6 +226,12 @@ class TestOrtSession: assert session.providers == self.ROCM_EP + @pytest.mark.providers(COREML_EP) + def test_uses_coreml(self, providers: list[str]) -> None: + session = OrtSession("ViT-B-32__openai") + + assert session.providers == self.COREML_EP + def test_sets_provider_kwarg(self) -> None: providers = ["CUDAExecutionProvider"] session = OrtSession("ViT-B-32__openai", providers=providers) @@ -233,11 +240,16 @@ class TestOrtSession: @pytest.mark.ov_device_ids(["GPU.0", "CPU"]) def test_sets_default_provider_options(self, ov_device_ids: list[str]) -> None: - model_path = "/cache/ViT-B-32__openai/model.onnx" + model_path = "/cache/ViT-B-32__openai/textual/model.onnx" + session = OrtSession(model_path, providers=["OpenVINOExecutionProvider", "CPUExecutionProvider"]) assert session.provider_options == [ - {"device_type": "GPU.0", "precision": "FP32", "cache_dir": "/cache/ViT-B-32__openai/openvino"}, + { + "device_type": "GPU.0", + "precision": "FP32", + "cache_dir": "/cache/ViT-B-32__openai/textual/openvino", + }, {"arena_extend_strategy": "kSameAsRequested"}, ] @@ -255,6 +267,21 @@ class TestOrtSession: } ] + def test_sets_openvino_to_fp16_if_enabled(self, mocker: MockerFixture) -> None: + model_path = "/cache/ViT-B-32__openai/textual/model.onnx" + os.environ["MACHINE_LEARNING_DEVICE_ID"] = "1" + mocker.patch.object(settings, "openvino_precision", ModelPrecision.FP16) + + session = OrtSession(model_path, providers=["OpenVINOExecutionProvider"]) + + assert session.provider_options == [ + { + "device_type": "GPU.1", + "precision": "FP16", + "cache_dir": "/cache/ViT-B-32__openai/textual/openvino", + } + ] + def test_sets_provider_options_for_cuda(self) -> None: os.environ["MACHINE_LEARNING_DEVICE_ID"] = "1" @@ -284,7 +311,6 @@ class TestOrtSession: assert session.sess_options.execution_mode == ort.ExecutionMode.ORT_SEQUENTIAL assert session.sess_options.inter_op_num_threads == 1 assert session.sess_options.intra_op_num_threads == 2 - assert session.sess_options.enable_cpu_mem_arena is False def test_sets_default_sess_options_does_not_set_threads_if_non_cpu_and_default_threads(self) -> None: session = OrtSession("ViT-B-32__openai", providers=["CUDAExecutionProvider", "CPUExecutionProvider"]) @@ -302,6 +328,26 @@ class TestOrtSession: assert session.sess_options.inter_op_num_threads == 2 assert session.sess_options.intra_op_num_threads == 4 + def test_uses_arena_if_enabled(self, mocker: MockerFixture) -> None: + mock_settings = mocker.patch("immich_ml.sessions.ort.settings", autospec=True) + mock_settings.model_inter_op_threads = 0 + mock_settings.model_intra_op_threads = 0 + mock_settings.model_arena = True + + session = OrtSession("ViT-B-32__openai", providers=["CPUExecutionProvider"]) + + assert session.sess_options.enable_cpu_mem_arena + + def test_does_not_use_arena_if_disabled(self, mocker: MockerFixture) -> None: + mock_settings = mocker.patch("immich_ml.sessions.ort.settings", autospec=True) + mock_settings.model_inter_op_threads = 0 + mock_settings.model_intra_op_threads = 0 + mock_settings.model_arena = False + + session = OrtSession("ViT-B-32__openai", providers=["CPUExecutionProvider"]) + + assert not session.sess_options.enable_cpu_mem_arena + def test_sets_sess_options_kwarg(self) -> None: sess_options = ort.SessionOptions() session = OrtSession( @@ -391,7 +437,7 @@ class TestRknnSession: session.run(None, input_feed) rknn_session.return_value.put.assert_called_once_with([input1, input2]) - np_spy.call_count == 2 + assert np_spy.call_count == 2 np_spy.assert_has_calls([mock.call(input1), mock.call(input2)]) @@ -899,11 +945,34 @@ class TestCache: any_order=True, ) + async def test_preloads_ocr_models(self, monkeypatch: MonkeyPatch, mock_get_model: mock.Mock) -> None: + os.environ["MACHINE_LEARNING_PRELOAD__OCR__DETECTION"] = "PP-OCRv5_mobile" + os.environ["MACHINE_LEARNING_PRELOAD__OCR__RECOGNITION"] = "PP-OCRv5_mobile" + + settings = Settings() + assert settings.preload is not None + assert settings.preload.ocr.detection == "PP-OCRv5_mobile" + assert settings.preload.ocr.recognition == "PP-OCRv5_mobile" + + model_cache = ModelCache() + monkeypatch.setattr("immich_ml.main.model_cache", model_cache) + + await preload_models(settings.preload) + mock_get_model.assert_has_calls( + [ + mock.call("PP-OCRv5_mobile", ModelType.DETECTION, ModelTask.OCR), + mock.call("PP-OCRv5_mobile", ModelType.RECOGNITION, ModelTask.OCR), + ], + any_order=True, + ) + async def test_preloads_all_models(self, monkeypatch: MonkeyPatch, mock_get_model: mock.Mock) -> None: os.environ["MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL"] = "ViT-B-32__openai" os.environ["MACHINE_LEARNING_PRELOAD__CLIP__VISUAL"] = "ViT-B-32__openai" os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION"] = "buffalo_s" os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION"] = "buffalo_s" + os.environ["MACHINE_LEARNING_PRELOAD__OCR__DETECTION"] = "PP-OCRv5_mobile" + os.environ["MACHINE_LEARNING_PRELOAD__OCR__RECOGNITION"] = "PP-OCRv5_mobile" settings = Settings() assert settings.preload is not None @@ -911,6 +980,8 @@ class TestCache: assert settings.preload.clip.textual == "ViT-B-32__openai" assert settings.preload.facial_recognition.recognition == "buffalo_s" assert settings.preload.facial_recognition.detection == "buffalo_s" + assert settings.preload.ocr.detection == "PP-OCRv5_mobile" + assert settings.preload.ocr.recognition == "PP-OCRv5_mobile" model_cache = ModelCache() monkeypatch.setattr("immich_ml.main.model_cache", model_cache) @@ -922,6 +993,8 @@ class TestCache: mock.call("ViT-B-32__openai", ModelType.VISUAL, ModelTask.SEARCH), mock.call("buffalo_s", ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION), mock.call("buffalo_s", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION), + mock.call("PP-OCRv5_mobile", ModelType.DETECTION, ModelTask.OCR), + mock.call("PP-OCRv5_mobile", ModelType.RECOGNITION, ModelTask.OCR), ], any_order=True, ) diff --git a/machine-learning/uv.lock b/machine-learning/uv.lock index c30120a40b..919a17d45e 100644 --- a/machine-learning/uv.lock +++ b/machine-learning/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 1 requires-python = ">=3.10, <4.0" resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'darwin'", @@ -23,9 +23,9 @@ resolution-markers = [ name = "aiocache" version = "0.12.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7a/64/b945b8025a9d1e6e2138845f4022165d3b337f55f50984fbc6a4c0a1e355/aiocache-0.12.3.tar.gz", hash = "sha256:f528b27bf4d436b497a1d0d1a8f59a542c153ab1e37c3621713cb376d44c4713", size = 132196, upload-time = "2024-09-25T13:20:23.823Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7a/64/b945b8025a9d1e6e2138845f4022165d3b337f55f50984fbc6a4c0a1e355/aiocache-0.12.3.tar.gz", hash = "sha256:f528b27bf4d436b497a1d0d1a8f59a542c153ab1e37c3621713cb376d44c4713", size = 132196 } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/d7/15d67e05b235d1ed8c3ce61688fe4d84130e72af1657acadfaac3479f4cf/aiocache-0.12.3-py2.py3-none-any.whl", hash = "sha256:889086fc24710f431937b87ad3720a289f7fc31c4fd8b68e9f918b9bacd8270d", size = 28199, upload-time = "2024-09-25T13:20:22.688Z" }, + { url = "https://files.pythonhosted.org/packages/37/d7/15d67e05b235d1ed8c3ce61688fe4d84130e72af1657acadfaac3479f4cf/aiocache-0.12.3-py2.py3-none-any.whl", hash = "sha256:889086fc24710f431937b87ad3720a289f7fc31c4fd8b68e9f918b9bacd8270d", size = 28199 }, ] [[package]] @@ -41,20 +41,26 @@ dependencies = [ { name = "scipy", version = "1.11.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "scipy", version = "1.16.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/14/d6/8dd5b690d28a332a0b2c3179a345808b5d4c7ad5ddc079b7e116098dff35/albumentations-1.3.1.tar.gz", hash = "sha256:a6a38388fe546c568071e8c82f414498e86c9ed03c08b58e7a88b31cf7a244c6", size = 176371, upload-time = "2023-06-10T07:44:32.36Z" } +sdist = { url = "https://files.pythonhosted.org/packages/14/d6/8dd5b690d28a332a0b2c3179a345808b5d4c7ad5ddc079b7e116098dff35/albumentations-1.3.1.tar.gz", hash = "sha256:a6a38388fe546c568071e8c82f414498e86c9ed03c08b58e7a88b31cf7a244c6", size = 176371 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/f6/c486cedb4f75147232f32ec4c97026714cfef7c7e247a1f0427bc5489f66/albumentations-1.3.1-py3-none-any.whl", hash = "sha256:6b641d13733181d9ecdc29550e6ad580d1bfa9d25e2213a66940062f25e291bd", size = 125706, upload-time = "2023-06-10T07:44:30.373Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f6/c486cedb4f75147232f32ec4c97026714cfef7c7e247a1f0427bc5489f66/albumentations-1.3.1-py3-none-any.whl", hash = "sha256:6b641d13733181d9ecdc29550e6ad580d1bfa9d25e2213a66940062f25e291bd", size = 125706 }, ] [[package]] name = "annotated-types" version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, ] +[[package]] +name = "antlr4-python3-runtime" +version = "4.9.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/38/7859ff46355f76f8d19459005ca000b6e7012f2f1ca597746cbcd1fbfe5e/antlr4-python3-runtime-4.9.3.tar.gz", hash = "sha256:f224469b4168294902bb1efa80a8bf7855f24c99aef99cbefc1bcd3cce77881b", size = 117034 } + [[package]] name = "anyio" version = "4.2.0" @@ -65,27 +71,27 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2d/b8/7333d87d5f03247215d86a86362fd3e324111788c6cdd8d2e6196a6ba833/anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f", size = 158770, upload-time = "2023-12-16T17:06:57.709Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/b8/7333d87d5f03247215d86a86362fd3e324111788c6cdd8d2e6196a6ba833/anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f", size = 158770 } wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/cd/d6d9bb1dadf73e7af02d18225cbd2c93f8552e13130484f1c8dcfece292b/anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee", size = 85481, upload-time = "2023-12-16T17:06:55.989Z" }, + { url = "https://files.pythonhosted.org/packages/bf/cd/d6d9bb1dadf73e7af02d18225cbd2c93f8552e13130484f1c8dcfece292b/anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee", size = 85481 }, ] [[package]] name = "backports-asyncio-runner" version = "1.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, + { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313 }, ] [[package]] name = "bidict" version = "0.23.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9a/6e/026678aa5a830e07cd9498a05d3e7e650a4f56a42f267a53d22bcda1bdc9/bidict-0.23.1.tar.gz", hash = "sha256:03069d763bc387bbd20e7d49914e75fc4132a41937fa3405417e1a5a2d006d71", size = 29093, upload-time = "2024-02-18T19:09:05.748Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/6e/026678aa5a830e07cd9498a05d3e7e650a4f56a42f267a53d22bcda1bdc9/bidict-0.23.1.tar.gz", hash = "sha256:03069d763bc387bbd20e7d49914e75fc4132a41937fa3405417e1a5a2d006d71", size = 29093 } wheels = [ - { url = "https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5", size = 32764, upload-time = "2024-02-18T19:09:04.156Z" }, + { url = "https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5", size = 32764 }, ] [[package]] @@ -101,113 +107,113 @@ dependencies = [ { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449, upload-time = "2025-01-29T04:15:40.373Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/3b/4ba3f93ac8d90410423fdd31d7541ada9bcee1df32fb90d26de41ed40e1d/black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32", size = 1629419, upload-time = "2025-01-29T05:37:06.642Z" }, - { url = "https://files.pythonhosted.org/packages/b4/02/0bde0485146a8a5e694daed47561785e8b77a0466ccc1f3e485d5ef2925e/black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da", size = 1461080, upload-time = "2025-01-29T05:37:09.321Z" }, - { url = "https://files.pythonhosted.org/packages/52/0e/abdf75183c830eaca7589144ff96d49bce73d7ec6ad12ef62185cc0f79a2/black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7", size = 1766886, upload-time = "2025-01-29T04:18:24.432Z" }, - { url = "https://files.pythonhosted.org/packages/dc/a6/97d8bb65b1d8a41f8a6736222ba0a334db7b7b77b8023ab4568288f23973/black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9", size = 1419404, upload-time = "2025-01-29T04:19:04.296Z" }, - { url = "https://files.pythonhosted.org/packages/7e/4f/87f596aca05c3ce5b94b8663dbfe242a12843caaa82dd3f85f1ffdc3f177/black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", size = 1614372, upload-time = "2025-01-29T05:37:11.71Z" }, - { url = "https://files.pythonhosted.org/packages/e7/d0/2c34c36190b741c59c901e56ab7f6e54dad8df05a6272a9747ecef7c6036/black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", size = 1442865, upload-time = "2025-01-29T05:37:14.309Z" }, - { url = "https://files.pythonhosted.org/packages/21/d4/7518c72262468430ead45cf22bd86c883a6448b9eb43672765d69a8f1248/black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", size = 1749699, upload-time = "2025-01-29T04:18:17.688Z" }, - { url = "https://files.pythonhosted.org/packages/58/db/4f5beb989b547f79096e035c4981ceb36ac2b552d0ac5f2620e941501c99/black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", size = 1428028, upload-time = "2025-01-29T04:18:51.711Z" }, - { url = "https://files.pythonhosted.org/packages/83/71/3fe4741df7adf015ad8dfa082dd36c94ca86bb21f25608eb247b4afb15b2/black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", size = 1650988, upload-time = "2025-01-29T05:37:16.707Z" }, - { url = "https://files.pythonhosted.org/packages/13/f3/89aac8a83d73937ccd39bbe8fc6ac8860c11cfa0af5b1c96d081facac844/black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", size = 1453985, upload-time = "2025-01-29T05:37:18.273Z" }, - { url = "https://files.pythonhosted.org/packages/6f/22/b99efca33f1f3a1d2552c714b1e1b5ae92efac6c43e790ad539a163d1754/black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", size = 1783816, upload-time = "2025-01-29T04:18:33.823Z" }, - { url = "https://files.pythonhosted.org/packages/18/7e/a27c3ad3822b6f2e0e00d63d58ff6299a99a5b3aee69fa77cd4b0076b261/black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", size = 1440860, upload-time = "2025-01-29T04:19:12.944Z" }, - { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673, upload-time = "2025-01-29T05:37:20.574Z" }, - { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190, upload-time = "2025-01-29T05:37:22.106Z" }, - { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926, upload-time = "2025-01-29T04:18:58.564Z" }, - { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613, upload-time = "2025-01-29T04:19:27.63Z" }, - { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646, upload-time = "2025-01-29T04:15:38.082Z" }, + { url = "https://files.pythonhosted.org/packages/4d/3b/4ba3f93ac8d90410423fdd31d7541ada9bcee1df32fb90d26de41ed40e1d/black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32", size = 1629419 }, + { url = "https://files.pythonhosted.org/packages/b4/02/0bde0485146a8a5e694daed47561785e8b77a0466ccc1f3e485d5ef2925e/black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da", size = 1461080 }, + { url = "https://files.pythonhosted.org/packages/52/0e/abdf75183c830eaca7589144ff96d49bce73d7ec6ad12ef62185cc0f79a2/black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7", size = 1766886 }, + { url = "https://files.pythonhosted.org/packages/dc/a6/97d8bb65b1d8a41f8a6736222ba0a334db7b7b77b8023ab4568288f23973/black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9", size = 1419404 }, + { url = "https://files.pythonhosted.org/packages/7e/4f/87f596aca05c3ce5b94b8663dbfe242a12843caaa82dd3f85f1ffdc3f177/black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", size = 1614372 }, + { url = "https://files.pythonhosted.org/packages/e7/d0/2c34c36190b741c59c901e56ab7f6e54dad8df05a6272a9747ecef7c6036/black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", size = 1442865 }, + { url = "https://files.pythonhosted.org/packages/21/d4/7518c72262468430ead45cf22bd86c883a6448b9eb43672765d69a8f1248/black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", size = 1749699 }, + { url = "https://files.pythonhosted.org/packages/58/db/4f5beb989b547f79096e035c4981ceb36ac2b552d0ac5f2620e941501c99/black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", size = 1428028 }, + { url = "https://files.pythonhosted.org/packages/83/71/3fe4741df7adf015ad8dfa082dd36c94ca86bb21f25608eb247b4afb15b2/black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", size = 1650988 }, + { url = "https://files.pythonhosted.org/packages/13/f3/89aac8a83d73937ccd39bbe8fc6ac8860c11cfa0af5b1c96d081facac844/black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", size = 1453985 }, + { url = "https://files.pythonhosted.org/packages/6f/22/b99efca33f1f3a1d2552c714b1e1b5ae92efac6c43e790ad539a163d1754/black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", size = 1783816 }, + { url = "https://files.pythonhosted.org/packages/18/7e/a27c3ad3822b6f2e0e00d63d58ff6299a99a5b3aee69fa77cd4b0076b261/black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", size = 1440860 }, + { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673 }, + { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190 }, + { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926 }, + { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613 }, + { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646 }, ] [[package]] name = "blinker" version = "1.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/13/6df5fc090ff4e5d246baf1f45fe9e5623aa8565757dfa5bd243f6a545f9e/blinker-1.7.0.tar.gz", hash = "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182", size = 28134, upload-time = "2023-11-01T22:06:01.588Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/13/6df5fc090ff4e5d246baf1f45fe9e5623aa8565757dfa5bd243f6a545f9e/blinker-1.7.0.tar.gz", hash = "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182", size = 28134 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/2a/7f3714cbc6356a0efec525ce7a0613d581072ed6eb53eb7b9754f33db807/blinker-1.7.0-py3-none-any.whl", hash = "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9", size = 13068, upload-time = "2023-11-01T22:06:00.162Z" }, + { url = "https://files.pythonhosted.org/packages/fa/2a/7f3714cbc6356a0efec525ce7a0613d581072ed6eb53eb7b9754f33db807/blinker-1.7.0-py3-none-any.whl", hash = "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9", size = 13068 }, ] [[package]] name = "brotli" version = "1.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2f/c2/f9e977608bdf958650638c3f1e28f85a1b075f075ebbe77db8555463787b/Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724", size = 7372270, upload-time = "2023-09-07T14:05:41.643Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/c2/f9e977608bdf958650638c3f1e28f85a1b075f075ebbe77db8555463787b/Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724", size = 7372270 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/3a/dbf4fb970c1019a57b5e492e1e0eae745d32e59ba4d6161ab5422b08eefe/Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752", size = 873045, upload-time = "2023-09-07T14:03:16.894Z" }, - { url = "https://files.pythonhosted.org/packages/dd/11/afc14026ea7f44bd6eb9316d800d439d092c8d508752055ce8d03086079a/Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9", size = 446218, upload-time = "2023-09-07T14:03:18.917Z" }, - { url = "https://files.pythonhosted.org/packages/36/83/7545a6e7729db43cb36c4287ae388d6885c85a86dd251768a47015dfde32/Brotli-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3", size = 2903872, upload-time = "2023-09-07T14:03:20.398Z" }, - { url = "https://files.pythonhosted.org/packages/32/23/35331c4d9391fcc0f29fd9bec2c76e4b4eeab769afbc4b11dd2e1098fb13/Brotli-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d", size = 2941254, upload-time = "2023-09-07T14:03:21.914Z" }, - { url = "https://files.pythonhosted.org/packages/3b/24/1671acb450c902edb64bd765d73603797c6c7280a9ada85a195f6b78c6e5/Brotli-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e", size = 2857293, upload-time = "2023-09-07T14:03:24Z" }, - { url = "https://files.pythonhosted.org/packages/d5/00/40f760cc27007912b327fe15bf6bfd8eaecbe451687f72a8abc587d503b3/Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da", size = 3002385, upload-time = "2023-09-07T14:03:26.248Z" }, - { url = "https://files.pythonhosted.org/packages/b8/cb/8aaa83f7a4caa131757668c0fb0c4b6384b09ffa77f2fba9570d87ab587d/Brotli-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80", size = 2911104, upload-time = "2023-09-07T14:03:27.849Z" }, - { url = "https://files.pythonhosted.org/packages/bc/c4/65456561d89d3c49f46b7fbeb8fe6e449f13bdc8ea7791832c5d476b2faf/Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d", size = 2809981, upload-time = "2023-09-07T14:03:29.92Z" }, - { url = "https://files.pythonhosted.org/packages/05/1b/cf49528437bae28abce5f6e059f0d0be6fecdcc1d3e33e7c54b3ca498425/Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0", size = 2935297, upload-time = "2023-09-07T14:03:32.035Z" }, - { url = "https://files.pythonhosted.org/packages/81/ff/190d4af610680bf0c5a09eb5d1eac6e99c7c8e216440f9c7cfd42b7adab5/Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e", size = 2930735, upload-time = "2023-09-07T14:03:33.801Z" }, - { url = "https://files.pythonhosted.org/packages/80/7d/f1abbc0c98f6e09abd3cad63ec34af17abc4c44f308a7a539010f79aae7a/Brotli-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5dab0844f2cf82be357a0eb11a9087f70c5430b2c241493fc122bb6f2bb0917c", size = 2933107, upload-time = "2024-10-18T12:32:09.016Z" }, - { url = "https://files.pythonhosted.org/packages/34/ce/5a5020ba48f2b5a4ad1c0522d095ad5847a0be508e7d7569c8630ce25062/Brotli-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e4fe605b917c70283db7dfe5ada75e04561479075761a0b3866c081d035b01c1", size = 2845400, upload-time = "2024-10-18T12:32:11.134Z" }, - { url = "https://files.pythonhosted.org/packages/44/89/fa2c4355ab1eecf3994e5a0a7f5492c6ff81dfcb5f9ba7859bd534bb5c1a/Brotli-1.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1e9a65b5736232e7a7f91ff3d02277f11d339bf34099a56cdab6a8b3410a02b2", size = 3031985, upload-time = "2024-10-18T12:32:12.813Z" }, - { url = "https://files.pythonhosted.org/packages/af/a4/79196b4a1674143d19dca400866b1a4d1a089040df7b93b88ebae81f3447/Brotli-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:58d4b711689366d4a03ac7957ab8c28890415e267f9b6589969e74b6e42225ec", size = 2927099, upload-time = "2024-10-18T12:32:14.733Z" }, - { url = "https://files.pythonhosted.org/packages/e9/54/1c0278556a097f9651e657b873ab08f01b9a9ae4cac128ceb66427d7cd20/Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2", size = 333172, upload-time = "2023-09-07T14:03:35.212Z" }, - { url = "https://files.pythonhosted.org/packages/f7/65/b785722e941193fd8b571afd9edbec2a9b838ddec4375d8af33a50b8dab9/Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128", size = 357255, upload-time = "2023-09-07T14:03:36.447Z" }, - { url = "https://files.pythonhosted.org/packages/96/12/ad41e7fadd5db55459c4c401842b47f7fee51068f86dd2894dd0dcfc2d2a/Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc", size = 873068, upload-time = "2023-09-07T14:03:37.779Z" }, - { url = "https://files.pythonhosted.org/packages/95/4e/5afab7b2b4b61a84e9c75b17814198ce515343a44e2ed4488fac314cd0a9/Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6", size = 446244, upload-time = "2023-09-07T14:03:39.223Z" }, - { url = "https://files.pythonhosted.org/packages/9d/e6/f305eb61fb9a8580c525478a4a34c5ae1a9bcb12c3aee619114940bc513d/Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd", size = 2906500, upload-time = "2023-09-07T14:03:40.858Z" }, - { url = "https://files.pythonhosted.org/packages/3e/4f/af6846cfbc1550a3024e5d3775ede1e00474c40882c7bf5b37a43ca35e91/Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf", size = 2943950, upload-time = "2023-09-07T14:03:42.896Z" }, - { url = "https://files.pythonhosted.org/packages/b3/e7/ca2993c7682d8629b62630ebf0d1f3bb3d579e667ce8e7ca03a0a0576a2d/Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61", size = 2918527, upload-time = "2023-09-07T14:03:44.552Z" }, - { url = "https://files.pythonhosted.org/packages/b3/96/da98e7bedc4c51104d29cc61e5f449a502dd3dbc211944546a4cc65500d3/Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327", size = 2845489, upload-time = "2023-09-07T14:03:46.594Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ef/ccbc16947d6ce943a7f57e1a40596c75859eeb6d279c6994eddd69615265/Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd", size = 2914080, upload-time = "2023-09-07T14:03:48.204Z" }, - { url = "https://files.pythonhosted.org/packages/80/d6/0bd38d758d1afa62a5524172f0b18626bb2392d717ff94806f741fcd5ee9/Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9", size = 2813051, upload-time = "2023-09-07T14:03:50.348Z" }, - { url = "https://files.pythonhosted.org/packages/14/56/48859dd5d129d7519e001f06dcfbb6e2cf6db92b2702c0c2ce7d97e086c1/Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265", size = 2938172, upload-time = "2023-09-07T14:03:52.395Z" }, - { url = "https://files.pythonhosted.org/packages/3d/77/a236d5f8cd9e9f4348da5acc75ab032ab1ab2c03cc8f430d24eea2672888/Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8", size = 2933023, upload-time = "2023-09-07T14:03:53.96Z" }, - { url = "https://files.pythonhosted.org/packages/f1/87/3b283efc0f5cb35f7f84c0c240b1e1a1003a5e47141a4881bf87c86d0ce2/Brotli-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f", size = 2935871, upload-time = "2024-10-18T12:32:16.688Z" }, - { url = "https://files.pythonhosted.org/packages/f3/eb/2be4cc3e2141dc1a43ad4ca1875a72088229de38c68e842746b342667b2a/Brotli-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757", size = 2847784, upload-time = "2024-10-18T12:32:18.459Z" }, - { url = "https://files.pythonhosted.org/packages/66/13/b58ddebfd35edde572ccefe6890cf7c493f0c319aad2a5badee134b4d8ec/Brotli-1.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0", size = 3034905, upload-time = "2024-10-18T12:32:20.192Z" }, - { url = "https://files.pythonhosted.org/packages/84/9c/bc96b6c7db824998a49ed3b38e441a2cae9234da6fa11f6ed17e8cf4f147/Brotli-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b", size = 2929467, upload-time = "2024-10-18T12:32:21.774Z" }, - { url = "https://files.pythonhosted.org/packages/e7/71/8f161dee223c7ff7fea9d44893fba953ce97cf2c3c33f78ba260a91bcff5/Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50", size = 333169, upload-time = "2023-09-07T14:03:55.404Z" }, - { url = "https://files.pythonhosted.org/packages/02/8a/fece0ee1057643cb2a5bbf59682de13f1725f8482b2c057d4e799d7ade75/Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1", size = 357253, upload-time = "2023-09-07T14:03:56.643Z" }, - { url = "https://files.pythonhosted.org/packages/5c/d0/5373ae13b93fe00095a58efcbce837fd470ca39f703a235d2a999baadfbc/Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28", size = 815693, upload-time = "2024-10-18T12:32:23.824Z" }, - { url = "https://files.pythonhosted.org/packages/8e/48/f6e1cdf86751300c288c1459724bfa6917a80e30dbfc326f92cea5d3683a/Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f", size = 422489, upload-time = "2024-10-18T12:32:25.641Z" }, - { url = "https://files.pythonhosted.org/packages/06/88/564958cedce636d0f1bed313381dfc4b4e3d3f6015a63dae6146e1b8c65c/Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409", size = 873081, upload-time = "2023-09-07T14:03:57.967Z" }, - { url = "https://files.pythonhosted.org/packages/58/79/b7026a8bb65da9a6bb7d14329fd2bd48d2b7f86d7329d5cc8ddc6a90526f/Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2", size = 446244, upload-time = "2023-09-07T14:03:59.319Z" }, - { url = "https://files.pythonhosted.org/packages/e5/18/c18c32ecea41b6c0004e15606e274006366fe19436b6adccc1ae7b2e50c2/Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451", size = 2906505, upload-time = "2023-09-07T14:04:01.327Z" }, - { url = "https://files.pythonhosted.org/packages/08/c8/69ec0496b1ada7569b62d85893d928e865df29b90736558d6c98c2031208/Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91", size = 2944152, upload-time = "2023-09-07T14:04:03.033Z" }, - { url = "https://files.pythonhosted.org/packages/ab/fb/0517cea182219d6768113a38167ef6d4eb157a033178cc938033a552ed6d/Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408", size = 2919252, upload-time = "2023-09-07T14:04:04.675Z" }, - { url = "https://files.pythonhosted.org/packages/c7/53/73a3431662e33ae61a5c80b1b9d2d18f58dfa910ae8dd696e57d39f1a2f5/Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0", size = 2845955, upload-time = "2023-09-07T14:04:06.585Z" }, - { url = "https://files.pythonhosted.org/packages/55/ac/bd280708d9c5ebdbf9de01459e625a3e3803cce0784f47d633562cf40e83/Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc", size = 2914304, upload-time = "2023-09-07T14:04:08.668Z" }, - { url = "https://files.pythonhosted.org/packages/76/58/5c391b41ecfc4527d2cc3350719b02e87cb424ef8ba2023fb662f9bf743c/Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180", size = 2814452, upload-time = "2023-09-07T14:04:10.736Z" }, - { url = "https://files.pythonhosted.org/packages/c7/4e/91b8256dfe99c407f174924b65a01f5305e303f486cc7a2e8a5d43c8bec3/Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248", size = 2938751, upload-time = "2023-09-07T14:04:12.875Z" }, - { url = "https://files.pythonhosted.org/packages/5a/a6/e2a39a5d3b412938362bbbeba5af904092bf3f95b867b4a3eb856104074e/Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966", size = 2933757, upload-time = "2023-09-07T14:04:14.551Z" }, - { url = "https://files.pythonhosted.org/packages/13/f0/358354786280a509482e0e77c1a5459e439766597d280f28cb097642fc26/Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9", size = 2936146, upload-time = "2024-10-18T12:32:27.257Z" }, - { url = "https://files.pythonhosted.org/packages/80/f7/daf538c1060d3a88266b80ecc1d1c98b79553b3f117a485653f17070ea2a/Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb", size = 2848055, upload-time = "2024-10-18T12:32:29.376Z" }, - { url = "https://files.pythonhosted.org/packages/ad/cf/0eaa0585c4077d3c2d1edf322d8e97aabf317941d3a72d7b3ad8bce004b0/Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111", size = 3035102, upload-time = "2024-10-18T12:32:31.371Z" }, - { url = "https://files.pythonhosted.org/packages/d8/63/1c1585b2aa554fe6dbce30f0c18bdbc877fa9a1bf5ff17677d9cca0ac122/Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839", size = 2930029, upload-time = "2024-10-18T12:32:33.293Z" }, - { url = "https://files.pythonhosted.org/packages/5f/3b/4e3fd1893eb3bbfef8e5a80d4508bec17a57bb92d586c85c12d28666bb13/Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0", size = 333276, upload-time = "2023-09-07T14:04:16.49Z" }, - { url = "https://files.pythonhosted.org/packages/3d/d5/942051b45a9e883b5b6e98c041698b1eb2012d25e5948c58d6bf85b1bb43/Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951", size = 357255, upload-time = "2023-09-07T14:04:17.83Z" }, - { url = "https://files.pythonhosted.org/packages/0a/9f/fb37bb8ffc52a8da37b1c03c459a8cd55df7a57bdccd8831d500e994a0ca/Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5", size = 815681, upload-time = "2024-10-18T12:32:34.942Z" }, - { url = "https://files.pythonhosted.org/packages/06/b3/dbd332a988586fefb0aa49c779f59f47cae76855c2d00f450364bb574cac/Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8", size = 422475, upload-time = "2024-10-18T12:32:36.485Z" }, - { url = "https://files.pythonhosted.org/packages/bb/80/6aaddc2f63dbcf2d93c2d204e49c11a9ec93a8c7c63261e2b4bd35198283/Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f", size = 2906173, upload-time = "2024-10-18T12:32:37.978Z" }, - { url = "https://files.pythonhosted.org/packages/ea/1d/e6ca79c96ff5b641df6097d299347507d39a9604bde8915e76bf026d6c77/Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648", size = 2943803, upload-time = "2024-10-18T12:32:39.606Z" }, - { url = "https://files.pythonhosted.org/packages/ac/a3/d98d2472e0130b7dd3acdbb7f390d478123dbf62b7d32bda5c830a96116d/Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0", size = 2918946, upload-time = "2024-10-18T12:32:41.679Z" }, - { url = "https://files.pythonhosted.org/packages/c4/a5/c69e6d272aee3e1423ed005d8915a7eaa0384c7de503da987f2d224d0721/Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089", size = 2845707, upload-time = "2024-10-18T12:32:43.478Z" }, - { url = "https://files.pythonhosted.org/packages/58/9f/4149d38b52725afa39067350696c09526de0125ebfbaab5acc5af28b42ea/Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368", size = 2936231, upload-time = "2024-10-18T12:32:45.224Z" }, - { url = "https://files.pythonhosted.org/packages/5a/5a/145de884285611838a16bebfdb060c231c52b8f84dfbe52b852a15780386/Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c", size = 2848157, upload-time = "2024-10-18T12:32:46.894Z" }, - { url = "https://files.pythonhosted.org/packages/50/ae/408b6bfb8525dadebd3b3dd5b19d631da4f7d46420321db44cd99dcf2f2c/Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284", size = 3035122, upload-time = "2024-10-18T12:32:48.844Z" }, - { url = "https://files.pythonhosted.org/packages/af/85/a94e5cfaa0ca449d8f91c3d6f78313ebf919a0dbd55a100c711c6e9655bc/Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7", size = 2930206, upload-time = "2024-10-18T12:32:51.198Z" }, - { url = "https://files.pythonhosted.org/packages/c2/f0/a61d9262cd01351df22e57ad7c34f66794709acab13f34be2675f45bf89d/Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0", size = 333804, upload-time = "2024-10-18T12:32:52.661Z" }, - { url = "https://files.pythonhosted.org/packages/7e/c1/ec214e9c94000d1c1974ec67ced1c970c148aa6b8d8373066123fc3dbf06/Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b", size = 358517, upload-time = "2024-10-18T12:32:54.066Z" }, + { url = "https://files.pythonhosted.org/packages/6d/3a/dbf4fb970c1019a57b5e492e1e0eae745d32e59ba4d6161ab5422b08eefe/Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752", size = 873045 }, + { url = "https://files.pythonhosted.org/packages/dd/11/afc14026ea7f44bd6eb9316d800d439d092c8d508752055ce8d03086079a/Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9", size = 446218 }, + { url = "https://files.pythonhosted.org/packages/36/83/7545a6e7729db43cb36c4287ae388d6885c85a86dd251768a47015dfde32/Brotli-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3", size = 2903872 }, + { url = "https://files.pythonhosted.org/packages/32/23/35331c4d9391fcc0f29fd9bec2c76e4b4eeab769afbc4b11dd2e1098fb13/Brotli-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d", size = 2941254 }, + { url = "https://files.pythonhosted.org/packages/3b/24/1671acb450c902edb64bd765d73603797c6c7280a9ada85a195f6b78c6e5/Brotli-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e", size = 2857293 }, + { url = "https://files.pythonhosted.org/packages/d5/00/40f760cc27007912b327fe15bf6bfd8eaecbe451687f72a8abc587d503b3/Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da", size = 3002385 }, + { url = "https://files.pythonhosted.org/packages/b8/cb/8aaa83f7a4caa131757668c0fb0c4b6384b09ffa77f2fba9570d87ab587d/Brotli-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80", size = 2911104 }, + { url = "https://files.pythonhosted.org/packages/bc/c4/65456561d89d3c49f46b7fbeb8fe6e449f13bdc8ea7791832c5d476b2faf/Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d", size = 2809981 }, + { url = "https://files.pythonhosted.org/packages/05/1b/cf49528437bae28abce5f6e059f0d0be6fecdcc1d3e33e7c54b3ca498425/Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0", size = 2935297 }, + { url = "https://files.pythonhosted.org/packages/81/ff/190d4af610680bf0c5a09eb5d1eac6e99c7c8e216440f9c7cfd42b7adab5/Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e", size = 2930735 }, + { url = "https://files.pythonhosted.org/packages/80/7d/f1abbc0c98f6e09abd3cad63ec34af17abc4c44f308a7a539010f79aae7a/Brotli-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5dab0844f2cf82be357a0eb11a9087f70c5430b2c241493fc122bb6f2bb0917c", size = 2933107 }, + { url = "https://files.pythonhosted.org/packages/34/ce/5a5020ba48f2b5a4ad1c0522d095ad5847a0be508e7d7569c8630ce25062/Brotli-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e4fe605b917c70283db7dfe5ada75e04561479075761a0b3866c081d035b01c1", size = 2845400 }, + { url = "https://files.pythonhosted.org/packages/44/89/fa2c4355ab1eecf3994e5a0a7f5492c6ff81dfcb5f9ba7859bd534bb5c1a/Brotli-1.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1e9a65b5736232e7a7f91ff3d02277f11d339bf34099a56cdab6a8b3410a02b2", size = 3031985 }, + { url = "https://files.pythonhosted.org/packages/af/a4/79196b4a1674143d19dca400866b1a4d1a089040df7b93b88ebae81f3447/Brotli-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:58d4b711689366d4a03ac7957ab8c28890415e267f9b6589969e74b6e42225ec", size = 2927099 }, + { url = "https://files.pythonhosted.org/packages/e9/54/1c0278556a097f9651e657b873ab08f01b9a9ae4cac128ceb66427d7cd20/Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2", size = 333172 }, + { url = "https://files.pythonhosted.org/packages/f7/65/b785722e941193fd8b571afd9edbec2a9b838ddec4375d8af33a50b8dab9/Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128", size = 357255 }, + { url = "https://files.pythonhosted.org/packages/96/12/ad41e7fadd5db55459c4c401842b47f7fee51068f86dd2894dd0dcfc2d2a/Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc", size = 873068 }, + { url = "https://files.pythonhosted.org/packages/95/4e/5afab7b2b4b61a84e9c75b17814198ce515343a44e2ed4488fac314cd0a9/Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6", size = 446244 }, + { url = "https://files.pythonhosted.org/packages/9d/e6/f305eb61fb9a8580c525478a4a34c5ae1a9bcb12c3aee619114940bc513d/Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd", size = 2906500 }, + { url = "https://files.pythonhosted.org/packages/3e/4f/af6846cfbc1550a3024e5d3775ede1e00474c40882c7bf5b37a43ca35e91/Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf", size = 2943950 }, + { url = "https://files.pythonhosted.org/packages/b3/e7/ca2993c7682d8629b62630ebf0d1f3bb3d579e667ce8e7ca03a0a0576a2d/Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61", size = 2918527 }, + { url = "https://files.pythonhosted.org/packages/b3/96/da98e7bedc4c51104d29cc61e5f449a502dd3dbc211944546a4cc65500d3/Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327", size = 2845489 }, + { url = "https://files.pythonhosted.org/packages/e8/ef/ccbc16947d6ce943a7f57e1a40596c75859eeb6d279c6994eddd69615265/Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd", size = 2914080 }, + { url = "https://files.pythonhosted.org/packages/80/d6/0bd38d758d1afa62a5524172f0b18626bb2392d717ff94806f741fcd5ee9/Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9", size = 2813051 }, + { url = "https://files.pythonhosted.org/packages/14/56/48859dd5d129d7519e001f06dcfbb6e2cf6db92b2702c0c2ce7d97e086c1/Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265", size = 2938172 }, + { url = "https://files.pythonhosted.org/packages/3d/77/a236d5f8cd9e9f4348da5acc75ab032ab1ab2c03cc8f430d24eea2672888/Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8", size = 2933023 }, + { url = "https://files.pythonhosted.org/packages/f1/87/3b283efc0f5cb35f7f84c0c240b1e1a1003a5e47141a4881bf87c86d0ce2/Brotli-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f", size = 2935871 }, + { url = "https://files.pythonhosted.org/packages/f3/eb/2be4cc3e2141dc1a43ad4ca1875a72088229de38c68e842746b342667b2a/Brotli-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757", size = 2847784 }, + { url = "https://files.pythonhosted.org/packages/66/13/b58ddebfd35edde572ccefe6890cf7c493f0c319aad2a5badee134b4d8ec/Brotli-1.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0", size = 3034905 }, + { url = "https://files.pythonhosted.org/packages/84/9c/bc96b6c7db824998a49ed3b38e441a2cae9234da6fa11f6ed17e8cf4f147/Brotli-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b", size = 2929467 }, + { url = "https://files.pythonhosted.org/packages/e7/71/8f161dee223c7ff7fea9d44893fba953ce97cf2c3c33f78ba260a91bcff5/Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50", size = 333169 }, + { url = "https://files.pythonhosted.org/packages/02/8a/fece0ee1057643cb2a5bbf59682de13f1725f8482b2c057d4e799d7ade75/Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1", size = 357253 }, + { url = "https://files.pythonhosted.org/packages/5c/d0/5373ae13b93fe00095a58efcbce837fd470ca39f703a235d2a999baadfbc/Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28", size = 815693 }, + { url = "https://files.pythonhosted.org/packages/8e/48/f6e1cdf86751300c288c1459724bfa6917a80e30dbfc326f92cea5d3683a/Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f", size = 422489 }, + { url = "https://files.pythonhosted.org/packages/06/88/564958cedce636d0f1bed313381dfc4b4e3d3f6015a63dae6146e1b8c65c/Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409", size = 873081 }, + { url = "https://files.pythonhosted.org/packages/58/79/b7026a8bb65da9a6bb7d14329fd2bd48d2b7f86d7329d5cc8ddc6a90526f/Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2", size = 446244 }, + { url = "https://files.pythonhosted.org/packages/e5/18/c18c32ecea41b6c0004e15606e274006366fe19436b6adccc1ae7b2e50c2/Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451", size = 2906505 }, + { url = "https://files.pythonhosted.org/packages/08/c8/69ec0496b1ada7569b62d85893d928e865df29b90736558d6c98c2031208/Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91", size = 2944152 }, + { url = "https://files.pythonhosted.org/packages/ab/fb/0517cea182219d6768113a38167ef6d4eb157a033178cc938033a552ed6d/Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408", size = 2919252 }, + { url = "https://files.pythonhosted.org/packages/c7/53/73a3431662e33ae61a5c80b1b9d2d18f58dfa910ae8dd696e57d39f1a2f5/Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0", size = 2845955 }, + { url = "https://files.pythonhosted.org/packages/55/ac/bd280708d9c5ebdbf9de01459e625a3e3803cce0784f47d633562cf40e83/Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc", size = 2914304 }, + { url = "https://files.pythonhosted.org/packages/76/58/5c391b41ecfc4527d2cc3350719b02e87cb424ef8ba2023fb662f9bf743c/Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180", size = 2814452 }, + { url = "https://files.pythonhosted.org/packages/c7/4e/91b8256dfe99c407f174924b65a01f5305e303f486cc7a2e8a5d43c8bec3/Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248", size = 2938751 }, + { url = "https://files.pythonhosted.org/packages/5a/a6/e2a39a5d3b412938362bbbeba5af904092bf3f95b867b4a3eb856104074e/Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966", size = 2933757 }, + { url = "https://files.pythonhosted.org/packages/13/f0/358354786280a509482e0e77c1a5459e439766597d280f28cb097642fc26/Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9", size = 2936146 }, + { url = "https://files.pythonhosted.org/packages/80/f7/daf538c1060d3a88266b80ecc1d1c98b79553b3f117a485653f17070ea2a/Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb", size = 2848055 }, + { url = "https://files.pythonhosted.org/packages/ad/cf/0eaa0585c4077d3c2d1edf322d8e97aabf317941d3a72d7b3ad8bce004b0/Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111", size = 3035102 }, + { url = "https://files.pythonhosted.org/packages/d8/63/1c1585b2aa554fe6dbce30f0c18bdbc877fa9a1bf5ff17677d9cca0ac122/Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839", size = 2930029 }, + { url = "https://files.pythonhosted.org/packages/5f/3b/4e3fd1893eb3bbfef8e5a80d4508bec17a57bb92d586c85c12d28666bb13/Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0", size = 333276 }, + { url = "https://files.pythonhosted.org/packages/3d/d5/942051b45a9e883b5b6e98c041698b1eb2012d25e5948c58d6bf85b1bb43/Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951", size = 357255 }, + { url = "https://files.pythonhosted.org/packages/0a/9f/fb37bb8ffc52a8da37b1c03c459a8cd55df7a57bdccd8831d500e994a0ca/Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5", size = 815681 }, + { url = "https://files.pythonhosted.org/packages/06/b3/dbd332a988586fefb0aa49c779f59f47cae76855c2d00f450364bb574cac/Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8", size = 422475 }, + { url = "https://files.pythonhosted.org/packages/bb/80/6aaddc2f63dbcf2d93c2d204e49c11a9ec93a8c7c63261e2b4bd35198283/Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f", size = 2906173 }, + { url = "https://files.pythonhosted.org/packages/ea/1d/e6ca79c96ff5b641df6097d299347507d39a9604bde8915e76bf026d6c77/Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648", size = 2943803 }, + { url = "https://files.pythonhosted.org/packages/ac/a3/d98d2472e0130b7dd3acdbb7f390d478123dbf62b7d32bda5c830a96116d/Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0", size = 2918946 }, + { url = "https://files.pythonhosted.org/packages/c4/a5/c69e6d272aee3e1423ed005d8915a7eaa0384c7de503da987f2d224d0721/Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089", size = 2845707 }, + { url = "https://files.pythonhosted.org/packages/58/9f/4149d38b52725afa39067350696c09526de0125ebfbaab5acc5af28b42ea/Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368", size = 2936231 }, + { url = "https://files.pythonhosted.org/packages/5a/5a/145de884285611838a16bebfdb060c231c52b8f84dfbe52b852a15780386/Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c", size = 2848157 }, + { url = "https://files.pythonhosted.org/packages/50/ae/408b6bfb8525dadebd3b3dd5b19d631da4f7d46420321db44cd99dcf2f2c/Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284", size = 3035122 }, + { url = "https://files.pythonhosted.org/packages/af/85/a94e5cfaa0ca449d8f91c3d6f78313ebf919a0dbd55a100c711c6e9655bc/Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7", size = 2930206 }, + { url = "https://files.pythonhosted.org/packages/c2/f0/a61d9262cd01351df22e57ad7c34f66794709acab13f34be2675f45bf89d/Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0", size = 333804 }, + { url = "https://files.pythonhosted.org/packages/7e/c1/ec214e9c94000d1c1974ec67ced1c970c148aa6b8d8373066123fc3dbf06/Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b", size = 358517 }, ] [[package]] name = "certifi" version = "2023.11.17" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d4/91/c89518dd4fe1f3a4e3f6ab7ff23cb00ef2e8c9adf99dacc618ad5e068e28/certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1", size = 163637, upload-time = "2023-11-18T02:54:02.397Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/91/c89518dd4fe1f3a4e3f6ab7ff23cb00ef2e8c9adf99dacc618ad5e068e28/certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1", size = 163637 } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/62/428ef076be88fa93716b576e4a01f919d25968913e817077a386fcbe4f42/certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474", size = 162530, upload-time = "2023-11-18T02:54:00.083Z" }, + { url = "https://files.pythonhosted.org/packages/64/62/428ef076be88fa93716b576e4a01f919d25968913e817077a386fcbe4f42/certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474", size = 162530 }, ] [[package]] @@ -217,108 +223,108 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pycparser" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191, upload-time = "2024-09-04T20:43:30.027Z" }, - { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592, upload-time = "2024-09-04T20:43:32.108Z" }, - { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024, upload-time = "2024-09-04T20:43:34.186Z" }, - { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188, upload-time = "2024-09-04T20:43:36.286Z" }, - { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571, upload-time = "2024-09-04T20:43:38.586Z" }, - { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687, upload-time = "2024-09-04T20:43:40.084Z" }, - { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211, upload-time = "2024-09-04T20:43:41.526Z" }, - { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325, upload-time = "2024-09-04T20:43:43.117Z" }, - { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784, upload-time = "2024-09-04T20:43:45.256Z" }, - { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564, upload-time = "2024-09-04T20:43:46.779Z" }, - { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804, upload-time = "2024-09-04T20:43:48.186Z" }, - { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299, upload-time = "2024-09-04T20:43:49.812Z" }, - { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264, upload-time = "2024-09-04T20:43:51.124Z" }, - { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651, upload-time = "2024-09-04T20:43:52.872Z" }, - { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259, upload-time = "2024-09-04T20:43:56.123Z" }, - { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200, upload-time = "2024-09-04T20:43:57.891Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235, upload-time = "2024-09-04T20:44:00.18Z" }, - { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721, upload-time = "2024-09-04T20:44:01.585Z" }, - { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242, upload-time = "2024-09-04T20:44:03.467Z" }, - { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999, upload-time = "2024-09-04T20:44:05.023Z" }, - { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242, upload-time = "2024-09-04T20:44:06.444Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604, upload-time = "2024-09-04T20:44:08.206Z" }, - { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727, upload-time = "2024-09-04T20:44:09.481Z" }, - { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400, upload-time = "2024-09-04T20:44:10.873Z" }, - { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178, upload-time = "2024-09-04T20:44:12.232Z" }, - { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840, upload-time = "2024-09-04T20:44:13.739Z" }, - { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" }, - { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload-time = "2024-09-04T20:44:17.188Z" }, - { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload-time = "2024-09-04T20:44:18.688Z" }, - { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256, upload-time = "2024-09-04T20:44:20.248Z" }, - { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424, upload-time = "2024-09-04T20:44:21.673Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568, upload-time = "2024-09-04T20:44:23.245Z" }, - { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload-time = "2024-09-04T20:44:24.757Z" }, - { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448, upload-time = "2024-09-04T20:44:26.208Z" }, - { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976, upload-time = "2024-09-04T20:44:27.578Z" }, - { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" }, - { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" }, - { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" }, - { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" }, - { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload-time = "2024-09-04T20:44:36.743Z" }, - { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" }, - { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" }, - { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" }, - { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" }, - { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" }, + { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191 }, + { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592 }, + { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024 }, + { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188 }, + { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571 }, + { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687 }, + { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211 }, + { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325 }, + { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784 }, + { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564 }, + { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804 }, + { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299 }, + { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, + { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, ] [[package]] name = "charset-normalizer" version = "3.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/63/09/c1bc53dab74b1816a00d8d030de5bf98f724c52c1635e07681d312f20be8/charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", size = 104809, upload-time = "2023-11-01T04:04:59.997Z" } +sdist = { url = "https://files.pythonhosted.org/packages/63/09/c1bc53dab74b1816a00d8d030de5bf98f724c52c1635e07681d312f20be8/charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", size = 104809 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/61/095a0aa1a84d1481998b534177c8566fdc50bb1233ea9a0478cd3cc075bd/charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", size = 194219, upload-time = "2023-11-01T04:02:29.048Z" }, - { url = "https://files.pythonhosted.org/packages/cc/94/f7cf5e5134175de79ad2059edf2adce18e0685ebdb9227ff0139975d0e93/charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", size = 122521, upload-time = "2023-11-01T04:02:32.452Z" }, - { url = "https://files.pythonhosted.org/packages/46/6a/d5c26c41c49b546860cc1acabdddf48b0b3fb2685f4f5617ac59261b44ae/charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", size = 120383, upload-time = "2023-11-01T04:02:34.11Z" }, - { url = "https://files.pythonhosted.org/packages/b8/60/e2f67915a51be59d4539ed189eb0a2b0d292bf79270410746becb32bc2c3/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", size = 138223, upload-time = "2023-11-01T04:02:36.213Z" }, - { url = "https://files.pythonhosted.org/packages/05/8c/eb854996d5fef5e4f33ad56927ad053d04dc820e4a3d39023f35cad72617/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", size = 148101, upload-time = "2023-11-01T04:02:38.067Z" }, - { url = "https://files.pythonhosted.org/packages/f6/93/bb6cbeec3bf9da9b2eba458c15966658d1daa8b982c642f81c93ad9b40e1/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", size = 140699, upload-time = "2023-11-01T04:02:39.436Z" }, - { url = "https://files.pythonhosted.org/packages/da/f1/3702ba2a7470666a62fd81c58a4c40be00670e5006a67f4d626e57f013ae/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", size = 142065, upload-time = "2023-11-01T04:02:41.357Z" }, - { url = "https://files.pythonhosted.org/packages/3f/ba/3f5e7be00b215fa10e13d64b1f6237eb6ebea66676a41b2bcdd09fe74323/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", size = 144505, upload-time = "2023-11-01T04:02:43.108Z" }, - { url = "https://files.pythonhosted.org/packages/33/c3/3b96a435c5109dd5b6adc8a59ba1d678b302a97938f032e3770cc84cd354/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", size = 139425, upload-time = "2023-11-01T04:02:45.427Z" }, - { url = "https://files.pythonhosted.org/packages/43/05/3bf613e719efe68fb3a77f9c536a389f35b95d75424b96b426a47a45ef1d/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", size = 145287, upload-time = "2023-11-01T04:02:46.705Z" }, - { url = "https://files.pythonhosted.org/packages/58/78/a0bc646900994df12e07b4ae5c713f2b3e5998f58b9d3720cce2aa45652f/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", size = 149929, upload-time = "2023-11-01T04:02:48.098Z" }, - { url = "https://files.pythonhosted.org/packages/eb/5c/97d97248af4920bc68687d9c3b3c0f47c910e21a8ff80af4565a576bd2f0/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", size = 141605, upload-time = "2023-11-01T04:02:49.605Z" }, - { url = "https://files.pythonhosted.org/packages/a8/31/47d018ef89f95b8aded95c589a77c072c55e94b50a41aa99c0a2008a45a4/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", size = 142646, upload-time = "2023-11-01T04:02:51.35Z" }, - { url = "https://files.pythonhosted.org/packages/ae/d5/4fecf1d58bedb1340a50f165ba1c7ddc0400252d6832ff619c4568b36cc0/charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", size = 92846, upload-time = "2023-11-01T04:02:52.679Z" }, - { url = "https://files.pythonhosted.org/packages/a2/a0/4af29e22cb5942488cf45630cbdd7cefd908768e69bdd90280842e4e8529/charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", size = 100343, upload-time = "2023-11-01T04:02:53.915Z" }, - { url = "https://files.pythonhosted.org/packages/68/77/02839016f6fbbf808e8b38601df6e0e66c17bbab76dff4613f7511413597/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", size = 191647, upload-time = "2023-11-01T04:02:55.329Z" }, - { url = "https://files.pythonhosted.org/packages/3e/33/21a875a61057165e92227466e54ee076b73af1e21fe1b31f1e292251aa1e/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", size = 121434, upload-time = "2023-11-01T04:02:57.173Z" }, - { url = "https://files.pythonhosted.org/packages/dd/51/68b61b90b24ca35495956b718f35a9756ef7d3dd4b3c1508056fa98d1a1b/charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", size = 118979, upload-time = "2023-11-01T04:02:58.442Z" }, - { url = "https://files.pythonhosted.org/packages/e4/a6/7ee57823d46331ddc37dd00749c95b0edec2c79b15fc0d6e6efb532e89ac/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", size = 136582, upload-time = "2023-11-01T04:02:59.776Z" }, - { url = "https://files.pythonhosted.org/packages/74/f1/0d9fe69ac441467b737ba7f48c68241487df2f4522dd7246d9426e7c690e/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", size = 146645, upload-time = "2023-11-01T04:03:02.186Z" }, - { url = "https://files.pythonhosted.org/packages/05/31/e1f51c76db7be1d4aef220d29fbfa5dbb4a99165d9833dcbf166753b6dc0/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", size = 139398, upload-time = "2023-11-01T04:03:04.255Z" }, - { url = "https://files.pythonhosted.org/packages/40/26/f35951c45070edc957ba40a5b1db3cf60a9dbb1b350c2d5bef03e01e61de/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", size = 140273, upload-time = "2023-11-01T04:03:05.983Z" }, - { url = "https://files.pythonhosted.org/packages/07/07/7e554f2bbce3295e191f7e653ff15d55309a9ca40d0362fcdab36f01063c/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", size = 142577, upload-time = "2023-11-01T04:03:07.567Z" }, - { url = "https://files.pythonhosted.org/packages/d8/b5/eb705c313100defa57da79277d9207dc8d8e45931035862fa64b625bfead/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", size = 137747, upload-time = "2023-11-01T04:03:08.886Z" }, - { url = "https://files.pythonhosted.org/packages/19/28/573147271fd041d351b438a5665be8223f1dd92f273713cb882ddafe214c/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", size = 143375, upload-time = "2023-11-01T04:03:10.613Z" }, - { url = "https://files.pythonhosted.org/packages/cf/7c/f3b682fa053cc21373c9a839e6beba7705857075686a05c72e0f8c4980ca/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", size = 148474, upload-time = "2023-11-01T04:03:11.973Z" }, - { url = "https://files.pythonhosted.org/packages/1e/49/7ab74d4ac537ece3bc3334ee08645e231f39f7d6df6347b29a74b0537103/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", size = 140232, upload-time = "2023-11-01T04:03:13.505Z" }, - { url = "https://files.pythonhosted.org/packages/2d/dc/9dacba68c9ac0ae781d40e1a0c0058e26302ea0660e574ddf6797a0347f7/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", size = 140859, upload-time = "2023-11-01T04:03:17.362Z" }, - { url = "https://files.pythonhosted.org/packages/6c/c2/4a583f800c0708dd22096298e49f887b49d9746d0e78bfc1d7e29816614c/charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", size = 92509, upload-time = "2023-11-01T04:03:21.453Z" }, - { url = "https://files.pythonhosted.org/packages/57/ec/80c8d48ac8b1741d5b963797b7c0c869335619e13d4744ca2f67fc11c6fc/charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", size = 99870, upload-time = "2023-11-01T04:03:22.723Z" }, - { url = "https://files.pythonhosted.org/packages/d1/b2/fcedc8255ec42afee97f9e6f0145c734bbe104aac28300214593eb326f1d/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", size = 192892, upload-time = "2023-11-01T04:03:24.135Z" }, - { url = "https://files.pythonhosted.org/packages/2e/7d/2259318c202f3d17f3fe6438149b3b9e706d1070fe3fcbb28049730bb25c/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", size = 122213, upload-time = "2023-11-01T04:03:25.66Z" }, - { url = "https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", size = 119404, upload-time = "2023-11-01T04:03:27.04Z" }, - { url = "https://files.pythonhosted.org/packages/99/b0/9c365f6d79a9f0f3c379ddb40a256a67aa69c59609608fe7feb6235896e1/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", size = 137275, upload-time = "2023-11-01T04:03:28.466Z" }, - { url = "https://files.pythonhosted.org/packages/91/33/749df346e93d7a30cdcb90cbfdd41a06026317bfbfb62cd68307c1a3c543/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", size = 147518, upload-time = "2023-11-01T04:03:29.82Z" }, - { url = "https://files.pythonhosted.org/packages/72/1a/641d5c9f59e6af4c7b53da463d07600a695b9824e20849cb6eea8a627761/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", size = 140182, upload-time = "2023-11-01T04:03:31.511Z" }, - { url = "https://files.pythonhosted.org/packages/ee/fb/14d30eb4956408ee3ae09ad34299131fb383c47df355ddb428a7331cfa1e/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", size = 141869, upload-time = "2023-11-01T04:03:32.887Z" }, - { url = "https://files.pythonhosted.org/packages/df/3e/a06b18788ca2eb6695c9b22325b6fde7dde0f1d1838b1792a0076f58fe9d/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", size = 144042, upload-time = "2023-11-01T04:03:34.412Z" }, - { url = "https://files.pythonhosted.org/packages/45/59/3d27019d3b447a88fe7e7d004a1e04be220227760264cc41b405e863891b/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", size = 138275, upload-time = "2023-11-01T04:03:35.759Z" }, - { url = "https://files.pythonhosted.org/packages/7b/ef/5eb105530b4da8ae37d506ccfa25057961b7b63d581def6f99165ea89c7e/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", size = 144819, upload-time = "2023-11-01T04:03:37.216Z" }, - { url = "https://files.pythonhosted.org/packages/a2/51/e5023f937d7f307c948ed3e5c29c4b7a3e42ed2ee0b8cdf8f3a706089bf0/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", size = 149415, upload-time = "2023-11-01T04:03:38.694Z" }, - { url = "https://files.pythonhosted.org/packages/24/9d/2e3ef673dfd5be0154b20363c5cdcc5606f35666544381bee15af3778239/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", size = 141212, upload-time = "2023-11-01T04:03:40.07Z" }, - { url = "https://files.pythonhosted.org/packages/5b/ae/ce2c12fcac59cb3860b2e2d76dc405253a4475436b1861d95fe75bdea520/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", size = 142167, upload-time = "2023-11-01T04:03:41.491Z" }, - { url = "https://files.pythonhosted.org/packages/ed/3a/a448bf035dce5da359daf9ae8a16b8a39623cc395a2ffb1620aa1bce62b0/charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", size = 93041, upload-time = "2023-11-01T04:03:42.836Z" }, - { url = "https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63242c23851bdf00a532489fba57fef3416e40/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", size = 100397, upload-time = "2023-11-01T04:03:44.467Z" }, - { url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543, upload-time = "2023-11-01T04:04:58.622Z" }, + { url = "https://files.pythonhosted.org/packages/2b/61/095a0aa1a84d1481998b534177c8566fdc50bb1233ea9a0478cd3cc075bd/charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", size = 194219 }, + { url = "https://files.pythonhosted.org/packages/cc/94/f7cf5e5134175de79ad2059edf2adce18e0685ebdb9227ff0139975d0e93/charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", size = 122521 }, + { url = "https://files.pythonhosted.org/packages/46/6a/d5c26c41c49b546860cc1acabdddf48b0b3fb2685f4f5617ac59261b44ae/charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", size = 120383 }, + { url = "https://files.pythonhosted.org/packages/b8/60/e2f67915a51be59d4539ed189eb0a2b0d292bf79270410746becb32bc2c3/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", size = 138223 }, + { url = "https://files.pythonhosted.org/packages/05/8c/eb854996d5fef5e4f33ad56927ad053d04dc820e4a3d39023f35cad72617/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", size = 148101 }, + { url = "https://files.pythonhosted.org/packages/f6/93/bb6cbeec3bf9da9b2eba458c15966658d1daa8b982c642f81c93ad9b40e1/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", size = 140699 }, + { url = "https://files.pythonhosted.org/packages/da/f1/3702ba2a7470666a62fd81c58a4c40be00670e5006a67f4d626e57f013ae/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", size = 142065 }, + { url = "https://files.pythonhosted.org/packages/3f/ba/3f5e7be00b215fa10e13d64b1f6237eb6ebea66676a41b2bcdd09fe74323/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", size = 144505 }, + { url = "https://files.pythonhosted.org/packages/33/c3/3b96a435c5109dd5b6adc8a59ba1d678b302a97938f032e3770cc84cd354/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", size = 139425 }, + { url = "https://files.pythonhosted.org/packages/43/05/3bf613e719efe68fb3a77f9c536a389f35b95d75424b96b426a47a45ef1d/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", size = 145287 }, + { url = "https://files.pythonhosted.org/packages/58/78/a0bc646900994df12e07b4ae5c713f2b3e5998f58b9d3720cce2aa45652f/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", size = 149929 }, + { url = "https://files.pythonhosted.org/packages/eb/5c/97d97248af4920bc68687d9c3b3c0f47c910e21a8ff80af4565a576bd2f0/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", size = 141605 }, + { url = "https://files.pythonhosted.org/packages/a8/31/47d018ef89f95b8aded95c589a77c072c55e94b50a41aa99c0a2008a45a4/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", size = 142646 }, + { url = "https://files.pythonhosted.org/packages/ae/d5/4fecf1d58bedb1340a50f165ba1c7ddc0400252d6832ff619c4568b36cc0/charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", size = 92846 }, + { url = "https://files.pythonhosted.org/packages/a2/a0/4af29e22cb5942488cf45630cbdd7cefd908768e69bdd90280842e4e8529/charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", size = 100343 }, + { url = "https://files.pythonhosted.org/packages/68/77/02839016f6fbbf808e8b38601df6e0e66c17bbab76dff4613f7511413597/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", size = 191647 }, + { url = "https://files.pythonhosted.org/packages/3e/33/21a875a61057165e92227466e54ee076b73af1e21fe1b31f1e292251aa1e/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", size = 121434 }, + { url = "https://files.pythonhosted.org/packages/dd/51/68b61b90b24ca35495956b718f35a9756ef7d3dd4b3c1508056fa98d1a1b/charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", size = 118979 }, + { url = "https://files.pythonhosted.org/packages/e4/a6/7ee57823d46331ddc37dd00749c95b0edec2c79b15fc0d6e6efb532e89ac/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", size = 136582 }, + { url = "https://files.pythonhosted.org/packages/74/f1/0d9fe69ac441467b737ba7f48c68241487df2f4522dd7246d9426e7c690e/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", size = 146645 }, + { url = "https://files.pythonhosted.org/packages/05/31/e1f51c76db7be1d4aef220d29fbfa5dbb4a99165d9833dcbf166753b6dc0/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", size = 139398 }, + { url = "https://files.pythonhosted.org/packages/40/26/f35951c45070edc957ba40a5b1db3cf60a9dbb1b350c2d5bef03e01e61de/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", size = 140273 }, + { url = "https://files.pythonhosted.org/packages/07/07/7e554f2bbce3295e191f7e653ff15d55309a9ca40d0362fcdab36f01063c/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", size = 142577 }, + { url = "https://files.pythonhosted.org/packages/d8/b5/eb705c313100defa57da79277d9207dc8d8e45931035862fa64b625bfead/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", size = 137747 }, + { url = "https://files.pythonhosted.org/packages/19/28/573147271fd041d351b438a5665be8223f1dd92f273713cb882ddafe214c/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", size = 143375 }, + { url = "https://files.pythonhosted.org/packages/cf/7c/f3b682fa053cc21373c9a839e6beba7705857075686a05c72e0f8c4980ca/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", size = 148474 }, + { url = "https://files.pythonhosted.org/packages/1e/49/7ab74d4ac537ece3bc3334ee08645e231f39f7d6df6347b29a74b0537103/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", size = 140232 }, + { url = "https://files.pythonhosted.org/packages/2d/dc/9dacba68c9ac0ae781d40e1a0c0058e26302ea0660e574ddf6797a0347f7/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", size = 140859 }, + { url = "https://files.pythonhosted.org/packages/6c/c2/4a583f800c0708dd22096298e49f887b49d9746d0e78bfc1d7e29816614c/charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", size = 92509 }, + { url = "https://files.pythonhosted.org/packages/57/ec/80c8d48ac8b1741d5b963797b7c0c869335619e13d4744ca2f67fc11c6fc/charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", size = 99870 }, + { url = "https://files.pythonhosted.org/packages/d1/b2/fcedc8255ec42afee97f9e6f0145c734bbe104aac28300214593eb326f1d/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", size = 192892 }, + { url = "https://files.pythonhosted.org/packages/2e/7d/2259318c202f3d17f3fe6438149b3b9e706d1070fe3fcbb28049730bb25c/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", size = 122213 }, + { url = "https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", size = 119404 }, + { url = "https://files.pythonhosted.org/packages/99/b0/9c365f6d79a9f0f3c379ddb40a256a67aa69c59609608fe7feb6235896e1/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", size = 137275 }, + { url = "https://files.pythonhosted.org/packages/91/33/749df346e93d7a30cdcb90cbfdd41a06026317bfbfb62cd68307c1a3c543/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", size = 147518 }, + { url = "https://files.pythonhosted.org/packages/72/1a/641d5c9f59e6af4c7b53da463d07600a695b9824e20849cb6eea8a627761/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", size = 140182 }, + { url = "https://files.pythonhosted.org/packages/ee/fb/14d30eb4956408ee3ae09ad34299131fb383c47df355ddb428a7331cfa1e/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", size = 141869 }, + { url = "https://files.pythonhosted.org/packages/df/3e/a06b18788ca2eb6695c9b22325b6fde7dde0f1d1838b1792a0076f58fe9d/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", size = 144042 }, + { url = "https://files.pythonhosted.org/packages/45/59/3d27019d3b447a88fe7e7d004a1e04be220227760264cc41b405e863891b/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", size = 138275 }, + { url = "https://files.pythonhosted.org/packages/7b/ef/5eb105530b4da8ae37d506ccfa25057961b7b63d581def6f99165ea89c7e/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", size = 144819 }, + { url = "https://files.pythonhosted.org/packages/a2/51/e5023f937d7f307c948ed3e5c29c4b7a3e42ed2ee0b8cdf8f3a706089bf0/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", size = 149415 }, + { url = "https://files.pythonhosted.org/packages/24/9d/2e3ef673dfd5be0154b20363c5cdcc5606f35666544381bee15af3778239/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", size = 141212 }, + { url = "https://files.pythonhosted.org/packages/5b/ae/ce2c12fcac59cb3860b2e2d76dc405253a4475436b1861d95fe75bdea520/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", size = 142167 }, + { url = "https://files.pythonhosted.org/packages/ed/3a/a448bf035dce5da359daf9ae8a16b8a39623cc395a2ffb1620aa1bce62b0/charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", size = 93041 }, + { url = "https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63242c23851bdf00a532489fba57fef3416e40/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", size = 100397 }, + { url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543 }, ] [[package]] @@ -328,18 +334,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121, upload-time = "2023-08-17T17:29:11.868Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941, upload-time = "2023-08-17T17:29:10.08Z" }, + { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] [[package]] @@ -349,18 +355,30 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "humanfriendly" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520, upload-time = "2021-06-11T10:22:45.202Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018, upload-time = "2021-06-11T10:22:42.561Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018 }, +] + +[[package]] +name = "colorlog" +version = "6.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d3/7a/359f4d5df2353f26172b3cc39ea32daa39af8de522205f512f458923e677/colorlog-6.9.0.tar.gz", hash = "sha256:bfba54a1b93b94f54e1f4fe48395725a3d92fd2a4af702f6bd70946bdc0c6ac2", size = 16624 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/51/9b208e85196941db2f0654ad0357ca6388ab3ed67efdbfc799f35d1f83aa/colorlog-6.9.0-py3-none-any.whl", hash = "sha256:5906e71acd67cb07a71e779c47c4bcb45fb8c2993eebe9e5adcd6a6f1b283eff", size = 11424 }, ] [[package]] name = "configargparse" version = "1.7.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/4d/6c9ef746dfcc2a32e26f3860bb4a011c008c392b83eabdfb598d1a8bbe5d/configargparse-1.7.1.tar.gz", hash = "sha256:79c2ddae836a1e5914b71d58e4b9adbd9f7779d4e6351a637b7d2d9b6c46d3d9", size = 43958, upload-time = "2025-05-23T14:26:17.369Z" } +sdist = { url = "https://files.pythonhosted.org/packages/85/4d/6c9ef746dfcc2a32e26f3860bb4a011c008c392b83eabdfb598d1a8bbe5d/configargparse-1.7.1.tar.gz", hash = "sha256:79c2ddae836a1e5914b71d58e4b9adbd9f7779d4e6351a637b7d2d9b6c46d3d9", size = 43958 } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/28/d28211d29bcc3620b1fece85a65ce5bb22f18670a03cd28ea4b75ede270c/configargparse-1.7.1-py3-none-any.whl", hash = "sha256:8b586a31f9d873abd1ca527ffbe58863c99f36d896e2829779803125e83be4b6", size = 25607, upload-time = "2025-05-23T14:26:15.923Z" }, + { url = "https://files.pythonhosted.org/packages/31/28/d28211d29bcc3620b1fece85a65ce5bb22f18670a03cd28ea4b75ede270c/configargparse-1.7.1-py3-none-any.whl", hash = "sha256:8b586a31f9d873abd1ca527ffbe58863c99f36d896e2829779803125e83be4b6", size = 25607 }, ] [[package]] @@ -375,38 +393,38 @@ resolution-markers = [ dependencies = [ { name = "numpy", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/11/a3/48ddc7ae832b000952cf4be64452381d150a41a2299c2eb19237168528d1/contourpy-1.2.0.tar.gz", hash = "sha256:171f311cb758de7da13fc53af221ae47a5877be5a0843a9fe150818c51ed276a", size = 13455881, upload-time = "2023-11-03T17:01:03.144Z" } +sdist = { url = "https://files.pythonhosted.org/packages/11/a3/48ddc7ae832b000952cf4be64452381d150a41a2299c2eb19237168528d1/contourpy-1.2.0.tar.gz", hash = "sha256:171f311cb758de7da13fc53af221ae47a5877be5a0843a9fe150818c51ed276a", size = 13455881 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/ea/f6e90933d82cc5aacf52f886a1c01f47f96eba99108ca2929c7b3ef45f82/contourpy-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0274c1cb63625972c0c007ab14dd9ba9e199c36ae1a231ce45d725cbcbfd10a8", size = 256873, upload-time = "2023-11-03T16:56:34.548Z" }, - { url = "https://files.pythonhosted.org/packages/fe/26/43821d61b7ee62c1809ec852bc572aaf4c27f101ebcebbbcce29a5ee0445/contourpy-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ab459a1cbbf18e8698399c595a01f6dcc5c138220ca3ea9e7e6126232d102bb4", size = 242211, upload-time = "2023-11-03T16:56:38.028Z" }, - { url = "https://files.pythonhosted.org/packages/9b/99/c8fb63072a7573fe7682e1786a021f29f9c5f660a3aafcdce80b9ee8348d/contourpy-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fdd887f17c2f4572ce548461e4f96396681212d858cae7bd52ba3310bc6f00f", size = 293195, upload-time = "2023-11-03T16:56:41.598Z" }, - { url = "https://files.pythonhosted.org/packages/c7/a7/ae0b4bb8e0c865270d02ee619981413996dc10ddf1fd2689c938173ff62f/contourpy-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d16edfc3fc09968e09ddffada434b3bf989bf4911535e04eada58469873e28e", size = 332279, upload-time = "2023-11-03T16:56:46.08Z" }, - { url = "https://files.pythonhosted.org/packages/94/7c/682228b9085ff323fb7e946fe139072e5f21b71360cf91f36ea079d4ea95/contourpy-1.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c203f617abc0dde5792beb586f827021069fb6d403d7f4d5c2b543d87edceb9", size = 305326, upload-time = "2023-11-03T16:56:49.647Z" }, - { url = "https://files.pythonhosted.org/packages/58/56/e2c43dcfa1f9c7db4d5e3d6f5134b24ed953f4e2133a4b12f0062148db58/contourpy-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b69303ceb2e4d4f146bf82fda78891ef7bcd80c41bf16bfca3d0d7eb545448aa", size = 310732, upload-time = "2023-11-03T16:56:53.773Z" }, - { url = "https://files.pythonhosted.org/packages/94/0b/8495c4582057abc8377f945f6e11a86f1c07ad7b32fd4fdc968478cd0324/contourpy-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:884c3f9d42d7218304bc74a8a7693d172685c84bd7ab2bab1ee567b769696df9", size = 803420, upload-time = "2023-11-03T16:57:00.669Z" }, - { url = "https://files.pythonhosted.org/packages/d5/1f/40399c7da649297147d404aedaa675cc60018f48ad284630c0d1406133e3/contourpy-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4a1b1208102be6e851f20066bf0e7a96b7d48a07c9b0cfe6d0d4545c2f6cadab", size = 829204, upload-time = "2023-11-03T16:57:07.813Z" }, - { url = "https://files.pythonhosted.org/packages/8b/01/4be433b60dce7cbce8315cbcdfc016e7d25430a8b94e272355dff79cc3a8/contourpy-1.2.0-cp310-cp310-win32.whl", hash = "sha256:34b9071c040d6fe45d9826cbbe3727d20d83f1b6110d219b83eb0e2a01d79488", size = 165434, upload-time = "2023-11-03T16:57:10.601Z" }, - { url = "https://files.pythonhosted.org/packages/fd/7c/168f8343f33d861305e18c56901ef1bb675d3c7f977f435ec72751a71a54/contourpy-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:bd2f1ae63998da104f16a8b788f685e55d65760cd1929518fd94cd682bf03e41", size = 186652, upload-time = "2023-11-03T16:57:13.57Z" }, - { url = "https://files.pythonhosted.org/packages/9b/54/1dafec3c84df1d29119037330f7289db84a679cb2d5283af4ef24d89f532/contourpy-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd10c26b4eadae44783c45ad6655220426f971c61d9b239e6f7b16d5cdaaa727", size = 258243, upload-time = "2023-11-03T16:57:16.604Z" }, - { url = "https://files.pythonhosted.org/packages/5b/ac/26fa1057f62beaa2af4c55c6ac733b114a403b746cfe0ce3dc6e4aec921a/contourpy-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c6b28956b7b232ae801406e529ad7b350d3f09a4fde958dfdf3c0520cdde0dd", size = 243408, upload-time = "2023-11-03T16:57:20.021Z" }, - { url = "https://files.pythonhosted.org/packages/b7/33/cd0ecc80123f499d76d2fe2807cb4d5638ef8730735c580c8a8a03e1928e/contourpy-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebeac59e9e1eb4b84940d076d9f9a6cec0064e241818bcb6e32124cc5c3e377a", size = 294142, upload-time = "2023-11-03T16:57:23.48Z" }, - { url = "https://files.pythonhosted.org/packages/6d/75/1b7bf20bf6394e01df2c4b4b3d44d3dc280c16ddaff72724639100bd4314/contourpy-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:139d8d2e1c1dd52d78682f505e980f592ba53c9f73bd6be102233e358b401063", size = 333129, upload-time = "2023-11-03T16:57:27.141Z" }, - { url = "https://files.pythonhosted.org/packages/22/5b/fedd961dff1877e5d3b83c5201295cfdcdc2438884c2851aa7ecf6cec045/contourpy-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e9dc350fb4c58adc64df3e0703ab076f60aac06e67d48b3848c23647ae4310e", size = 307461, upload-time = "2023-11-03T16:57:30.537Z" }, - { url = "https://files.pythonhosted.org/packages/e2/83/29a63bbc72839cc6b24b5a0e3d004d4ed4e8439f26460ad9a34e39251904/contourpy-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18fc2b4ed8e4a8fe849d18dce4bd3c7ea637758c6343a1f2bae1e9bd4c9f4686", size = 313352, upload-time = "2023-11-03T16:57:34.937Z" }, - { url = "https://files.pythonhosted.org/packages/4b/c7/4bac0fc4f1e802ab47e75076d83d2e1448e0668ba6cc9000cf4e9d5bd94a/contourpy-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:16a7380e943a6d52472096cb7ad5264ecee36ed60888e2a3d3814991a0107286", size = 804127, upload-time = "2023-11-03T16:57:42.201Z" }, - { url = "https://files.pythonhosted.org/packages/e3/47/b3fd5bdc2f6ec13502d57a5bc390ffe62648605ed1689c93b0015150a784/contourpy-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8d8faf05be5ec8e02a4d86f616fc2a0322ff4a4ce26c0f09d9f7fb5330a35c95", size = 829561, upload-time = "2023-11-03T16:57:49.667Z" }, - { url = "https://files.pythonhosted.org/packages/5c/04/be16038e754169caea4d02d82f8e5cd97dece593e5ac9e05735da0afd0c5/contourpy-1.2.0-cp311-cp311-win32.whl", hash = "sha256:67b7f17679fa62ec82b7e3e611c43a016b887bd64fb933b3ae8638583006c6d6", size = 166197, upload-time = "2023-11-03T16:57:52.682Z" }, - { url = "https://files.pythonhosted.org/packages/ca/2a/d197a412ec474391ee878b1218cf2fe9c6e963903755887fc5654c06636a/contourpy-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:99ad97258985328b4f207a5e777c1b44a83bfe7cf1f87b99f9c11d4ee477c4de", size = 187556, upload-time = "2023-11-03T16:57:55.286Z" }, - { url = "https://files.pythonhosted.org/packages/4f/03/839da46999173226bead08794cbd7b4d37c9e6b02686ca74c93556b43258/contourpy-1.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:575bcaf957a25d1194903a10bc9f316c136c19f24e0985a2b9b5608bdf5dbfe0", size = 259253, upload-time = "2023-11-03T16:57:58.572Z" }, - { url = "https://files.pythonhosted.org/packages/f3/9e/8fb3f53144269d3fecdd8786d3a4686eeff55b9b35a3c0772a3f62f71e36/contourpy-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9e6c93b5b2dbcedad20a2f18ec22cae47da0d705d454308063421a3b290d9ea4", size = 242555, upload-time = "2023-11-03T16:58:01.48Z" }, - { url = "https://files.pythonhosted.org/packages/a6/85/9815ccb5a18ee8c9a46bd5ef20d02b292cd4a99c62553f38c87015f16d59/contourpy-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:464b423bc2a009088f19bdf1f232299e8b6917963e2b7e1d277da5041f33a779", size = 288108, upload-time = "2023-11-03T16:58:05.546Z" }, - { url = "https://files.pythonhosted.org/packages/5a/d9/4df5c26bd0f496c8cd7940fd53db95d07deeb98518f02f805ce570590da8/contourpy-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:68ce4788b7d93e47f84edd3f1f95acdcd142ae60bc0e5493bfd120683d2d4316", size = 330810, upload-time = "2023-11-03T16:58:09.568Z" }, - { url = "https://files.pythonhosted.org/packages/67/d4/8aae9793a0cfde72959312521ebd3aa635c260c3d580448e8db6bdcdd1aa/contourpy-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d7d1f8871998cdff5d2ff6a087e5e1780139abe2838e85b0b46b7ae6cc25399", size = 305290, upload-time = "2023-11-03T16:58:13.017Z" }, - { url = "https://files.pythonhosted.org/packages/20/84/ffddcdcc579cbf7213fd92a3578ca08a931a3bf879a22deb5a83ffc5002c/contourpy-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e739530c662a8d6d42c37c2ed52a6f0932c2d4a3e8c1f90692ad0ce1274abe0", size = 303937, upload-time = "2023-11-03T16:58:16.426Z" }, - { url = "https://files.pythonhosted.org/packages/d8/ad/6e570cf525f909da94559ed716189f92f529bc7b5f78645733c44619a0e2/contourpy-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:247b9d16535acaa766d03037d8e8fb20866d054d3c7fbf6fd1f993f11fc60ca0", size = 801977, upload-time = "2023-11-03T16:58:23.539Z" }, - { url = "https://files.pythonhosted.org/packages/36/b4/55f23482c596eca36d16fc668b147865c56fcf90353f4c57f073d8d5e532/contourpy-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:461e3ae84cd90b30f8d533f07d87c00379644205b1d33a5ea03381edc4b69431", size = 827442, upload-time = "2023-11-03T16:58:30.724Z" }, - { url = "https://files.pythonhosted.org/packages/e9/47/9c081b1f11d6053cb0aa4c46b7de2ea2849a4a8d40de81c7bc3f99773b02/contourpy-1.2.0-cp312-cp312-win32.whl", hash = "sha256:1c2559d6cffc94890b0529ea7eeecc20d6fadc1539273aa27faf503eb4656d8f", size = 165363, upload-time = "2023-11-03T16:58:33.54Z" }, - { url = "https://files.pythonhosted.org/packages/8e/ae/a6353db548bff1a592b85ae6bb80275f0a51dc25a0410d059e5b33183e36/contourpy-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:491b1917afdd8638a05b611a56d46587d5a632cabead889a5440f7c638bc6ed9", size = 187731, upload-time = "2023-11-03T16:58:36.585Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ea/f6e90933d82cc5aacf52f886a1c01f47f96eba99108ca2929c7b3ef45f82/contourpy-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0274c1cb63625972c0c007ab14dd9ba9e199c36ae1a231ce45d725cbcbfd10a8", size = 256873 }, + { url = "https://files.pythonhosted.org/packages/fe/26/43821d61b7ee62c1809ec852bc572aaf4c27f101ebcebbbcce29a5ee0445/contourpy-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ab459a1cbbf18e8698399c595a01f6dcc5c138220ca3ea9e7e6126232d102bb4", size = 242211 }, + { url = "https://files.pythonhosted.org/packages/9b/99/c8fb63072a7573fe7682e1786a021f29f9c5f660a3aafcdce80b9ee8348d/contourpy-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fdd887f17c2f4572ce548461e4f96396681212d858cae7bd52ba3310bc6f00f", size = 293195 }, + { url = "https://files.pythonhosted.org/packages/c7/a7/ae0b4bb8e0c865270d02ee619981413996dc10ddf1fd2689c938173ff62f/contourpy-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d16edfc3fc09968e09ddffada434b3bf989bf4911535e04eada58469873e28e", size = 332279 }, + { url = "https://files.pythonhosted.org/packages/94/7c/682228b9085ff323fb7e946fe139072e5f21b71360cf91f36ea079d4ea95/contourpy-1.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c203f617abc0dde5792beb586f827021069fb6d403d7f4d5c2b543d87edceb9", size = 305326 }, + { url = "https://files.pythonhosted.org/packages/58/56/e2c43dcfa1f9c7db4d5e3d6f5134b24ed953f4e2133a4b12f0062148db58/contourpy-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b69303ceb2e4d4f146bf82fda78891ef7bcd80c41bf16bfca3d0d7eb545448aa", size = 310732 }, + { url = "https://files.pythonhosted.org/packages/94/0b/8495c4582057abc8377f945f6e11a86f1c07ad7b32fd4fdc968478cd0324/contourpy-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:884c3f9d42d7218304bc74a8a7693d172685c84bd7ab2bab1ee567b769696df9", size = 803420 }, + { url = "https://files.pythonhosted.org/packages/d5/1f/40399c7da649297147d404aedaa675cc60018f48ad284630c0d1406133e3/contourpy-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4a1b1208102be6e851f20066bf0e7a96b7d48a07c9b0cfe6d0d4545c2f6cadab", size = 829204 }, + { url = "https://files.pythonhosted.org/packages/8b/01/4be433b60dce7cbce8315cbcdfc016e7d25430a8b94e272355dff79cc3a8/contourpy-1.2.0-cp310-cp310-win32.whl", hash = "sha256:34b9071c040d6fe45d9826cbbe3727d20d83f1b6110d219b83eb0e2a01d79488", size = 165434 }, + { url = "https://files.pythonhosted.org/packages/fd/7c/168f8343f33d861305e18c56901ef1bb675d3c7f977f435ec72751a71a54/contourpy-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:bd2f1ae63998da104f16a8b788f685e55d65760cd1929518fd94cd682bf03e41", size = 186652 }, + { url = "https://files.pythonhosted.org/packages/9b/54/1dafec3c84df1d29119037330f7289db84a679cb2d5283af4ef24d89f532/contourpy-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd10c26b4eadae44783c45ad6655220426f971c61d9b239e6f7b16d5cdaaa727", size = 258243 }, + { url = "https://files.pythonhosted.org/packages/5b/ac/26fa1057f62beaa2af4c55c6ac733b114a403b746cfe0ce3dc6e4aec921a/contourpy-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c6b28956b7b232ae801406e529ad7b350d3f09a4fde958dfdf3c0520cdde0dd", size = 243408 }, + { url = "https://files.pythonhosted.org/packages/b7/33/cd0ecc80123f499d76d2fe2807cb4d5638ef8730735c580c8a8a03e1928e/contourpy-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebeac59e9e1eb4b84940d076d9f9a6cec0064e241818bcb6e32124cc5c3e377a", size = 294142 }, + { url = "https://files.pythonhosted.org/packages/6d/75/1b7bf20bf6394e01df2c4b4b3d44d3dc280c16ddaff72724639100bd4314/contourpy-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:139d8d2e1c1dd52d78682f505e980f592ba53c9f73bd6be102233e358b401063", size = 333129 }, + { url = "https://files.pythonhosted.org/packages/22/5b/fedd961dff1877e5d3b83c5201295cfdcdc2438884c2851aa7ecf6cec045/contourpy-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e9dc350fb4c58adc64df3e0703ab076f60aac06e67d48b3848c23647ae4310e", size = 307461 }, + { url = "https://files.pythonhosted.org/packages/e2/83/29a63bbc72839cc6b24b5a0e3d004d4ed4e8439f26460ad9a34e39251904/contourpy-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18fc2b4ed8e4a8fe849d18dce4bd3c7ea637758c6343a1f2bae1e9bd4c9f4686", size = 313352 }, + { url = "https://files.pythonhosted.org/packages/4b/c7/4bac0fc4f1e802ab47e75076d83d2e1448e0668ba6cc9000cf4e9d5bd94a/contourpy-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:16a7380e943a6d52472096cb7ad5264ecee36ed60888e2a3d3814991a0107286", size = 804127 }, + { url = "https://files.pythonhosted.org/packages/e3/47/b3fd5bdc2f6ec13502d57a5bc390ffe62648605ed1689c93b0015150a784/contourpy-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8d8faf05be5ec8e02a4d86f616fc2a0322ff4a4ce26c0f09d9f7fb5330a35c95", size = 829561 }, + { url = "https://files.pythonhosted.org/packages/5c/04/be16038e754169caea4d02d82f8e5cd97dece593e5ac9e05735da0afd0c5/contourpy-1.2.0-cp311-cp311-win32.whl", hash = "sha256:67b7f17679fa62ec82b7e3e611c43a016b887bd64fb933b3ae8638583006c6d6", size = 166197 }, + { url = "https://files.pythonhosted.org/packages/ca/2a/d197a412ec474391ee878b1218cf2fe9c6e963903755887fc5654c06636a/contourpy-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:99ad97258985328b4f207a5e777c1b44a83bfe7cf1f87b99f9c11d4ee477c4de", size = 187556 }, + { url = "https://files.pythonhosted.org/packages/4f/03/839da46999173226bead08794cbd7b4d37c9e6b02686ca74c93556b43258/contourpy-1.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:575bcaf957a25d1194903a10bc9f316c136c19f24e0985a2b9b5608bdf5dbfe0", size = 259253 }, + { url = "https://files.pythonhosted.org/packages/f3/9e/8fb3f53144269d3fecdd8786d3a4686eeff55b9b35a3c0772a3f62f71e36/contourpy-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9e6c93b5b2dbcedad20a2f18ec22cae47da0d705d454308063421a3b290d9ea4", size = 242555 }, + { url = "https://files.pythonhosted.org/packages/a6/85/9815ccb5a18ee8c9a46bd5ef20d02b292cd4a99c62553f38c87015f16d59/contourpy-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:464b423bc2a009088f19bdf1f232299e8b6917963e2b7e1d277da5041f33a779", size = 288108 }, + { url = "https://files.pythonhosted.org/packages/5a/d9/4df5c26bd0f496c8cd7940fd53db95d07deeb98518f02f805ce570590da8/contourpy-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:68ce4788b7d93e47f84edd3f1f95acdcd142ae60bc0e5493bfd120683d2d4316", size = 330810 }, + { url = "https://files.pythonhosted.org/packages/67/d4/8aae9793a0cfde72959312521ebd3aa635c260c3d580448e8db6bdcdd1aa/contourpy-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d7d1f8871998cdff5d2ff6a087e5e1780139abe2838e85b0b46b7ae6cc25399", size = 305290 }, + { url = "https://files.pythonhosted.org/packages/20/84/ffddcdcc579cbf7213fd92a3578ca08a931a3bf879a22deb5a83ffc5002c/contourpy-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e739530c662a8d6d42c37c2ed52a6f0932c2d4a3e8c1f90692ad0ce1274abe0", size = 303937 }, + { url = "https://files.pythonhosted.org/packages/d8/ad/6e570cf525f909da94559ed716189f92f529bc7b5f78645733c44619a0e2/contourpy-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:247b9d16535acaa766d03037d8e8fb20866d054d3c7fbf6fd1f993f11fc60ca0", size = 801977 }, + { url = "https://files.pythonhosted.org/packages/36/b4/55f23482c596eca36d16fc668b147865c56fcf90353f4c57f073d8d5e532/contourpy-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:461e3ae84cd90b30f8d533f07d87c00379644205b1d33a5ea03381edc4b69431", size = 827442 }, + { url = "https://files.pythonhosted.org/packages/e9/47/9c081b1f11d6053cb0aa4c46b7de2ea2849a4a8d40de81c7bc3f99773b02/contourpy-1.2.0-cp312-cp312-win32.whl", hash = "sha256:1c2559d6cffc94890b0529ea7eeecc20d6fadc1539273aa27faf503eb4656d8f", size = 165363 }, + { url = "https://files.pythonhosted.org/packages/8e/ae/a6353db548bff1a592b85ae6bb80275f0a51dc25a0410d059e5b33183e36/contourpy-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:491b1917afdd8638a05b611a56d46587d5a632cabead889a5440f7c638bc6ed9", size = 187731 }, ] [[package]] @@ -430,164 +448,138 @@ resolution-markers = [ dependencies = [ { name = "numpy", marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174 } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, - { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, - { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, - { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, - { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, - { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, - { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, - { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, - { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, - { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, - { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, - { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, - { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, - { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, - { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, - { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, - { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, - { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, - { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, - { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, - { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, - { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, - { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, - { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, - { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, - { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, - { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, - { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, - { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, - { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, - { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, - { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, - { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, - { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, - { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, - { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, - { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, - { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, - { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, - { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, - { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, - { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, - { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, - { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, - { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, - { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, - { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, - { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, - { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, - { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, - { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, - { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, - { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, - { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, - { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, - { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, - { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, - { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, - { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, - { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, - { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, - { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, - { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, - { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, - { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, - { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, - { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, - { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, - { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, + { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773 }, + { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149 }, + { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222 }, + { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234 }, + { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555 }, + { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238 }, + { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218 }, + { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867 }, + { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677 }, + { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234 }, + { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123 }, + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419 }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979 }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653 }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536 }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397 }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601 }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288 }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386 }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018 }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567 }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655 }, + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257 }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034 }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672 }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234 }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169 }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859 }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062 }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932 }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024 }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578 }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524 }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730 }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897 }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751 }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486 }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106 }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548 }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297 }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023 }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157 }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570 }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713 }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189 }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251 }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810 }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871 }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264 }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819 }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650 }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833 }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692 }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424 }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300 }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769 }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892 }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748 }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554 }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118 }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555 }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295 }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027 }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428 }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331 }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831 }, + { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809 }, + { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593 }, + { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202 }, + { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207 }, + { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315 }, ] [[package]] name = "coverage" -version = "7.10.6" +version = "7.6.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/14/70/025b179c993f019105b79575ac6edb5e084fb0f0e63f15cdebef4e454fb5/coverage-7.10.6.tar.gz", hash = "sha256:f644a3ae5933a552a29dbb9aa2f90c677a875f80ebea028e5a52a4f429044b90", size = 823736, upload-time = "2025-08-29T15:35:16.668Z" } +sdist = { url = "https://files.pythonhosted.org/packages/52/12/3669b6382792783e92046730ad3327f53b2726f0603f4c311c4da4824222/coverage-7.6.4.tar.gz", hash = "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73", size = 798716 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/1d/2e64b43d978b5bd184e0756a41415597dfef30fcbd90b747474bd749d45f/coverage-7.10.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:70e7bfbd57126b5554aa482691145f798d7df77489a177a6bef80de78860a356", size = 217025, upload-time = "2025-08-29T15:32:57.169Z" }, - { url = "https://files.pythonhosted.org/packages/23/62/b1e0f513417c02cc10ef735c3ee5186df55f190f70498b3702d516aad06f/coverage-7.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e41be6f0f19da64af13403e52f2dec38bbc2937af54df8ecef10850ff8d35301", size = 217419, upload-time = "2025-08-29T15:32:59.908Z" }, - { url = "https://files.pythonhosted.org/packages/e7/16/b800640b7a43e7c538429e4d7223e0a94fd72453a1a048f70bf766f12e96/coverage-7.10.6-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c61fc91ab80b23f5fddbee342d19662f3d3328173229caded831aa0bd7595460", size = 244180, upload-time = "2025-08-29T15:33:01.608Z" }, - { url = "https://files.pythonhosted.org/packages/fb/6f/5e03631c3305cad187eaf76af0b559fff88af9a0b0c180d006fb02413d7a/coverage-7.10.6-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10356fdd33a7cc06e8051413140bbdc6f972137508a3572e3f59f805cd2832fd", size = 245992, upload-time = "2025-08-29T15:33:03.239Z" }, - { url = "https://files.pythonhosted.org/packages/eb/a1/f30ea0fb400b080730125b490771ec62b3375789f90af0bb68bfb8a921d7/coverage-7.10.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80b1695cf7c5ebe7b44bf2521221b9bb8cdf69b1f24231149a7e3eb1ae5fa2fb", size = 247851, upload-time = "2025-08-29T15:33:04.603Z" }, - { url = "https://files.pythonhosted.org/packages/02/8e/cfa8fee8e8ef9a6bb76c7bef039f3302f44e615d2194161a21d3d83ac2e9/coverage-7.10.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2e4c33e6378b9d52d3454bd08847a8651f4ed23ddbb4a0520227bd346382bbc6", size = 245891, upload-time = "2025-08-29T15:33:06.176Z" }, - { url = "https://files.pythonhosted.org/packages/93/a9/51be09b75c55c4f6c16d8d73a6a1d46ad764acca0eab48fa2ffaef5958fe/coverage-7.10.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c8a3ec16e34ef980a46f60dc6ad86ec60f763c3f2fa0db6d261e6e754f72e945", size = 243909, upload-time = "2025-08-29T15:33:07.74Z" }, - { url = "https://files.pythonhosted.org/packages/e9/a6/ba188b376529ce36483b2d585ca7bdac64aacbe5aa10da5978029a9c94db/coverage-7.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7d79dabc0a56f5af990cc6da9ad1e40766e82773c075f09cc571e2076fef882e", size = 244786, upload-time = "2025-08-29T15:33:08.965Z" }, - { url = "https://files.pythonhosted.org/packages/d0/4c/37ed872374a21813e0d3215256180c9a382c3f5ced6f2e5da0102fc2fd3e/coverage-7.10.6-cp310-cp310-win32.whl", hash = "sha256:86b9b59f2b16e981906e9d6383eb6446d5b46c278460ae2c36487667717eccf1", size = 219521, upload-time = "2025-08-29T15:33:10.599Z" }, - { url = "https://files.pythonhosted.org/packages/8e/36/9311352fdc551dec5b973b61f4e453227ce482985a9368305880af4f85dd/coverage-7.10.6-cp310-cp310-win_amd64.whl", hash = "sha256:e132b9152749bd33534e5bd8565c7576f135f157b4029b975e15ee184325f528", size = 220417, upload-time = "2025-08-29T15:33:11.907Z" }, - { url = "https://files.pythonhosted.org/packages/d4/16/2bea27e212c4980753d6d563a0803c150edeaaddb0771a50d2afc410a261/coverage-7.10.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c706db3cabb7ceef779de68270150665e710b46d56372455cd741184f3868d8f", size = 217129, upload-time = "2025-08-29T15:33:13.575Z" }, - { url = "https://files.pythonhosted.org/packages/2a/51/e7159e068831ab37e31aac0969d47b8c5ee25b7d307b51e310ec34869315/coverage-7.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e0c38dc289e0508ef68ec95834cb5d2e96fdbe792eaccaa1bccac3966bbadcc", size = 217532, upload-time = "2025-08-29T15:33:14.872Z" }, - { url = "https://files.pythonhosted.org/packages/e7/c0/246ccbea53d6099325d25cd208df94ea435cd55f0db38099dd721efc7a1f/coverage-7.10.6-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:752a3005a1ded28f2f3a6e8787e24f28d6abe176ca64677bcd8d53d6fe2ec08a", size = 247931, upload-time = "2025-08-29T15:33:16.142Z" }, - { url = "https://files.pythonhosted.org/packages/7d/fb/7435ef8ab9b2594a6e3f58505cc30e98ae8b33265d844007737946c59389/coverage-7.10.6-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:689920ecfd60f992cafca4f5477d55720466ad2c7fa29bb56ac8d44a1ac2b47a", size = 249864, upload-time = "2025-08-29T15:33:17.434Z" }, - { url = "https://files.pythonhosted.org/packages/51/f8/d9d64e8da7bcddb094d511154824038833c81e3a039020a9d6539bf303e9/coverage-7.10.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec98435796d2624d6905820a42f82149ee9fc4f2d45c2c5bc5a44481cc50db62", size = 251969, upload-time = "2025-08-29T15:33:18.822Z" }, - { url = "https://files.pythonhosted.org/packages/43/28/c43ba0ef19f446d6463c751315140d8f2a521e04c3e79e5c5fe211bfa430/coverage-7.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b37201ce4a458c7a758ecc4efa92fa8ed783c66e0fa3c42ae19fc454a0792153", size = 249659, upload-time = "2025-08-29T15:33:20.407Z" }, - { url = "https://files.pythonhosted.org/packages/79/3e/53635bd0b72beaacf265784508a0b386defc9ab7fad99ff95f79ce9db555/coverage-7.10.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2904271c80898663c810a6b067920a61dd8d38341244a3605bd31ab55250dad5", size = 247714, upload-time = "2025-08-29T15:33:21.751Z" }, - { url = "https://files.pythonhosted.org/packages/4c/55/0964aa87126624e8c159e32b0bc4e84edef78c89a1a4b924d28dd8265625/coverage-7.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5aea98383463d6e1fa4e95416d8de66f2d0cb588774ee20ae1b28df826bcb619", size = 248351, upload-time = "2025-08-29T15:33:23.105Z" }, - { url = "https://files.pythonhosted.org/packages/eb/ab/6cfa9dc518c6c8e14a691c54e53a9433ba67336c760607e299bfcf520cb1/coverage-7.10.6-cp311-cp311-win32.whl", hash = "sha256:e3fb1fa01d3598002777dd259c0c2e6d9d5e10e7222976fc8e03992f972a2cba", size = 219562, upload-time = "2025-08-29T15:33:24.717Z" }, - { url = "https://files.pythonhosted.org/packages/5b/18/99b25346690cbc55922e7cfef06d755d4abee803ef335baff0014268eff4/coverage-7.10.6-cp311-cp311-win_amd64.whl", hash = "sha256:f35ed9d945bece26553d5b4c8630453169672bea0050a564456eb88bdffd927e", size = 220453, upload-time = "2025-08-29T15:33:26.482Z" }, - { url = "https://files.pythonhosted.org/packages/d8/ed/81d86648a07ccb124a5cf1f1a7788712b8d7216b593562683cd5c9b0d2c1/coverage-7.10.6-cp311-cp311-win_arm64.whl", hash = "sha256:99e1a305c7765631d74b98bf7dbf54eeea931f975e80f115437d23848ee8c27c", size = 219127, upload-time = "2025-08-29T15:33:27.777Z" }, - { url = "https://files.pythonhosted.org/packages/26/06/263f3305c97ad78aab066d116b52250dd316e74fcc20c197b61e07eb391a/coverage-7.10.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b2dd6059938063a2c9fee1af729d4f2af28fd1a545e9b7652861f0d752ebcea", size = 217324, upload-time = "2025-08-29T15:33:29.06Z" }, - { url = "https://files.pythonhosted.org/packages/e9/60/1e1ded9a4fe80d843d7d53b3e395c1db3ff32d6c301e501f393b2e6c1c1f/coverage-7.10.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:388d80e56191bf846c485c14ae2bc8898aa3124d9d35903fef7d907780477634", size = 217560, upload-time = "2025-08-29T15:33:30.748Z" }, - { url = "https://files.pythonhosted.org/packages/b8/25/52136173c14e26dfed8b106ed725811bb53c30b896d04d28d74cb64318b3/coverage-7.10.6-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:90cb5b1a4670662719591aa92d0095bb41714970c0b065b02a2610172dbf0af6", size = 249053, upload-time = "2025-08-29T15:33:32.041Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1d/ae25a7dc58fcce8b172d42ffe5313fc267afe61c97fa872b80ee72d9515a/coverage-7.10.6-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:961834e2f2b863a0e14260a9a273aff07ff7818ab6e66d2addf5628590c628f9", size = 251802, upload-time = "2025-08-29T15:33:33.625Z" }, - { url = "https://files.pythonhosted.org/packages/f5/7a/1f561d47743710fe996957ed7c124b421320f150f1d38523d8d9102d3e2a/coverage-7.10.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf9a19f5012dab774628491659646335b1928cfc931bf8d97b0d5918dd58033c", size = 252935, upload-time = "2025-08-29T15:33:34.909Z" }, - { url = "https://files.pythonhosted.org/packages/6c/ad/8b97cd5d28aecdfde792dcbf646bac141167a5cacae2cd775998b45fabb5/coverage-7.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99c4283e2a0e147b9c9cc6bc9c96124de9419d6044837e9799763a0e29a7321a", size = 250855, upload-time = "2025-08-29T15:33:36.922Z" }, - { url = "https://files.pythonhosted.org/packages/33/6a/95c32b558d9a61858ff9d79580d3877df3eb5bc9eed0941b1f187c89e143/coverage-7.10.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:282b1b20f45df57cc508c1e033403f02283adfb67d4c9c35a90281d81e5c52c5", size = 248974, upload-time = "2025-08-29T15:33:38.175Z" }, - { url = "https://files.pythonhosted.org/packages/0d/9c/8ce95dee640a38e760d5b747c10913e7a06554704d60b41e73fdea6a1ffd/coverage-7.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cdbe264f11afd69841bd8c0d83ca10b5b32853263ee62e6ac6a0ab63895f972", size = 250409, upload-time = "2025-08-29T15:33:39.447Z" }, - { url = "https://files.pythonhosted.org/packages/04/12/7a55b0bdde78a98e2eb2356771fd2dcddb96579e8342bb52aa5bc52e96f0/coverage-7.10.6-cp312-cp312-win32.whl", hash = "sha256:a517feaf3a0a3eca1ee985d8373135cfdedfbba3882a5eab4362bda7c7cf518d", size = 219724, upload-time = "2025-08-29T15:33:41.172Z" }, - { url = "https://files.pythonhosted.org/packages/36/4a/32b185b8b8e327802c9efce3d3108d2fe2d9d31f153a0f7ecfd59c773705/coverage-7.10.6-cp312-cp312-win_amd64.whl", hash = "sha256:856986eadf41f52b214176d894a7de05331117f6035a28ac0016c0f63d887629", size = 220536, upload-time = "2025-08-29T15:33:42.524Z" }, - { url = "https://files.pythonhosted.org/packages/08/3a/d5d8dc703e4998038c3099eaf77adddb00536a3cec08c8dcd556a36a3eb4/coverage-7.10.6-cp312-cp312-win_arm64.whl", hash = "sha256:acf36b8268785aad739443fa2780c16260ee3fa09d12b3a70f772ef100939d80", size = 219171, upload-time = "2025-08-29T15:33:43.974Z" }, - { url = "https://files.pythonhosted.org/packages/bd/e7/917e5953ea29a28c1057729c1d5af9084ab6d9c66217523fd0e10f14d8f6/coverage-7.10.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ffea0575345e9ee0144dfe5701aa17f3ba546f8c3bb48db62ae101afb740e7d6", size = 217351, upload-time = "2025-08-29T15:33:45.438Z" }, - { url = "https://files.pythonhosted.org/packages/eb/86/2e161b93a4f11d0ea93f9bebb6a53f113d5d6e416d7561ca41bb0a29996b/coverage-7.10.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:95d91d7317cde40a1c249d6b7382750b7e6d86fad9d8eaf4fa3f8f44cf171e80", size = 217600, upload-time = "2025-08-29T15:33:47.269Z" }, - { url = "https://files.pythonhosted.org/packages/0e/66/d03348fdd8df262b3a7fb4ee5727e6e4936e39e2f3a842e803196946f200/coverage-7.10.6-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e23dd5408fe71a356b41baa82892772a4cefcf758f2ca3383d2aa39e1b7a003", size = 248600, upload-time = "2025-08-29T15:33:48.953Z" }, - { url = "https://files.pythonhosted.org/packages/73/dd/508420fb47d09d904d962f123221bc249f64b5e56aa93d5f5f7603be475f/coverage-7.10.6-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0f3f56e4cb573755e96a16501a98bf211f100463d70275759e73f3cbc00d4f27", size = 251206, upload-time = "2025-08-29T15:33:50.697Z" }, - { url = "https://files.pythonhosted.org/packages/e9/1f/9020135734184f439da85c70ea78194c2730e56c2d18aee6e8ff1719d50d/coverage-7.10.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db4a1d897bbbe7339946ffa2fe60c10cc81c43fab8b062d3fcb84188688174a4", size = 252478, upload-time = "2025-08-29T15:33:52.303Z" }, - { url = "https://files.pythonhosted.org/packages/a4/a4/3d228f3942bb5a2051fde28c136eea23a761177dc4ff4ef54533164ce255/coverage-7.10.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d8fd7879082953c156d5b13c74aa6cca37f6a6f4747b39538504c3f9c63d043d", size = 250637, upload-time = "2025-08-29T15:33:53.67Z" }, - { url = "https://files.pythonhosted.org/packages/36/e3/293dce8cdb9a83de971637afc59b7190faad60603b40e32635cbd15fbf61/coverage-7.10.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:28395ca3f71cd103b8c116333fa9db867f3a3e1ad6a084aa3725ae002b6583bc", size = 248529, upload-time = "2025-08-29T15:33:55.022Z" }, - { url = "https://files.pythonhosted.org/packages/90/26/64eecfa214e80dd1d101e420cab2901827de0e49631d666543d0e53cf597/coverage-7.10.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:61c950fc33d29c91b9e18540e1aed7d9f6787cc870a3e4032493bbbe641d12fc", size = 250143, upload-time = "2025-08-29T15:33:56.386Z" }, - { url = "https://files.pythonhosted.org/packages/3e/70/bd80588338f65ea5b0d97e424b820fb4068b9cfb9597fbd91963086e004b/coverage-7.10.6-cp313-cp313-win32.whl", hash = "sha256:160c00a5e6b6bdf4e5984b0ef21fc860bc94416c41b7df4d63f536d17c38902e", size = 219770, upload-time = "2025-08-29T15:33:58.063Z" }, - { url = "https://files.pythonhosted.org/packages/a7/14/0b831122305abcc1060c008f6c97bbdc0a913ab47d65070a01dc50293c2b/coverage-7.10.6-cp313-cp313-win_amd64.whl", hash = "sha256:628055297f3e2aa181464c3808402887643405573eb3d9de060d81531fa79d32", size = 220566, upload-time = "2025-08-29T15:33:59.766Z" }, - { url = "https://files.pythonhosted.org/packages/83/c6/81a83778c1f83f1a4a168ed6673eeedc205afb562d8500175292ca64b94e/coverage-7.10.6-cp313-cp313-win_arm64.whl", hash = "sha256:df4ec1f8540b0bcbe26ca7dd0f541847cc8a108b35596f9f91f59f0c060bfdd2", size = 219195, upload-time = "2025-08-29T15:34:01.191Z" }, - { url = "https://files.pythonhosted.org/packages/d7/1c/ccccf4bf116f9517275fa85047495515add43e41dfe8e0bef6e333c6b344/coverage-7.10.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c9a8b7a34a4de3ed987f636f71881cd3b8339f61118b1aa311fbda12741bff0b", size = 218059, upload-time = "2025-08-29T15:34:02.91Z" }, - { url = "https://files.pythonhosted.org/packages/92/97/8a3ceff833d27c7492af4f39d5da6761e9ff624831db9e9f25b3886ddbca/coverage-7.10.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dd5af36092430c2b075cee966719898f2ae87b636cefb85a653f1d0ba5d5393", size = 218287, upload-time = "2025-08-29T15:34:05.106Z" }, - { url = "https://files.pythonhosted.org/packages/92/d8/50b4a32580cf41ff0423777a2791aaf3269ab60c840b62009aec12d3970d/coverage-7.10.6-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b0353b0f0850d49ada66fdd7d0c7cdb0f86b900bb9e367024fd14a60cecc1e27", size = 259625, upload-time = "2025-08-29T15:34:06.575Z" }, - { url = "https://files.pythonhosted.org/packages/7e/7e/6a7df5a6fb440a0179d94a348eb6616ed4745e7df26bf2a02bc4db72c421/coverage-7.10.6-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d6b9ae13d5d3e8aeca9ca94198aa7b3ebbc5acfada557d724f2a1f03d2c0b0df", size = 261801, upload-time = "2025-08-29T15:34:08.006Z" }, - { url = "https://files.pythonhosted.org/packages/3a/4c/a270a414f4ed5d196b9d3d67922968e768cd971d1b251e1b4f75e9362f75/coverage-7.10.6-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:675824a363cc05781b1527b39dc2587b8984965834a748177ee3c37b64ffeafb", size = 264027, upload-time = "2025-08-29T15:34:09.806Z" }, - { url = "https://files.pythonhosted.org/packages/9c/8b/3210d663d594926c12f373c5370bf1e7c5c3a427519a8afa65b561b9a55c/coverage-7.10.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:692d70ea725f471a547c305f0d0fc6a73480c62fb0da726370c088ab21aed282", size = 261576, upload-time = "2025-08-29T15:34:11.585Z" }, - { url = "https://files.pythonhosted.org/packages/72/d0/e1961eff67e9e1dba3fc5eb7a4caf726b35a5b03776892da8d79ec895775/coverage-7.10.6-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:851430a9a361c7a8484a36126d1d0ff8d529d97385eacc8dfdc9bfc8c2d2cbe4", size = 259341, upload-time = "2025-08-29T15:34:13.159Z" }, - { url = "https://files.pythonhosted.org/packages/3a/06/d6478d152cd189b33eac691cba27a40704990ba95de49771285f34a5861e/coverage-7.10.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d9369a23186d189b2fc95cc08b8160ba242057e887d766864f7adf3c46b2df21", size = 260468, upload-time = "2025-08-29T15:34:14.571Z" }, - { url = "https://files.pythonhosted.org/packages/ed/73/737440247c914a332f0b47f7598535b29965bf305e19bbc22d4c39615d2b/coverage-7.10.6-cp313-cp313t-win32.whl", hash = "sha256:92be86fcb125e9bda0da7806afd29a3fd33fdf58fba5d60318399adf40bf37d0", size = 220429, upload-time = "2025-08-29T15:34:16.394Z" }, - { url = "https://files.pythonhosted.org/packages/bd/76/b92d3214740f2357ef4a27c75a526eb6c28f79c402e9f20a922c295c05e2/coverage-7.10.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6b3039e2ca459a70c79523d39347d83b73f2f06af5624905eba7ec34d64d80b5", size = 221493, upload-time = "2025-08-29T15:34:17.835Z" }, - { url = "https://files.pythonhosted.org/packages/fc/8e/6dcb29c599c8a1f654ec6cb68d76644fe635513af16e932d2d4ad1e5ac6e/coverage-7.10.6-cp313-cp313t-win_arm64.whl", hash = "sha256:3fb99d0786fe17b228eab663d16bee2288e8724d26a199c29325aac4b0319b9b", size = 219757, upload-time = "2025-08-29T15:34:19.248Z" }, - { url = "https://files.pythonhosted.org/packages/d3/aa/76cf0b5ec00619ef208da4689281d48b57f2c7fde883d14bf9441b74d59f/coverage-7.10.6-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6008a021907be8c4c02f37cdc3ffb258493bdebfeaf9a839f9e71dfdc47b018e", size = 217331, upload-time = "2025-08-29T15:34:20.846Z" }, - { url = "https://files.pythonhosted.org/packages/65/91/8e41b8c7c505d398d7730206f3cbb4a875a35ca1041efc518051bfce0f6b/coverage-7.10.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5e75e37f23eb144e78940b40395b42f2321951206a4f50e23cfd6e8a198d3ceb", size = 217607, upload-time = "2025-08-29T15:34:22.433Z" }, - { url = "https://files.pythonhosted.org/packages/87/7f/f718e732a423d442e6616580a951b8d1ec3575ea48bcd0e2228386805e79/coverage-7.10.6-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0f7cb359a448e043c576f0da00aa8bfd796a01b06aa610ca453d4dde09cc1034", size = 248663, upload-time = "2025-08-29T15:34:24.425Z" }, - { url = "https://files.pythonhosted.org/packages/e6/52/c1106120e6d801ac03e12b5285e971e758e925b6f82ee9b86db3aa10045d/coverage-7.10.6-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c68018e4fc4e14b5668f1353b41ccf4bc83ba355f0e1b3836861c6f042d89ac1", size = 251197, upload-time = "2025-08-29T15:34:25.906Z" }, - { url = "https://files.pythonhosted.org/packages/3d/ec/3a8645b1bb40e36acde9c0609f08942852a4af91a937fe2c129a38f2d3f5/coverage-7.10.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cd4b2b0707fc55afa160cd5fc33b27ccbf75ca11d81f4ec9863d5793fc6df56a", size = 252551, upload-time = "2025-08-29T15:34:27.337Z" }, - { url = "https://files.pythonhosted.org/packages/a1/70/09ecb68eeb1155b28a1d16525fd3a9b65fbe75337311a99830df935d62b6/coverage-7.10.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4cec13817a651f8804a86e4f79d815b3b28472c910e099e4d5a0e8a3b6a1d4cb", size = 250553, upload-time = "2025-08-29T15:34:29.065Z" }, - { url = "https://files.pythonhosted.org/packages/c6/80/47df374b893fa812e953b5bc93dcb1427a7b3d7a1a7d2db33043d17f74b9/coverage-7.10.6-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f2a6a8e06bbda06f78739f40bfb56c45d14eb8249d0f0ea6d4b3d48e1f7c695d", size = 248486, upload-time = "2025-08-29T15:34:30.897Z" }, - { url = "https://files.pythonhosted.org/packages/4a/65/9f98640979ecee1b0d1a7164b589de720ddf8100d1747d9bbdb84be0c0fb/coverage-7.10.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:081b98395ced0d9bcf60ada7661a0b75f36b78b9d7e39ea0790bb4ed8da14747", size = 249981, upload-time = "2025-08-29T15:34:32.365Z" }, - { url = "https://files.pythonhosted.org/packages/1f/55/eeb6603371e6629037f47bd25bef300387257ed53a3c5fdb159b7ac8c651/coverage-7.10.6-cp314-cp314-win32.whl", hash = "sha256:6937347c5d7d069ee776b2bf4e1212f912a9f1f141a429c475e6089462fcecc5", size = 220054, upload-time = "2025-08-29T15:34:34.124Z" }, - { url = "https://files.pythonhosted.org/packages/15/d1/a0912b7611bc35412e919a2cd59ae98e7ea3b475e562668040a43fb27897/coverage-7.10.6-cp314-cp314-win_amd64.whl", hash = "sha256:adec1d980fa07e60b6ef865f9e5410ba760e4e1d26f60f7e5772c73b9a5b0713", size = 220851, upload-time = "2025-08-29T15:34:35.651Z" }, - { url = "https://files.pythonhosted.org/packages/ef/2d/11880bb8ef80a45338e0b3e0725e4c2d73ffbb4822c29d987078224fd6a5/coverage-7.10.6-cp314-cp314-win_arm64.whl", hash = "sha256:a80f7aef9535442bdcf562e5a0d5a5538ce8abe6bb209cfbf170c462ac2c2a32", size = 219429, upload-time = "2025-08-29T15:34:37.16Z" }, - { url = "https://files.pythonhosted.org/packages/83/c0/1f00caad775c03a700146f55536ecd097a881ff08d310a58b353a1421be0/coverage-7.10.6-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0de434f4fbbe5af4fa7989521c655c8c779afb61c53ab561b64dcee6149e4c65", size = 218080, upload-time = "2025-08-29T15:34:38.919Z" }, - { url = "https://files.pythonhosted.org/packages/a9/c4/b1c5d2bd7cc412cbeb035e257fd06ed4e3e139ac871d16a07434e145d18d/coverage-7.10.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6e31b8155150c57e5ac43ccd289d079eb3f825187d7c66e755a055d2c85794c6", size = 218293, upload-time = "2025-08-29T15:34:40.425Z" }, - { url = "https://files.pythonhosted.org/packages/3f/07/4468d37c94724bf6ec354e4ec2f205fda194343e3e85fd2e59cec57e6a54/coverage-7.10.6-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:98cede73eb83c31e2118ae8d379c12e3e42736903a8afcca92a7218e1f2903b0", size = 259800, upload-time = "2025-08-29T15:34:41.996Z" }, - { url = "https://files.pythonhosted.org/packages/82/d8/f8fb351be5fee31690cd8da768fd62f1cfab33c31d9f7baba6cd8960f6b8/coverage-7.10.6-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f863c08f4ff6b64fa8045b1e3da480f5374779ef187f07b82e0538c68cb4ff8e", size = 261965, upload-time = "2025-08-29T15:34:43.61Z" }, - { url = "https://files.pythonhosted.org/packages/e8/70/65d4d7cfc75c5c6eb2fed3ee5cdf420fd8ae09c4808723a89a81d5b1b9c3/coverage-7.10.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b38261034fda87be356f2c3f42221fdb4171c3ce7658066ae449241485390d5", size = 264220, upload-time = "2025-08-29T15:34:45.387Z" }, - { url = "https://files.pythonhosted.org/packages/98/3c/069df106d19024324cde10e4ec379fe2fb978017d25e97ebee23002fbadf/coverage-7.10.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e93b1476b79eae849dc3872faeb0bf7948fd9ea34869590bc16a2a00b9c82a7", size = 261660, upload-time = "2025-08-29T15:34:47.288Z" }, - { url = "https://files.pythonhosted.org/packages/fc/8a/2974d53904080c5dc91af798b3a54a4ccb99a45595cc0dcec6eb9616a57d/coverage-7.10.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ff8a991f70f4c0cf53088abf1e3886edcc87d53004c7bb94e78650b4d3dac3b5", size = 259417, upload-time = "2025-08-29T15:34:48.779Z" }, - { url = "https://files.pythonhosted.org/packages/30/38/9616a6b49c686394b318974d7f6e08f38b8af2270ce7488e879888d1e5db/coverage-7.10.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ac765b026c9f33044419cbba1da913cfb82cca1b60598ac1c7a5ed6aac4621a0", size = 260567, upload-time = "2025-08-29T15:34:50.718Z" }, - { url = "https://files.pythonhosted.org/packages/76/16/3ed2d6312b371a8cf804abf4e14895b70e4c3491c6e53536d63fd0958a8d/coverage-7.10.6-cp314-cp314t-win32.whl", hash = "sha256:441c357d55f4936875636ef2cfb3bee36e466dcf50df9afbd398ce79dba1ebb7", size = 220831, upload-time = "2025-08-29T15:34:52.653Z" }, - { url = "https://files.pythonhosted.org/packages/d5/e5/d38d0cb830abede2adb8b147770d2a3d0e7fecc7228245b9b1ae6c24930a/coverage-7.10.6-cp314-cp314t-win_amd64.whl", hash = "sha256:073711de3181b2e204e4870ac83a7c4853115b42e9cd4d145f2231e12d670930", size = 221950, upload-time = "2025-08-29T15:34:54.212Z" }, - { url = "https://files.pythonhosted.org/packages/f4/51/e48e550f6279349895b0ffcd6d2a690e3131ba3a7f4eafccc141966d4dea/coverage-7.10.6-cp314-cp314t-win_arm64.whl", hash = "sha256:137921f2bac5559334ba66122b753db6dc5d1cf01eb7b64eb412bb0d064ef35b", size = 219969, upload-time = "2025-08-29T15:34:55.83Z" }, - { url = "https://files.pythonhosted.org/packages/44/0c/50db5379b615854b5cf89146f8f5bd1d5a9693d7f3a987e269693521c404/coverage-7.10.6-py3-none-any.whl", hash = "sha256:92c4ecf6bf11b2e85fd4d8204814dc26e6a19f0c9d938c207c5cb0eadfcabbe3", size = 208986, upload-time = "2025-08-29T15:35:14.506Z" }, + { url = "https://files.pythonhosted.org/packages/a5/93/4ad92f71e28ece5c0326e5f4a6630aa4928a8846654a65cfff69b49b95b9/coverage-7.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f8ae553cba74085db385d489c7a792ad66f7f9ba2ee85bfa508aeb84cf0ba07", size = 206713 }, + { url = "https://files.pythonhosted.org/packages/01/ae/747a580b1eda3f2e431d87de48f0604bd7bc92e52a1a95185a4aa585bc47/coverage-7.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8165b796df0bd42e10527a3f493c592ba494f16ef3c8b531288e3d0d72c1f6f0", size = 207149 }, + { url = "https://files.pythonhosted.org/packages/07/1a/1f573f8a6145f6d4c9130bbc120e0024daf1b24cf2a78d7393fa6eb6aba7/coverage-7.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c8b95bf47db6d19096a5e052ffca0a05f335bc63cef281a6e8fe864d450a72", size = 235584 }, + { url = "https://files.pythonhosted.org/packages/40/42/c8523f2e4db34aa9389caee0d3688b6ada7a84fcc782e943a868a7f302bd/coverage-7.6.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ed9281d1b52628e81393f5eaee24a45cbd64965f41857559c2b7ff19385df51", size = 233486 }, + { url = "https://files.pythonhosted.org/packages/8d/95/565c310fffa16ede1a042e9ea1ca3962af0d8eb5543bc72df6b91dc0c3d5/coverage-7.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0809082ee480bb8f7416507538243c8863ac74fd8a5d2485c46f0f7499f2b491", size = 234649 }, + { url = "https://files.pythonhosted.org/packages/d5/81/3b550674d98968ec29c92e3e8650682be6c8b1fa7581a059e7e12e74c431/coverage-7.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d541423cdd416b78626b55f123412fcf979d22a2c39fce251b350de38c15c15b", size = 233744 }, + { url = "https://files.pythonhosted.org/packages/0d/70/d66c7f51b3e33aabc5ea9f9624c1c9d9655472962270eb5e7b0d32707224/coverage-7.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58809e238a8a12a625c70450b48e8767cff9eb67c62e6154a642b21ddf79baea", size = 232204 }, + { url = "https://files.pythonhosted.org/packages/23/2d/2b3a2dbed7a5f40693404c8a09e779d7c1a5fbed089d3e7224c002129ec8/coverage-7.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c9b8e184898ed014884ca84c70562b4a82cbc63b044d366fedc68bc2b2f3394a", size = 233335 }, + { url = "https://files.pythonhosted.org/packages/5a/4f/92d1d2ad720d698a4e71c176eacf531bfb8e0721d5ad560556f2c484a513/coverage-7.6.4-cp310-cp310-win32.whl", hash = "sha256:6bd818b7ea14bc6e1f06e241e8234508b21edf1b242d49831831a9450e2f35fa", size = 209435 }, + { url = "https://files.pythonhosted.org/packages/c7/b9/cdf158e7991e2287bcf9082670928badb73d310047facac203ff8dcd5ff3/coverage-7.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:06babbb8f4e74b063dbaeb74ad68dfce9186c595a15f11f5d5683f748fa1d172", size = 210243 }, + { url = "https://files.pythonhosted.org/packages/87/31/9c0cf84f0dfcbe4215b7eb95c31777cdc0483c13390e69584c8150c85175/coverage-7.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:73d2b73584446e66ee633eaad1a56aad577c077f46c35ca3283cd687b7715b0b", size = 206819 }, + { url = "https://files.pythonhosted.org/packages/53/ed/a38401079ad320ad6e054a01ec2b61d270511aeb3c201c80e99c841229d5/coverage-7.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51b44306032045b383a7a8a2c13878de375117946d68dcb54308111f39775a25", size = 207263 }, + { url = "https://files.pythonhosted.org/packages/20/e7/c3ad33b179ab4213f0d70da25a9c214d52464efa11caeab438592eb1d837/coverage-7.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3fb02fe73bed561fa12d279a417b432e5b50fe03e8d663d61b3d5990f29546", size = 239205 }, + { url = "https://files.pythonhosted.org/packages/36/91/fc02e8d8e694f557752120487fd982f654ba1421bbaa5560debf96ddceda/coverage-7.6.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed8fe9189d2beb6edc14d3ad19800626e1d9f2d975e436f84e19efb7fa19469b", size = 236612 }, + { url = "https://files.pythonhosted.org/packages/cc/57/cb08f0eda0389a9a8aaa4fc1f9fec7ac361c3e2d68efd5890d7042c18aa3/coverage-7.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b369ead6527d025a0fe7bd3864e46dbee3aa8f652d48df6174f8d0bac9e26e0e", size = 238479 }, + { url = "https://files.pythonhosted.org/packages/d5/c9/2c7681a9b3ca6e6f43d489c2e6653a53278ed857fd6e7010490c307b0a47/coverage-7.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ade3ca1e5f0ff46b678b66201f7ff477e8fa11fb537f3b55c3f0568fbfe6e718", size = 237405 }, + { url = "https://files.pythonhosted.org/packages/b5/4e/ebfc6944b96317df8b537ae875d2e57c27b84eb98820bc0a1055f358f056/coverage-7.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:27fb4a050aaf18772db513091c9c13f6cb94ed40eacdef8dad8411d92d9992db", size = 236038 }, + { url = "https://files.pythonhosted.org/packages/13/f2/3a0bf1841a97c0654905e2ef531170f02c89fad2555879db8fe41a097871/coverage-7.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f704f0998911abf728a7783799444fcbbe8261c4a6c166f667937ae6a8aa522", size = 236812 }, + { url = "https://files.pythonhosted.org/packages/b9/9c/66bf59226b52ce6ed9541b02d33e80a6e816a832558fbdc1111a7bd3abd4/coverage-7.6.4-cp311-cp311-win32.whl", hash = "sha256:29155cd511ee058e260db648b6182c419422a0d2e9a4fa44501898cf918866cf", size = 209400 }, + { url = "https://files.pythonhosted.org/packages/2a/a0/b0790934c04dfc8d658d4a62acb8f7ca0efdf3818456fcad757b11c6479d/coverage-7.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:8902dd6a30173d4ef09954bfcb24b5d7b5190cf14a43170e386979651e09ba19", size = 210243 }, + { url = "https://files.pythonhosted.org/packages/7d/e7/9291de916d084f41adddfd4b82246e68d61d6a75747f075f7e64628998d2/coverage-7.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12394842a3a8affa3ba62b0d4ab7e9e210c5e366fbac3e8b2a68636fb19892c2", size = 207013 }, + { url = "https://files.pythonhosted.org/packages/27/03/932c2c5717a7fa80cd43c6a07d3177076d97b79f12f40f882f9916db0063/coverage-7.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b6b4c83d8e8ea79f27ab80778c19bc037759aea298da4b56621f4474ffeb117", size = 207251 }, + { url = "https://files.pythonhosted.org/packages/d5/3f/0af47dcb9327f65a45455fbca846fe96eb57c153af46c4754a3ba678938a/coverage-7.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d5b8007f81b88696d06f7df0cb9af0d3b835fe0c8dbf489bad70b45f0e45613", size = 240268 }, + { url = "https://files.pythonhosted.org/packages/8a/3c/37a9d81bbd4b23bc7d46ca820e16174c613579c66342faa390a271d2e18b/coverage-7.6.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b57b768feb866f44eeed9f46975f3d6406380275c5ddfe22f531a2bf187eda27", size = 237298 }, + { url = "https://files.pythonhosted.org/packages/c0/70/6b0627e5bd68204ee580126ed3513140b2298995c1233bd67404b4e44d0e/coverage-7.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5915fcdec0e54ee229926868e9b08586376cae1f5faa9bbaf8faf3561b393d52", size = 239367 }, + { url = "https://files.pythonhosted.org/packages/3c/eb/634d7dfab24ac3b790bebaf9da0f4a5352cbc125ce6a9d5c6cf4c6cae3c7/coverage-7.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b58c672d14f16ed92a48db984612f5ce3836ae7d72cdd161001cc54512571f2", size = 238853 }, + { url = "https://files.pythonhosted.org/packages/d9/0d/8e3ed00f1266ef7472a4e33458f42e39492e01a64281084fb3043553d3f1/coverage-7.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2fdef0d83a2d08d69b1f2210a93c416d54e14d9eb398f6ab2f0a209433db19e1", size = 237160 }, + { url = "https://files.pythonhosted.org/packages/ce/9c/4337f468ef0ab7a2e0887a9c9da0e58e2eada6fc6cbee637a4acd5dfd8a9/coverage-7.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cf717ee42012be8c0cb205dbbf18ffa9003c4cbf4ad078db47b95e10748eec5", size = 238824 }, + { url = "https://files.pythonhosted.org/packages/5e/09/3e94912b8dd37251377bb02727a33a67ee96b84bbbe092f132b401ca5dd9/coverage-7.6.4-cp312-cp312-win32.whl", hash = "sha256:7bb92c539a624cf86296dd0c68cd5cc286c9eef2d0c3b8b192b604ce9de20a17", size = 209639 }, + { url = "https://files.pythonhosted.org/packages/01/69/d4f3a4101171f32bc5b3caec8ff94c2c60f700107a6aaef7244b2c166793/coverage-7.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:1032e178b76a4e2b5b32e19d0fd0abbce4b58e77a1ca695820d10e491fa32b08", size = 210428 }, + { url = "https://files.pythonhosted.org/packages/c2/4d/2dede4f7cb5a70fb0bb40a57627fddf1dbdc6b9c1db81f7c4dcdcb19e2f4/coverage-7.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9", size = 207039 }, + { url = "https://files.pythonhosted.org/packages/3f/f9/d86368ae8c79e28f1fb458ebc76ae9ff3e8bd8069adc24e8f2fed03c58b7/coverage-7.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba", size = 207298 }, + { url = "https://files.pythonhosted.org/packages/64/c5/b4cc3c3f64622c58fbfd4d8b9a7a8ce9d355f172f91fcabbba1f026852f6/coverage-7.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c", size = 239813 }, + { url = "https://files.pythonhosted.org/packages/8a/86/14c42e60b70a79b26099e4d289ccdfefbc68624d096f4481163085aa614c/coverage-7.6.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06", size = 236959 }, + { url = "https://files.pythonhosted.org/packages/7f/f8/4436a643631a2fbab4b44d54f515028f6099bfb1cd95b13cfbf701e7f2f2/coverage-7.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f", size = 238950 }, + { url = "https://files.pythonhosted.org/packages/49/50/1571810ddd01f99a0a8be464a4ac8b147f322cd1e8e296a1528984fc560b/coverage-7.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b", size = 238610 }, + { url = "https://files.pythonhosted.org/packages/f3/8c/6312d241fe7cbd1f0cade34a62fea6f333d1a261255d76b9a87074d8703c/coverage-7.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21", size = 236697 }, + { url = "https://files.pythonhosted.org/packages/ce/5f/fef33dfd05d87ee9030f614c857deb6df6556b8f6a1c51bbbb41e24ee5ac/coverage-7.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a", size = 238541 }, + { url = "https://files.pythonhosted.org/packages/a9/64/6a984b6e92e1ea1353b7ffa08e27f707a5e29b044622445859200f541e8c/coverage-7.6.4-cp313-cp313-win32.whl", hash = "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e", size = 209707 }, + { url = "https://files.pythonhosted.org/packages/5c/60/ce5a9e942e9543783b3db5d942e0578b391c25cdd5e7f342d854ea83d6b7/coverage-7.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963", size = 210439 }, + { url = "https://files.pythonhosted.org/packages/78/53/6719677e92c308207e7f10561a1b16ab8b5c00e9328efc9af7cfd6fb703e/coverage-7.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f", size = 207784 }, + { url = "https://files.pythonhosted.org/packages/fa/dd/7054928930671fcb39ae6a83bb71d9ab5f0afb733172543ced4b09a115ca/coverage-7.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806", size = 208058 }, + { url = "https://files.pythonhosted.org/packages/b5/7d/fd656ddc2b38301927b9eb3aae3fe827e7aa82e691923ed43721fd9423c9/coverage-7.6.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11", size = 250772 }, + { url = "https://files.pythonhosted.org/packages/90/d0/eb9a3cc2100b83064bb086f18aedde3afffd7de6ead28f69736c00b7f302/coverage-7.6.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3", size = 246490 }, + { url = "https://files.pythonhosted.org/packages/45/44/3f64f38f6faab8a0cfd2c6bc6eb4c6daead246b97cf5f8fc23bf3788f841/coverage-7.6.4-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a", size = 248848 }, + { url = "https://files.pythonhosted.org/packages/5d/11/4c465a5f98656821e499f4b4619929bd5a34639c466021740ecdca42aa30/coverage-7.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc", size = 248340 }, + { url = "https://files.pythonhosted.org/packages/f1/96/ebecda2d016cce9da812f404f720ca5df83c6b29f65dc80d2000d0078741/coverage-7.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70", size = 246229 }, + { url = "https://files.pythonhosted.org/packages/16/d9/3d820c00066ae55d69e6d0eae11d6149a5ca7546de469ba9d597f01bf2d7/coverage-7.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef", size = 247510 }, + { url = "https://files.pythonhosted.org/packages/8f/c3/4fa1eb412bb288ff6bfcc163c11700ff06e02c5fad8513817186e460ed43/coverage-7.6.4-cp313-cp313t-win32.whl", hash = "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e", size = 210353 }, + { url = "https://files.pythonhosted.org/packages/7e/77/03fc2979d1538884d921c2013075917fc927f41cd8526909852fe4494112/coverage-7.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1", size = 211502 }, + { url = "https://files.pythonhosted.org/packages/cc/56/e1d75e8981a2a92c2a777e67c26efa96c66da59d645423146eb9ff3a851b/coverage-7.6.4-pp39.pp310-none-any.whl", hash = "sha256:3c65d37f3a9ebb703e710befdc489a38683a5b152242664b973a7b7b22348a4e", size = 198954 }, ] [package.optional-dependencies] @@ -599,57 +591,57 @@ toml = [ name = "cycler" version = "0.12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 }, ] [[package]] name = "cython" version = "3.0.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/68/09/ffb61f29b8e3d207c444032b21328327d753e274ea081bc74e009827cc81/Cython-3.0.8.tar.gz", hash = "sha256:8333423d8fd5765e7cceea3a9985dd1e0a5dfeb2734629e1a2ed2d6233d39de6", size = 2744096, upload-time = "2024-01-10T11:01:02.155Z" } +sdist = { url = "https://files.pythonhosted.org/packages/68/09/ffb61f29b8e3d207c444032b21328327d753e274ea081bc74e009827cc81/Cython-3.0.8.tar.gz", hash = "sha256:8333423d8fd5765e7cceea3a9985dd1e0a5dfeb2734629e1a2ed2d6233d39de6", size = 2744096 } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/f4/d2542e186fe33ec1cc542770fb17466421ed54f4ffe04d00fe9549d0a467/Cython-3.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a846e0a38e2b24e9a5c5dc74b0e54c6e29420d88d1dafabc99e0fc0f3e338636", size = 3100459, upload-time = "2024-01-10T11:33:49.545Z" }, - { url = "https://files.pythonhosted.org/packages/fc/27/2652f395aa708fb3081148e0df3ab700bd7288636c65332ef7febad6a380/Cython-3.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45523fdc2b78d79b32834cc1cc12dc2ca8967af87e22a3ee1bff20e77c7f5520", size = 3456626, upload-time = "2024-01-10T11:01:44.897Z" }, - { url = "https://files.pythonhosted.org/packages/f9/bd/e8a1d26d04c08a67bcc383f2ea5493a4e77f37a8770ead00a238b08ad729/Cython-3.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa0b7f3f841fe087410cab66778e2d3fb20ae2d2078a2be3dffe66c6574be39", size = 3621379, upload-time = "2024-01-10T11:01:48.777Z" }, - { url = "https://files.pythonhosted.org/packages/03/ae/ead7ec03d0062d439879d41b7830e4f2480213f7beabf2f7052a191cc6f7/Cython-3.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e87294e33e40c289c77a135f491cd721bd089f193f956f7b8ed5aa2d0b8c558f", size = 3671873, upload-time = "2024-01-10T11:01:51.858Z" }, - { url = "https://files.pythonhosted.org/packages/63/b0/81dad725604d7b529c492f873a7fa1b5800704a9f26e100ed25e9fd8d057/Cython-3.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a1df7a129344b1215c20096d33c00193437df1a8fcca25b71f17c23b1a44f782", size = 3463832, upload-time = "2024-01-10T11:01:55.364Z" }, - { url = "https://files.pythonhosted.org/packages/13/cd/72b8e0af597ac1b376421847acf6d6fa252e60059a2a00dcf05ceb16d28f/Cython-3.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:13c2a5e57a0358da467d97667297bf820b62a1a87ae47c5f87938b9bb593acbd", size = 3618325, upload-time = "2024-01-10T11:01:59.03Z" }, - { url = "https://files.pythonhosted.org/packages/ef/73/11a4355d8b8966504c751e5bcb25916c4140de27bb2ba1b54ff21994d7fe/Cython-3.0.8-cp310-cp310-win32.whl", hash = "sha256:96b028f044f5880e3cb18ecdcfc6c8d3ce9d0af28418d5ab464509f26d8adf12", size = 2571305, upload-time = "2024-01-10T11:02:02.589Z" }, - { url = "https://files.pythonhosted.org/packages/18/15/fdc0c3552d20f9337b134a36d786da24e47998fc39f62cb61c1534f26123/Cython-3.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:8140597a8b5cc4f119a1190f5a2228a84f5ca6d8d9ec386cfce24663f48b2539", size = 2776113, upload-time = "2024-01-10T11:02:05.581Z" }, - { url = "https://files.pythonhosted.org/packages/db/a7/f4a0bc9a80e23b380daa2ebb4879bf434aaa0b3b91f7ad8a7f9762b4bd1b/Cython-3.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aae26f9663e50caf9657148403d9874eea41770ecdd6caf381d177c2b1bb82ba", size = 3113615, upload-time = "2024-01-10T11:34:05.899Z" }, - { url = "https://files.pythonhosted.org/packages/e9/e9/e9295df74246c165b91253a473bfa179debf739c9bee961cbb3ae56c2b79/Cython-3.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:547eb3cdb2f8c6f48e6865d5a741d9dd051c25b3ce076fbca571727977b28ac3", size = 3436320, upload-time = "2024-01-10T11:02:08.689Z" }, - { url = "https://files.pythonhosted.org/packages/26/2c/6a887c957aa53e44f928119dea628a5dfacc8e875424034f5fecac9daba4/Cython-3.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a567d4b9ba70b26db89d75b243529de9e649a2f56384287533cf91512705bee", size = 3591755, upload-time = "2024-01-10T11:02:11.773Z" }, - { url = "https://files.pythonhosted.org/packages/ba/b8/f9c97bae6281da50b3ecb1f7fef0f7f7851eae084609b364717a2b366bf1/Cython-3.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51d1426263b0e82fb22bda8ea60dc77a428581cc19e97741011b938445d383f1", size = 3636099, upload-time = "2024-01-10T11:02:15.191Z" }, - { url = "https://files.pythonhosted.org/packages/17/ae/cd055c2c081c67a6fcad1d8d17d82bd6395b14c6741e3a938f40318c8bc5/Cython-3.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c26daaeccda072459b48d211415fd1e5507c06bcd976fa0d5b8b9f1063467d7b", size = 3458119, upload-time = "2024-01-10T11:02:19.103Z" }, - { url = "https://files.pythonhosted.org/packages/72/ab/ac6f5548d6194f4bb2fc8c6c996aa7369f0fa1403e4d4de787d9e9309b27/Cython-3.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:289ce7838208211cd166e975865fd73b0649bf118170b6cebaedfbdaf4a37795", size = 3614418, upload-time = "2024-01-10T11:02:22.732Z" }, - { url = "https://files.pythonhosted.org/packages/70/e2/3e3e448b7a94887bec3235bcb71957b6681dc42b4536459f8f54d46fa936/Cython-3.0.8-cp311-cp311-win32.whl", hash = "sha256:c8aa05f5e17f8042a3be052c24f2edc013fb8af874b0bf76907d16c51b4e7871", size = 2572819, upload-time = "2024-01-10T11:02:25.976Z" }, - { url = "https://files.pythonhosted.org/packages/85/7d/58635941dfbb5b4e197adb88080b9cbfb230dc3b75683698a530a1989bdb/Cython-3.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:000dc9e135d0eec6ecb2b40a5b02d0868a2f8d2e027a41b0fe16a908a9e6de02", size = 2784167, upload-time = "2024-01-10T11:02:28.808Z" }, - { url = "https://files.pythonhosted.org/packages/3d/8e/28f8c6109990eef7317ab7e43644092b49a88a39f9373dcd19318946df09/Cython-3.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:90d3fe31db55685d8cb97d43b0ec39ef614fcf660f83c77ed06aa670cb0e164f", size = 3135638, upload-time = "2024-01-10T11:34:22.889Z" }, - { url = "https://files.pythonhosted.org/packages/83/1f/4720cb682b8ed1ab9749dea35351a66dd29b6a022628cce038415660c384/Cython-3.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e24791ddae2324e88e3c902a765595c738f19ae34ee66bfb1a6dac54b1833419", size = 3340052, upload-time = "2024-01-10T11:02:32.471Z" }, - { url = "https://files.pythonhosted.org/packages/8a/47/ec3fceb9e8f7d6fa130216b8740038e1df7c8e5f215bba363fcf1272a6c1/Cython-3.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f020fa1c0552052e0660790b8153b79e3fc9a15dbd8f1d0b841fe5d204a6ae6", size = 3510079, upload-time = "2024-01-10T11:02:35.312Z" }, - { url = "https://files.pythonhosted.org/packages/71/31/b458127851e248effb909e2791b55870914863cde7c60b94db5ee65d7867/Cython-3.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18bfa387d7a7f77d7b2526af69a65dbd0b731b8d941aaff5becff8e21f6d7717", size = 3573972, upload-time = "2024-01-10T11:02:39.044Z" }, - { url = "https://files.pythonhosted.org/packages/6b/d5/ca6513844d0634abd05ba12304053a454bb70441a9520afa9897d4300156/Cython-3.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fe81b339cffd87c0069c6049b4d33e28bdd1874625ee515785bf42c9fdff3658", size = 3356158, upload-time = "2024-01-10T11:02:42.125Z" }, - { url = "https://files.pythonhosted.org/packages/33/59/98a87b6264f4ad45c820db13c4ec657567476efde020c49443cc842a86af/Cython-3.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:80fd94c076e1e1b1ee40a309be03080b75f413e8997cddcf401a118879863388", size = 3522312, upload-time = "2024-01-10T11:02:45.056Z" }, - { url = "https://files.pythonhosted.org/packages/2b/cb/132115d07a0b9d4f075e0741db70a5416b424dcd875b2bb0dd805e818222/Cython-3.0.8-cp312-cp312-win32.whl", hash = "sha256:85077915a93e359a9b920280d214dc0cf8a62773e1f3d7d30fab8ea4daed670c", size = 2602579, upload-time = "2024-01-10T11:02:48.368Z" }, - { url = "https://files.pythonhosted.org/packages/b4/69/cb4620287cd9ef461103e122c0a2ae7f7ecf183e02510676fb5a15c95b05/Cython-3.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:0cb2dcc565c7851f75d496f724a384a790fab12d1b82461b663e66605bec429a", size = 2791268, upload-time = "2024-01-10T11:02:51.483Z" }, - { url = "https://files.pythonhosted.org/packages/e3/7f/f584f5d15323feb897d42ef0e9d910649e2150d7a30cf7e7a8cc1d236e6f/Cython-3.0.8-py2.py3-none-any.whl", hash = "sha256:171b27051253d3f9108e9759e504ba59ff06e7f7ba944457f94deaf9c21bf0b6", size = 1168213, upload-time = "2024-01-10T11:00:56.857Z" }, + { url = "https://files.pythonhosted.org/packages/63/f4/d2542e186fe33ec1cc542770fb17466421ed54f4ffe04d00fe9549d0a467/Cython-3.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a846e0a38e2b24e9a5c5dc74b0e54c6e29420d88d1dafabc99e0fc0f3e338636", size = 3100459 }, + { url = "https://files.pythonhosted.org/packages/fc/27/2652f395aa708fb3081148e0df3ab700bd7288636c65332ef7febad6a380/Cython-3.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45523fdc2b78d79b32834cc1cc12dc2ca8967af87e22a3ee1bff20e77c7f5520", size = 3456626 }, + { url = "https://files.pythonhosted.org/packages/f9/bd/e8a1d26d04c08a67bcc383f2ea5493a4e77f37a8770ead00a238b08ad729/Cython-3.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa0b7f3f841fe087410cab66778e2d3fb20ae2d2078a2be3dffe66c6574be39", size = 3621379 }, + { url = "https://files.pythonhosted.org/packages/03/ae/ead7ec03d0062d439879d41b7830e4f2480213f7beabf2f7052a191cc6f7/Cython-3.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e87294e33e40c289c77a135f491cd721bd089f193f956f7b8ed5aa2d0b8c558f", size = 3671873 }, + { url = "https://files.pythonhosted.org/packages/63/b0/81dad725604d7b529c492f873a7fa1b5800704a9f26e100ed25e9fd8d057/Cython-3.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a1df7a129344b1215c20096d33c00193437df1a8fcca25b71f17c23b1a44f782", size = 3463832 }, + { url = "https://files.pythonhosted.org/packages/13/cd/72b8e0af597ac1b376421847acf6d6fa252e60059a2a00dcf05ceb16d28f/Cython-3.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:13c2a5e57a0358da467d97667297bf820b62a1a87ae47c5f87938b9bb593acbd", size = 3618325 }, + { url = "https://files.pythonhosted.org/packages/ef/73/11a4355d8b8966504c751e5bcb25916c4140de27bb2ba1b54ff21994d7fe/Cython-3.0.8-cp310-cp310-win32.whl", hash = "sha256:96b028f044f5880e3cb18ecdcfc6c8d3ce9d0af28418d5ab464509f26d8adf12", size = 2571305 }, + { url = "https://files.pythonhosted.org/packages/18/15/fdc0c3552d20f9337b134a36d786da24e47998fc39f62cb61c1534f26123/Cython-3.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:8140597a8b5cc4f119a1190f5a2228a84f5ca6d8d9ec386cfce24663f48b2539", size = 2776113 }, + { url = "https://files.pythonhosted.org/packages/db/a7/f4a0bc9a80e23b380daa2ebb4879bf434aaa0b3b91f7ad8a7f9762b4bd1b/Cython-3.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aae26f9663e50caf9657148403d9874eea41770ecdd6caf381d177c2b1bb82ba", size = 3113615 }, + { url = "https://files.pythonhosted.org/packages/e9/e9/e9295df74246c165b91253a473bfa179debf739c9bee961cbb3ae56c2b79/Cython-3.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:547eb3cdb2f8c6f48e6865d5a741d9dd051c25b3ce076fbca571727977b28ac3", size = 3436320 }, + { url = "https://files.pythonhosted.org/packages/26/2c/6a887c957aa53e44f928119dea628a5dfacc8e875424034f5fecac9daba4/Cython-3.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a567d4b9ba70b26db89d75b243529de9e649a2f56384287533cf91512705bee", size = 3591755 }, + { url = "https://files.pythonhosted.org/packages/ba/b8/f9c97bae6281da50b3ecb1f7fef0f7f7851eae084609b364717a2b366bf1/Cython-3.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51d1426263b0e82fb22bda8ea60dc77a428581cc19e97741011b938445d383f1", size = 3636099 }, + { url = "https://files.pythonhosted.org/packages/17/ae/cd055c2c081c67a6fcad1d8d17d82bd6395b14c6741e3a938f40318c8bc5/Cython-3.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c26daaeccda072459b48d211415fd1e5507c06bcd976fa0d5b8b9f1063467d7b", size = 3458119 }, + { url = "https://files.pythonhosted.org/packages/72/ab/ac6f5548d6194f4bb2fc8c6c996aa7369f0fa1403e4d4de787d9e9309b27/Cython-3.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:289ce7838208211cd166e975865fd73b0649bf118170b6cebaedfbdaf4a37795", size = 3614418 }, + { url = "https://files.pythonhosted.org/packages/70/e2/3e3e448b7a94887bec3235bcb71957b6681dc42b4536459f8f54d46fa936/Cython-3.0.8-cp311-cp311-win32.whl", hash = "sha256:c8aa05f5e17f8042a3be052c24f2edc013fb8af874b0bf76907d16c51b4e7871", size = 2572819 }, + { url = "https://files.pythonhosted.org/packages/85/7d/58635941dfbb5b4e197adb88080b9cbfb230dc3b75683698a530a1989bdb/Cython-3.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:000dc9e135d0eec6ecb2b40a5b02d0868a2f8d2e027a41b0fe16a908a9e6de02", size = 2784167 }, + { url = "https://files.pythonhosted.org/packages/3d/8e/28f8c6109990eef7317ab7e43644092b49a88a39f9373dcd19318946df09/Cython-3.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:90d3fe31db55685d8cb97d43b0ec39ef614fcf660f83c77ed06aa670cb0e164f", size = 3135638 }, + { url = "https://files.pythonhosted.org/packages/83/1f/4720cb682b8ed1ab9749dea35351a66dd29b6a022628cce038415660c384/Cython-3.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e24791ddae2324e88e3c902a765595c738f19ae34ee66bfb1a6dac54b1833419", size = 3340052 }, + { url = "https://files.pythonhosted.org/packages/8a/47/ec3fceb9e8f7d6fa130216b8740038e1df7c8e5f215bba363fcf1272a6c1/Cython-3.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f020fa1c0552052e0660790b8153b79e3fc9a15dbd8f1d0b841fe5d204a6ae6", size = 3510079 }, + { url = "https://files.pythonhosted.org/packages/71/31/b458127851e248effb909e2791b55870914863cde7c60b94db5ee65d7867/Cython-3.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18bfa387d7a7f77d7b2526af69a65dbd0b731b8d941aaff5becff8e21f6d7717", size = 3573972 }, + { url = "https://files.pythonhosted.org/packages/6b/d5/ca6513844d0634abd05ba12304053a454bb70441a9520afa9897d4300156/Cython-3.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fe81b339cffd87c0069c6049b4d33e28bdd1874625ee515785bf42c9fdff3658", size = 3356158 }, + { url = "https://files.pythonhosted.org/packages/33/59/98a87b6264f4ad45c820db13c4ec657567476efde020c49443cc842a86af/Cython-3.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:80fd94c076e1e1b1ee40a309be03080b75f413e8997cddcf401a118879863388", size = 3522312 }, + { url = "https://files.pythonhosted.org/packages/2b/cb/132115d07a0b9d4f075e0741db70a5416b424dcd875b2bb0dd805e818222/Cython-3.0.8-cp312-cp312-win32.whl", hash = "sha256:85077915a93e359a9b920280d214dc0cf8a62773e1f3d7d30fab8ea4daed670c", size = 2602579 }, + { url = "https://files.pythonhosted.org/packages/b4/69/cb4620287cd9ef461103e122c0a2ae7f7ecf183e02510676fb5a15c95b05/Cython-3.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:0cb2dcc565c7851f75d496f724a384a790fab12d1b82461b663e66605bec429a", size = 2791268 }, + { url = "https://files.pythonhosted.org/packages/e3/7f/f584f5d15323feb897d42ef0e9d910649e2150d7a30cf7e7a8cc1d236e6f/Cython-3.0.8-py2.py3-none-any.whl", hash = "sha256:171b27051253d3f9108e9759e504ba59ff06e7f7ba944457f94deaf9c21bf0b6", size = 1168213 }, ] [[package]] name = "easydict" version = "1.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0a/d2/deb3296d08097fedd622d423c0ec8b68b78c1704b3f1545326f6ce05c75c/easydict-1.11.tar.gz", hash = "sha256:dcb1d2ed28eb300c8e46cd371340373abc62f7c14d6dea74fdfc6f1069061c78", size = 6644, upload-time = "2023-10-23T23:01:37.686Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/d2/deb3296d08097fedd622d423c0ec8b68b78c1704b3f1545326f6ce05c75c/easydict-1.11.tar.gz", hash = "sha256:dcb1d2ed28eb300c8e46cd371340373abc62f7c14d6dea74fdfc6f1069061c78", size = 6644 } [[package]] name = "exceptiongroup" version = "1.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8e/1c/beef724eaf5b01bb44b6338c8c3494eff7cab376fab4904cfbbc3585dc79/exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68", size = 26264, upload-time = "2023-11-21T08:42:17.407Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/1c/beef724eaf5b01bb44b6338c8c3494eff7cab376fab4904cfbbc3585dc79/exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68", size = 26264 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/9a/5028fd52db10e600f1c4674441b968cf2ea4959085bfb5b99fb1250e5f68/exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14", size = 16210, upload-time = "2023-11-21T08:42:15.525Z" }, + { url = "https://files.pythonhosted.org/packages/b8/9a/5028fd52db10e600f1c4674441b968cf2ea4959085bfb5b99fb1250e5f68/exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14", size = 16210 }, ] [[package]] @@ -661,18 +653,18 @@ dependencies = [ { name = "starlette" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/d7/6c8b3bfe33eeffa208183ec037fee0cce9f7f024089ab1c5d12ef04bd27c/fastapi-0.116.1.tar.gz", hash = "sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143", size = 296485, upload-time = "2025-07-11T16:22:32.057Z" } +sdist = { url = "https://files.pythonhosted.org/packages/78/d7/6c8b3bfe33eeffa208183ec037fee0cce9f7f024089ab1c5d12ef04bd27c/fastapi-0.116.1.tar.gz", hash = "sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143", size = 296485 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/47/d63c60f59a59467fda0f93f46335c9d18526d7071f025cb5b89d5353ea42/fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565", size = 95631, upload-time = "2025-07-11T16:22:30.485Z" }, + { url = "https://files.pythonhosted.org/packages/e5/47/d63c60f59a59467fda0f93f46335c9d18526d7071f025cb5b89d5353ea42/fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565", size = 95631 }, ] [[package]] name = "filelock" version = "3.13.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/70/70/41905c80dcfe71b22fb06827b8eae65781783d4a14194bce79d16a013263/filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e", size = 14553, upload-time = "2023-10-30T18:29:39.035Z" } +sdist = { url = "https://files.pythonhosted.org/packages/70/70/41905c80dcfe71b22fb06827b8eae65781783d4a14194bce79d16a013263/filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e", size = 14553 } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/54/84d42a0bee35edba99dee7b59a8d4970eccdd44b99fe728ed912106fc781/filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c", size = 11740, upload-time = "2023-10-30T18:29:37.267Z" }, + { url = "https://files.pythonhosted.org/packages/81/54/84d42a0bee35edba99dee7b59a8d4970eccdd44b99fe728ed912106fc781/filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c", size = 11740 }, ] [[package]] @@ -686,9 +678,9 @@ dependencies = [ { name = "jinja2" }, { name = "werkzeug" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d8/09/c1a7354d3925a3c6c8cfdebf4245bae67d633ffda1ba415add06ffc839c5/flask-3.0.0.tar.gz", hash = "sha256:cfadcdb638b609361d29ec22360d6070a77d7463dcb3ab08d2c2f2f168845f58", size = 674171, upload-time = "2023-09-30T14:36:12.918Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/09/c1a7354d3925a3c6c8cfdebf4245bae67d633ffda1ba415add06ffc839c5/flask-3.0.0.tar.gz", hash = "sha256:cfadcdb638b609361d29ec22360d6070a77d7463dcb3ab08d2c2f2f168845f58", size = 674171 } wheels = [ - { url = "https://files.pythonhosted.org/packages/36/42/015c23096649b908c809c69388a805a571a3bea44362fe87e33fc3afa01f/flask-3.0.0-py3-none-any.whl", hash = "sha256:21128f47e4e3b9d597a3e8521a329bf56909b690fcc3fa3e477725aa81367638", size = 99724, upload-time = "2023-09-30T14:36:10.961Z" }, + { url = "https://files.pythonhosted.org/packages/36/42/015c23096649b908c809c69388a805a571a3bea44362fe87e33fc3afa01f/flask-3.0.0-py3-none-any.whl", hash = "sha256:21128f47e4e3b9d597a3e8521a329bf56909b690fcc3fa3e477725aa81367638", size = 99724 }, ] [[package]] @@ -698,9 +690,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "flask" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/40/6a/a8d56d60bcfa1ec3e4fdad81b45aafd508c3bd5c244a16526fa29139d7d4/flask_cors-4.0.1.tar.gz", hash = "sha256:eeb69b342142fdbf4766ad99357a7f3876a2ceb77689dc10ff912aac06c389e4", size = 30306, upload-time = "2024-05-04T19:49:43.538Z" } +sdist = { url = "https://files.pythonhosted.org/packages/40/6a/a8d56d60bcfa1ec3e4fdad81b45aafd508c3bd5c244a16526fa29139d7d4/flask_cors-4.0.1.tar.gz", hash = "sha256:eeb69b342142fdbf4766ad99357a7f3876a2ceb77689dc10ff912aac06c389e4", size = 30306 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/52/2aa6285f104616f73ee1ad7905a16b2b35af0143034ad0cf7b64bcba715c/Flask_Cors-4.0.1-py2.py3-none-any.whl", hash = "sha256:f2a704e4458665580c074b714c4627dd5a306b333deb9074d0b1794dfa2fb677", size = 14290, upload-time = "2024-05-04T19:49:41.721Z" }, + { url = "https://files.pythonhosted.org/packages/8b/52/2aa6285f104616f73ee1ad7905a16b2b35af0143034ad0cf7b64bcba715c/Flask_Cors-4.0.1-py2.py3-none-any.whl", hash = "sha256:f2a704e4458665580c074b714c4627dd5a306b333deb9074d0b1794dfa2fb677", size = 14290 }, ] [[package]] @@ -711,60 +703,60 @@ dependencies = [ { name = "flask" }, { name = "werkzeug" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c3/6e/2f4e13e373bb49e68c02c51ceadd22d172715a06716f9299d9df01b6ddb2/Flask-Login-0.6.3.tar.gz", hash = "sha256:5e23d14a607ef12806c699590b89d0f0e0d67baeec599d75947bf9c147330333", size = 48834, upload-time = "2023-10-30T14:53:21.151Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/6e/2f4e13e373bb49e68c02c51ceadd22d172715a06716f9299d9df01b6ddb2/Flask-Login-0.6.3.tar.gz", hash = "sha256:5e23d14a607ef12806c699590b89d0f0e0d67baeec599d75947bf9c147330333", size = 48834 } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/f5/67e9cc5c2036f58115f9fe0f00d203cf6780c3ff8ae0e705e7a9d9e8ff9e/Flask_Login-0.6.3-py3-none-any.whl", hash = "sha256:849b25b82a436bf830a054e74214074af59097171562ab10bfa999e6b78aae5d", size = 17303, upload-time = "2023-10-30T14:53:19.636Z" }, + { url = "https://files.pythonhosted.org/packages/59/f5/67e9cc5c2036f58115f9fe0f00d203cf6780c3ff8ae0e705e7a9d9e8ff9e/Flask_Login-0.6.3-py3-none-any.whl", hash = "sha256:849b25b82a436bf830a054e74214074af59097171562ab10bfa999e6b78aae5d", size = 17303 }, ] [[package]] name = "flatbuffers" version = "23.5.26" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0c/6e/3e52cd294d8e7a61e010973cce076a0cb2c6c0dfd4d0b7a13648c1b98329/flatbuffers-23.5.26.tar.gz", hash = "sha256:9ea1144cac05ce5d86e2859f431c6cd5e66cd9c78c558317c7955fb8d4c78d89", size = 22114, upload-time = "2023-05-26T17:35:16.034Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/6e/3e52cd294d8e7a61e010973cce076a0cb2c6c0dfd4d0b7a13648c1b98329/flatbuffers-23.5.26.tar.gz", hash = "sha256:9ea1144cac05ce5d86e2859f431c6cd5e66cd9c78c558317c7955fb8d4c78d89", size = 22114 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/12/d5c79ee252793ffe845d58a913197bfa02ae9a0b5c9bc3dc4b58d477b9e7/flatbuffers-23.5.26-py2.py3-none-any.whl", hash = "sha256:c0ff356da363087b915fde4b8b45bdda73432fc17cddb3c8157472eab1422ad1", size = 26744, upload-time = "2023-05-26T17:35:14.269Z" }, + { url = "https://files.pythonhosted.org/packages/6f/12/d5c79ee252793ffe845d58a913197bfa02ae9a0b5c9bc3dc4b58d477b9e7/flatbuffers-23.5.26-py2.py3-none-any.whl", hash = "sha256:c0ff356da363087b915fde4b8b45bdda73432fc17cddb3c8157472eab1422ad1", size = 26744 }, ] [[package]] name = "fonttools" version = "4.47.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e5/cd/75d24afa673edf92fd04657fad7d3b5e20c4abc3cad5bc14e5e30051c1f0/fonttools-4.47.2.tar.gz", hash = "sha256:7df26dd3650e98ca45f1e29883c96a0b9f5bb6af8d632a6a108bc744fa0bd9b3", size = 3410067, upload-time = "2024-01-11T11:22:45.293Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/cd/75d24afa673edf92fd04657fad7d3b5e20c4abc3cad5bc14e5e30051c1f0/fonttools-4.47.2.tar.gz", hash = "sha256:7df26dd3650e98ca45f1e29883c96a0b9f5bb6af8d632a6a108bc744fa0bd9b3", size = 3410067 } wheels = [ - { url = "https://files.pythonhosted.org/packages/19/30/02de0b7f3d72f2c4fce3e512b166c1bdbe5a687408474b61eb0114be921c/fonttools-4.47.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3b629108351d25512d4ea1a8393a2dba325b7b7d7308116b605ea3f8e1be88df", size = 2779949, upload-time = "2024-01-11T11:19:56.276Z" }, - { url = "https://files.pythonhosted.org/packages/9a/52/1a5e1373afb78a040ea0c371ab8a79da121060a8e518968bb8f41457ca90/fonttools-4.47.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c19044256c44fe299d9a73456aabee4b4d06c6b930287be93b533b4737d70aa1", size = 2281336, upload-time = "2024-01-11T11:20:08.835Z" }, - { url = "https://files.pythonhosted.org/packages/c5/ce/9d3b5bf51aafee024566ebb374f5b040381d92660cb04647af3c5860c611/fonttools-4.47.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8be28c036b9f186e8c7eaf8a11b42373e7e4949f9e9f370202b9da4c4c3f56c", size = 4541692, upload-time = "2024-01-11T11:20:13.378Z" }, - { url = "https://files.pythonhosted.org/packages/e8/68/af41b7cfd35c7418e17b6a43bb106be4b0f0e5feb405a88dee29b186f2a7/fonttools-4.47.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f83a4daef6d2a202acb9bf572958f91cfde5b10c8ee7fb1d09a4c81e5d851fd8", size = 4600529, upload-time = "2024-01-11T11:20:17.27Z" }, - { url = "https://files.pythonhosted.org/packages/ab/7e/428dbb4cfc342b7a05cbc9d349e134e7fad6588f4ce2a7128e8e3e58ad3b/fonttools-4.47.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a5a5318ba5365d992666ac4fe35365f93004109d18858a3e18ae46f67907670", size = 4524215, upload-time = "2024-01-11T11:20:21.061Z" }, - { url = "https://files.pythonhosted.org/packages/a6/61/762fad1cc1debc4626f2eb373fa999591c63c231fce53d5073574a639531/fonttools-4.47.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8f57ecd742545362a0f7186774b2d1c53423ed9ece67689c93a1055b236f638c", size = 4584778, upload-time = "2024-01-11T11:20:25.815Z" }, - { url = "https://files.pythonhosted.org/packages/04/30/170ca22284c1d825470e8b5871d6b25d3a70e2f5b185ffb1647d5e11ee4d/fonttools-4.47.2-cp310-cp310-win32.whl", hash = "sha256:a1c154bb85dc9a4cf145250c88d112d88eb414bad81d4cb524d06258dea1bdc0", size = 2131876, upload-time = "2024-01-11T11:20:30.261Z" }, - { url = "https://files.pythonhosted.org/packages/df/07/4a30437bed355b838b8ce31d14c5983334c31adc97e70c6ecff90c60d6d2/fonttools-4.47.2-cp310-cp310-win_amd64.whl", hash = "sha256:3e2b95dce2ead58fb12524d0ca7d63a63459dd489e7e5838c3cd53557f8933e1", size = 2177937, upload-time = "2024-01-11T11:20:33.814Z" }, - { url = "https://files.pythonhosted.org/packages/dd/1d/670372323642eada0f7743cfcdd156de6a28d37769c916421fec2f32c814/fonttools-4.47.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:29495d6d109cdbabe73cfb6f419ce67080c3ef9ea1e08d5750240fd4b0c4763b", size = 2782908, upload-time = "2024-01-11T11:20:37.495Z" }, - { url = "https://files.pythonhosted.org/packages/c1/36/5f0bb863a6575db4c4b67fa9be7f98e4c551dd87638ef327bc180b988998/fonttools-4.47.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0a1d313a415eaaba2b35d6cd33536560deeebd2ed758b9bfb89ab5d97dc5deac", size = 2283501, upload-time = "2024-01-11T11:20:42.027Z" }, - { url = "https://files.pythonhosted.org/packages/bd/1e/95de682a86567426bcc40a56c9b118ffa97de6cbfcc293addf20994e329d/fonttools-4.47.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90f898cdd67f52f18049250a6474185ef6544c91f27a7bee70d87d77a8daf89c", size = 4848039, upload-time = "2024-01-11T11:20:47.038Z" }, - { url = "https://files.pythonhosted.org/packages/ef/95/92a0b5fc844c1db734752f8a51431de519cd6b02e7e561efa9e9fd415544/fonttools-4.47.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3480eeb52770ff75140fe7d9a2ec33fb67b07efea0ab5129c7e0c6a639c40c70", size = 4893166, upload-time = "2024-01-11T11:20:50.855Z" }, - { url = "https://files.pythonhosted.org/packages/ff/e6/ed9dd7ee1afd6cd70eb7237688118fe489dbde962e3765c91c86c095f84b/fonttools-4.47.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0255dbc128fee75fb9be364806b940ed450dd6838672a150d501ee86523ac61e", size = 4815529, upload-time = "2024-01-11T11:20:54.696Z" }, - { url = "https://files.pythonhosted.org/packages/6b/67/cdffa0b3cd8f863b45125c335bbd3d9dc16ec42f5a8d5b64dd1244c5ce6b/fonttools-4.47.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f791446ff297fd5f1e2247c188de53c1bfb9dd7f0549eba55b73a3c2087a2703", size = 4875414, upload-time = "2024-01-11T11:20:58.435Z" }, - { url = "https://files.pythonhosted.org/packages/b8/fb/41638e748c8f20f5483987afcf9be746d3ccb9e9600ca62128a27c791a82/fonttools-4.47.2-cp311-cp311-win32.whl", hash = "sha256:740947906590a878a4bde7dd748e85fefa4d470a268b964748403b3ab2aeed6c", size = 2130073, upload-time = "2024-01-11T11:21:02.056Z" }, - { url = "https://files.pythonhosted.org/packages/a0/ef/93321cf55180a778b4d97919b28739874c0afab90e7b9f5b232db70f47c2/fonttools-4.47.2-cp311-cp311-win_amd64.whl", hash = "sha256:63fbed184979f09a65aa9c88b395ca539c94287ba3a364517698462e13e457c9", size = 2178744, upload-time = "2024-01-11T11:21:05.88Z" }, - { url = "https://files.pythonhosted.org/packages/c0/bd/4dd1e8a9e632f325d9203ce543402f912f26efd213c8d9efec0180fbac64/fonttools-4.47.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4ec558c543609e71b2275c4894e93493f65d2f41c15fe1d089080c1d0bb4d635", size = 2754076, upload-time = "2024-01-11T11:21:09.745Z" }, - { url = "https://files.pythonhosted.org/packages/e6/4d/c2ebaac81dadbc3fc3c3c2fa5fe7b16429dc713b1b8ace49e11e92904d78/fonttools-4.47.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e040f905d542362e07e72e03612a6270c33d38281fd573160e1003e43718d68d", size = 2263784, upload-time = "2024-01-11T11:21:13.367Z" }, - { url = "https://files.pythonhosted.org/packages/d3/f6/9d484cd275845c7e503a8669a5952a7fa089c7a881babb4dce5ebe6fc5d1/fonttools-4.47.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6dd58cc03016b281bd2c74c84cdaa6bd3ce54c5a7f47478b7657b930ac3ed8eb", size = 4769142, upload-time = "2024-01-11T11:21:17.615Z" }, - { url = "https://files.pythonhosted.org/packages/7a/bf/c6ae0768a531b38245aac0bb8d30bc05d53d499e09fccdc5d72e7c8d28b6/fonttools-4.47.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32ab2e9702dff0dd4510c7bb958f265a8d3dd5c0e2547e7b5f7a3df4979abb07", size = 4853241, upload-time = "2024-01-11T11:21:21.16Z" }, - { url = "https://files.pythonhosted.org/packages/2b/f0/c06709666cb7722447efb70ea456c302bd6eb3b997d30076401fb32bca4b/fonttools-4.47.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a808f3c1d1df1f5bf39be869b6e0c263570cdafb5bdb2df66087733f566ea71", size = 4730447, upload-time = "2024-01-11T11:21:24.755Z" }, - { url = "https://files.pythonhosted.org/packages/3e/71/4c758ae5f4f8047904fc1c6bbbb828248c94cc7aa6406af3a62ede766f25/fonttools-4.47.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ac71e2e201df041a2891067dc36256755b1229ae167edbdc419b16da78732c2f", size = 4809265, upload-time = "2024-01-11T11:21:28.586Z" }, - { url = "https://files.pythonhosted.org/packages/81/f6/a6912c11280607d48947341e2167502605a3917925c835afcd7dfcabc289/fonttools-4.47.2-cp312-cp312-win32.whl", hash = "sha256:69731e8bea0578b3c28fdb43dbf95b9386e2d49a399e9a4ad736b8e479b08085", size = 2118363, upload-time = "2024-01-11T11:21:33.245Z" }, - { url = "https://files.pythonhosted.org/packages/81/4b/42d0488765ea5aa308b4e8197cb75366b2124240a73e86f98b6107ccf282/fonttools-4.47.2-cp312-cp312-win_amd64.whl", hash = "sha256:b3e1304e5f19ca861d86a72218ecce68f391646d85c851742d265787f55457a4", size = 2165866, upload-time = "2024-01-11T11:21:37.23Z" }, - { url = "https://files.pythonhosted.org/packages/af/2f/c34b0f99d46766cf49566d1ee2ee3606e4c9880b5a7d734257dc61c804e9/fonttools-4.47.2-py3-none-any.whl", hash = "sha256:7eb7ad665258fba68fd22228a09f347469d95a97fb88198e133595947a20a184", size = 1063011, upload-time = "2024-01-11T11:22:41.676Z" }, + { url = "https://files.pythonhosted.org/packages/19/30/02de0b7f3d72f2c4fce3e512b166c1bdbe5a687408474b61eb0114be921c/fonttools-4.47.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3b629108351d25512d4ea1a8393a2dba325b7b7d7308116b605ea3f8e1be88df", size = 2779949 }, + { url = "https://files.pythonhosted.org/packages/9a/52/1a5e1373afb78a040ea0c371ab8a79da121060a8e518968bb8f41457ca90/fonttools-4.47.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c19044256c44fe299d9a73456aabee4b4d06c6b930287be93b533b4737d70aa1", size = 2281336 }, + { url = "https://files.pythonhosted.org/packages/c5/ce/9d3b5bf51aafee024566ebb374f5b040381d92660cb04647af3c5860c611/fonttools-4.47.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8be28c036b9f186e8c7eaf8a11b42373e7e4949f9e9f370202b9da4c4c3f56c", size = 4541692 }, + { url = "https://files.pythonhosted.org/packages/e8/68/af41b7cfd35c7418e17b6a43bb106be4b0f0e5feb405a88dee29b186f2a7/fonttools-4.47.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f83a4daef6d2a202acb9bf572958f91cfde5b10c8ee7fb1d09a4c81e5d851fd8", size = 4600529 }, + { url = "https://files.pythonhosted.org/packages/ab/7e/428dbb4cfc342b7a05cbc9d349e134e7fad6588f4ce2a7128e8e3e58ad3b/fonttools-4.47.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a5a5318ba5365d992666ac4fe35365f93004109d18858a3e18ae46f67907670", size = 4524215 }, + { url = "https://files.pythonhosted.org/packages/a6/61/762fad1cc1debc4626f2eb373fa999591c63c231fce53d5073574a639531/fonttools-4.47.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8f57ecd742545362a0f7186774b2d1c53423ed9ece67689c93a1055b236f638c", size = 4584778 }, + { url = "https://files.pythonhosted.org/packages/04/30/170ca22284c1d825470e8b5871d6b25d3a70e2f5b185ffb1647d5e11ee4d/fonttools-4.47.2-cp310-cp310-win32.whl", hash = "sha256:a1c154bb85dc9a4cf145250c88d112d88eb414bad81d4cb524d06258dea1bdc0", size = 2131876 }, + { url = "https://files.pythonhosted.org/packages/df/07/4a30437bed355b838b8ce31d14c5983334c31adc97e70c6ecff90c60d6d2/fonttools-4.47.2-cp310-cp310-win_amd64.whl", hash = "sha256:3e2b95dce2ead58fb12524d0ca7d63a63459dd489e7e5838c3cd53557f8933e1", size = 2177937 }, + { url = "https://files.pythonhosted.org/packages/dd/1d/670372323642eada0f7743cfcdd156de6a28d37769c916421fec2f32c814/fonttools-4.47.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:29495d6d109cdbabe73cfb6f419ce67080c3ef9ea1e08d5750240fd4b0c4763b", size = 2782908 }, + { url = "https://files.pythonhosted.org/packages/c1/36/5f0bb863a6575db4c4b67fa9be7f98e4c551dd87638ef327bc180b988998/fonttools-4.47.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0a1d313a415eaaba2b35d6cd33536560deeebd2ed758b9bfb89ab5d97dc5deac", size = 2283501 }, + { url = "https://files.pythonhosted.org/packages/bd/1e/95de682a86567426bcc40a56c9b118ffa97de6cbfcc293addf20994e329d/fonttools-4.47.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90f898cdd67f52f18049250a6474185ef6544c91f27a7bee70d87d77a8daf89c", size = 4848039 }, + { url = "https://files.pythonhosted.org/packages/ef/95/92a0b5fc844c1db734752f8a51431de519cd6b02e7e561efa9e9fd415544/fonttools-4.47.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3480eeb52770ff75140fe7d9a2ec33fb67b07efea0ab5129c7e0c6a639c40c70", size = 4893166 }, + { url = "https://files.pythonhosted.org/packages/ff/e6/ed9dd7ee1afd6cd70eb7237688118fe489dbde962e3765c91c86c095f84b/fonttools-4.47.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0255dbc128fee75fb9be364806b940ed450dd6838672a150d501ee86523ac61e", size = 4815529 }, + { url = "https://files.pythonhosted.org/packages/6b/67/cdffa0b3cd8f863b45125c335bbd3d9dc16ec42f5a8d5b64dd1244c5ce6b/fonttools-4.47.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f791446ff297fd5f1e2247c188de53c1bfb9dd7f0549eba55b73a3c2087a2703", size = 4875414 }, + { url = "https://files.pythonhosted.org/packages/b8/fb/41638e748c8f20f5483987afcf9be746d3ccb9e9600ca62128a27c791a82/fonttools-4.47.2-cp311-cp311-win32.whl", hash = "sha256:740947906590a878a4bde7dd748e85fefa4d470a268b964748403b3ab2aeed6c", size = 2130073 }, + { url = "https://files.pythonhosted.org/packages/a0/ef/93321cf55180a778b4d97919b28739874c0afab90e7b9f5b232db70f47c2/fonttools-4.47.2-cp311-cp311-win_amd64.whl", hash = "sha256:63fbed184979f09a65aa9c88b395ca539c94287ba3a364517698462e13e457c9", size = 2178744 }, + { url = "https://files.pythonhosted.org/packages/c0/bd/4dd1e8a9e632f325d9203ce543402f912f26efd213c8d9efec0180fbac64/fonttools-4.47.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4ec558c543609e71b2275c4894e93493f65d2f41c15fe1d089080c1d0bb4d635", size = 2754076 }, + { url = "https://files.pythonhosted.org/packages/e6/4d/c2ebaac81dadbc3fc3c3c2fa5fe7b16429dc713b1b8ace49e11e92904d78/fonttools-4.47.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e040f905d542362e07e72e03612a6270c33d38281fd573160e1003e43718d68d", size = 2263784 }, + { url = "https://files.pythonhosted.org/packages/d3/f6/9d484cd275845c7e503a8669a5952a7fa089c7a881babb4dce5ebe6fc5d1/fonttools-4.47.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6dd58cc03016b281bd2c74c84cdaa6bd3ce54c5a7f47478b7657b930ac3ed8eb", size = 4769142 }, + { url = "https://files.pythonhosted.org/packages/7a/bf/c6ae0768a531b38245aac0bb8d30bc05d53d499e09fccdc5d72e7c8d28b6/fonttools-4.47.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32ab2e9702dff0dd4510c7bb958f265a8d3dd5c0e2547e7b5f7a3df4979abb07", size = 4853241 }, + { url = "https://files.pythonhosted.org/packages/2b/f0/c06709666cb7722447efb70ea456c302bd6eb3b997d30076401fb32bca4b/fonttools-4.47.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a808f3c1d1df1f5bf39be869b6e0c263570cdafb5bdb2df66087733f566ea71", size = 4730447 }, + { url = "https://files.pythonhosted.org/packages/3e/71/4c758ae5f4f8047904fc1c6bbbb828248c94cc7aa6406af3a62ede766f25/fonttools-4.47.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ac71e2e201df041a2891067dc36256755b1229ae167edbdc419b16da78732c2f", size = 4809265 }, + { url = "https://files.pythonhosted.org/packages/81/f6/a6912c11280607d48947341e2167502605a3917925c835afcd7dfcabc289/fonttools-4.47.2-cp312-cp312-win32.whl", hash = "sha256:69731e8bea0578b3c28fdb43dbf95b9386e2d49a399e9a4ad736b8e479b08085", size = 2118363 }, + { url = "https://files.pythonhosted.org/packages/81/4b/42d0488765ea5aa308b4e8197cb75366b2124240a73e86f98b6107ccf282/fonttools-4.47.2-cp312-cp312-win_amd64.whl", hash = "sha256:b3e1304e5f19ca861d86a72218ecce68f391646d85c851742d265787f55457a4", size = 2165866 }, + { url = "https://files.pythonhosted.org/packages/af/2f/c34b0f99d46766cf49566d1ee2ee3606e4c9880b5a7d734257dc61c804e9/fonttools-4.47.2-py3-none-any.whl", hash = "sha256:7eb7ad665258fba68fd22228a09f347469d95a97fb88198e133595947a20a184", size = 1063011 }, ] [[package]] name = "fsspec" version = "2023.12.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fa/08/cac914ff6ff46c4500fc4323a939dbe7a0f528cca04e7fd3e859611dea41/fsspec-2023.12.2.tar.gz", hash = "sha256:8548d39e8810b59c38014934f6b31e57f40c1b20f911f4cc2b85389c7e9bf0cb", size = 167507, upload-time = "2023-12-11T21:19:54.832Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/08/cac914ff6ff46c4500fc4323a939dbe7a0f528cca04e7fd3e859611dea41/fsspec-2023.12.2.tar.gz", hash = "sha256:8548d39e8810b59c38014934f6b31e57f40c1b20f911f4cc2b85389c7e9bf0cb", size = 167507 } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/25/fab23259a52ece5670dcb8452e1af34b89e6135ecc17cd4b54b4b479eac6/fsspec-2023.12.2-py3-none-any.whl", hash = "sha256:d800d87f72189a745fa3d6b033b9dc4a34ad069f60ca60b943a63599f5501960", size = 168979, upload-time = "2023-12-11T21:19:52.446Z" }, + { url = "https://files.pythonhosted.org/packages/70/25/fab23259a52ece5670dcb8452e1af34b89e6135ecc17cd4b54b4b479eac6/fsspec-2023.12.2-py3-none-any.whl", hash = "sha256:d800d87f72189a745fa3d6b033b9dc4a34ad069f60ca60b943a63599f5501960", size = 168979 }, ] [[package]] @@ -774,9 +766,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "wcwidth" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a5/d3/8650919bc3c7c6e90ee3fa7fd618bf373cbbe55dff043bd67353dbb20cd8/ftfy-6.3.1.tar.gz", hash = "sha256:9b3c3d90f84fb267fe64d375a07b7f8912d817cf86009ae134aa03e1819506ec", size = 308927, upload-time = "2024-10-26T00:50:35.149Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/d3/8650919bc3c7c6e90ee3fa7fd618bf373cbbe55dff043bd67353dbb20cd8/ftfy-6.3.1.tar.gz", hash = "sha256:9b3c3d90f84fb267fe64d375a07b7f8912d817cf86009ae134aa03e1819506ec", size = 308927 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/6e/81d47999aebc1b155f81eca4477a616a70f238a2549848c38983f3c22a82/ftfy-6.3.1-py3-none-any.whl", hash = "sha256:7c70eb532015cd2f9adb53f101fb6c7945988d023a085d127d1573dc49dd0083", size = 44821, upload-time = "2024-10-26T00:50:33.425Z" }, + { url = "https://files.pythonhosted.org/packages/ab/6e/81d47999aebc1b155f81eca4477a616a70f238a2549848c38983f3c22a82/ftfy-6.3.1-py3-none-any.whl", hash = "sha256:7c70eb532015cd2f9adb53f101fb6c7945988d023a085d127d1573dc49dd0083", size = 44821 }, ] [[package]] @@ -789,41 +781,41 @@ dependencies = [ { name = "zope-event" }, { name = "zope-interface" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/70/f0/be10ed5d7721ed2317d7feb59e167603217156c2a6d57f128523e24e673d/gevent-24.10.3.tar.gz", hash = "sha256:aa7ee1bd5cabb2b7ef35105f863b386c8d5e332f754b60cfc354148bd70d35d1", size = 6108837, upload-time = "2024-10-18T16:06:25.867Z" } +sdist = { url = "https://files.pythonhosted.org/packages/70/f0/be10ed5d7721ed2317d7feb59e167603217156c2a6d57f128523e24e673d/gevent-24.10.3.tar.gz", hash = "sha256:aa7ee1bd5cabb2b7ef35105f863b386c8d5e332f754b60cfc354148bd70d35d1", size = 6108837 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/6f/a2100e7883c7bdfc2b45cb60b310ca748762a21596258b9dd01c5c093dbc/gevent-24.10.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:d7a1ad0f2da582f5bd238bca067e1c6c482c30c15a6e4d14aaa3215cbb2232f3", size = 3014382, upload-time = "2024-10-18T15:37:34.041Z" }, - { url = "https://files.pythonhosted.org/packages/7a/b1/460e4884ed6185d9eb9c4c2e9639d2b254197e46513301c0f63dec22dc90/gevent-24.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4e526fdc279c655c1e809b0c34b45844182c2a6b219802da5e411bd2cf5a8ad", size = 4853460, upload-time = "2024-10-18T16:19:39.515Z" }, - { url = "https://files.pythonhosted.org/packages/ca/f6/7ded98760d381229183ecce8db2edcce96f13e23807d31a90c66dae85304/gevent-24.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57a5c4e0bdac482c5f02f240d0354e61362df73501ef6ebafce8ef635cad7527", size = 4977636, upload-time = "2024-10-18T16:18:45.464Z" }, - { url = "https://files.pythonhosted.org/packages/7d/21/7b928e6029eedb93ef94fc0aee701f497af2e601f0ec00aac0e72e3f450e/gevent-24.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d67daed8383326dc8b5e58d88e148d29b6b52274a489e383530b0969ae7b9cb9", size = 5058031, upload-time = "2024-10-18T16:23:10.719Z" }, - { url = "https://files.pythonhosted.org/packages/00/98/12c03fd004fbeeca01276ffc589f5a368fd741d02582ab7006d1bdef57e7/gevent-24.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e24ffea72e27987979c009536fd0868e52239b44afe6cf7135ce8aafd0f108e", size = 6683694, upload-time = "2024-10-18T15:59:35.475Z" }, - { url = "https://files.pythonhosted.org/packages/64/4c/ea14d971452d3da09e49267e052d8312f112c7835120aed78d22ef14efee/gevent-24.10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c1d80090485da1ea3d99205fe97908b31188c1f4857f08b333ffaf2de2e89d18", size = 5286063, upload-time = "2024-10-18T16:38:24.113Z" }, - { url = "https://files.pythonhosted.org/packages/39/3f/397efff27e637d7306caa00d1560512c44028c25c70be1e72c46b79b1b66/gevent-24.10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f0c129f81d60cda614acb4b0c5731997ca05b031fb406fcb58ad53a7ade53b13", size = 6817462, upload-time = "2024-10-18T16:02:48.427Z" }, - { url = "https://files.pythonhosted.org/packages/aa/5d/19939eaa7c5b7c0f37e0a0665a911ddfe1e35c25c512446fc356a065c16e/gevent-24.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:26ca7a6b42d35129617025ac801135118333cad75856ffc3217b38e707383eba", size = 1566631, upload-time = "2024-10-18T16:08:38.489Z" }, - { url = "https://files.pythonhosted.org/packages/6e/01/1be5cf013826d8baae235976d6a94f3628014fd2db7c071aeec13f82b4d1/gevent-24.10.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:68c3a0d8402755eba7f69022e42e8021192a721ca8341908acc222ea597029b6", size = 2966909, upload-time = "2024-10-18T15:37:31.43Z" }, - { url = "https://files.pythonhosted.org/packages/fe/3e/7fa9ab023f24d8689e2c77951981f8ea1f25089e0349a0bf8b35ee9b9277/gevent-24.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d850a453d66336272be4f1d3a8126777f3efdaea62d053b4829857f91e09755", size = 4913247, upload-time = "2024-10-18T16:19:41.792Z" }, - { url = "https://files.pythonhosted.org/packages/db/63/6e40eaaa3c2abd1561faff11dc3e6781f8c25e975354b8835762834415af/gevent-24.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8e58ee3723f1fbe07d66892f1caa7481c306f653a6829b6fd16cb23d618a5915", size = 5049036, upload-time = "2024-10-18T16:18:47.419Z" }, - { url = "https://files.pythonhosted.org/packages/94/89/158bc32cdc898dda0481040ac18650022e73133d93460c5af56ca622fe9a/gevent-24.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b52382124eca13135a3abe4f65c6bd428656975980a48e51b17aeab68bdb14db", size = 5107299, upload-time = "2024-10-18T16:23:12.296Z" }, - { url = "https://files.pythonhosted.org/packages/64/91/1abe62ee350fdfac186d33f615d0d3a0b3b140e7ccf23c73547aa0deec44/gevent-24.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ca2266e08f43c0e22c028801dff7d92a0b102ef20e4caeb6a46abfb95f6a328", size = 6819625, upload-time = "2024-10-18T15:59:38.226Z" }, - { url = "https://files.pythonhosted.org/packages/92/8b/0b2fe0d36b7c4d463e46cc68eaf6c14488bd7d86cc37e995c64a0ff7d02f/gevent-24.10.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d758f0d4dbf32502ec87bb9b536ca8055090a16f8305f0ada3ce6f34e70f2fd7", size = 5474079, upload-time = "2024-10-18T16:38:26.866Z" }, - { url = "https://files.pythonhosted.org/packages/12/7b/9f5abbf0021a50321314f850697e0f46d2e5081168223af2d8544af9d19f/gevent-24.10.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0de6eb3d55c03138fda567d9bfed28487ce5d0928c5107549767a93efdf2be26", size = 6901323, upload-time = "2024-10-18T16:02:50.066Z" }, - { url = "https://files.pythonhosted.org/packages/8a/63/607715c621ae78ed581b7ba36d076df63feeb352993d521327f865056771/gevent-24.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:385710355eadecdb70428a5ae3e7e5a45dcf888baa1426884588be9d25ac4290", size = 1549468, upload-time = "2024-10-18T16:01:30.331Z" }, - { url = "https://files.pythonhosted.org/packages/d9/e4/4edbe17001bb3e6fade4ad2d85ca8f9e4eabcbde4aa29aa6889281616e3e/gevent-24.10.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3ad8fb70aa0ebc935729c9699ac31b210a49b689a7b27b7ac9f91676475f3f53", size = 2970952, upload-time = "2024-10-18T15:37:31.389Z" }, - { url = "https://files.pythonhosted.org/packages/3c/a6/ce0824fe9398ba6b00028a74840f12be1165d5feaacdc028ea953db3d6c3/gevent-24.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f18689f7a70d2ed0e75bad5036ec3c89690a493d4cfac8d7cdb258ac04b132bd", size = 5172230, upload-time = "2024-10-18T16:19:43.661Z" }, - { url = "https://files.pythonhosted.org/packages/25/d4/9002cfb585bfa52c860ed4b1349d1a6400bdf2df9f1bd21df5ff33eea33c/gevent-24.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f4f171d4d2018170454d84c934842e1b5f6ce7468ba298f6e7f7cff15000a3", size = 5338394, upload-time = "2024-10-18T16:18:49.371Z" }, - { url = "https://files.pythonhosted.org/packages/0c/98/222f1a14f22ad2d1cbcc37edb74095264c1f9c7ab49e6423693383462b8a/gevent-24.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7021e26d70189b33c27173d4173f27bf4685d6b6f1c0ea50e5335f8491cb110c", size = 5437989, upload-time = "2024-10-18T16:23:13.851Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e8/cbb46afea3c7ecdc7289e15cb4a6f89903f4f9754a27ca320d3e465abc78/gevent-24.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34aea15f9c79f27a8faeaa361bc1e72c773a9b54a1996a2ec4eefc8bcd59a824", size = 6838539, upload-time = "2024-10-18T15:59:40.489Z" }, - { url = "https://files.pythonhosted.org/packages/69/c3/e43e348f23da404a6d4368a14453ed097cdfca97d5212eaceb987d04a0e1/gevent-24.10.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8af65a4d4feaec6042c666d22c322a310fba3b47e841ad52f724b9c3ce5da48e", size = 5513842, upload-time = "2024-10-18T16:38:29.538Z" }, - { url = "https://files.pythonhosted.org/packages/c2/76/84b7c19c072a80900118717a85236859127d630cdf8b079fe42f19649f12/gevent-24.10.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:89c4115e3f5ada55f92b61701a46043fe42f702b5af863b029e4c1a76f6cc2d4", size = 6927374, upload-time = "2024-10-18T16:02:51.669Z" }, - { url = "https://files.pythonhosted.org/packages/5e/69/0ab1b04c363547058fb5035275c144957b80b36cb6aee715fe6181b0cee9/gevent-24.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:1ce6dab94c0b0d24425ba55712de2f8c9cb21267150ca63f5bb3a0e1f165da99", size = 1546701, upload-time = "2024-10-18T15:54:53.562Z" }, - { url = "https://files.pythonhosted.org/packages/f7/2d/c783583d7999cd2f2e7aa2d6a1c333d663003ca61255a89ff6a891be95f4/gevent-24.10.3-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:f147e38423fbe96e8731f60a63475b3d2cab2f3d10578d8ee9d10c507c58a2ff", size = 2962857, upload-time = "2024-10-18T15:37:33.098Z" }, - { url = "https://files.pythonhosted.org/packages/f3/77/d3ce96fd49406f61976e9a3b6c742b97bb274d3b30c68ff190c5b5f81afd/gevent-24.10.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18e6984ec96fc95fd67488555c38ece3015be1f38b1bcceb27b7d6c36b343008", size = 5141676, upload-time = "2024-10-18T16:19:45.484Z" }, - { url = "https://files.pythonhosted.org/packages/49/f4/f99f893770c316b9d2f03bd684947126cbed0321b89fe5423838974c2025/gevent-24.10.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:051b22e2758accfddb0457728bfc9abf8c3f2ce6bca43f1ff6e07b5ed9e49bf4", size = 5310248, upload-time = "2024-10-18T16:18:51.175Z" }, - { url = "https://files.pythonhosted.org/packages/e3/0c/67257ba906f76ed82e8f0bd8c00c2a0687b360a1050b70db7e58dff749ab/gevent-24.10.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb5edb6433764119a664bbb148d2aea9990950aa89cc3498f475c2408d523ea3", size = 5407304, upload-time = "2024-10-18T16:23:15.348Z" }, - { url = "https://files.pythonhosted.org/packages/35/6c/3a72da7c224b0111728130c0f1abc3ee07feff91b37e0ea83db98f4a3eaf/gevent-24.10.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce417bcaaab496bc9c77f75566531e9d93816262037b8b2dbb88b0fdcd66587c", size = 6818624, upload-time = "2024-10-18T15:59:42.068Z" }, - { url = "https://files.pythonhosted.org/packages/a3/96/cc5f6ecba032a45fc312fe0db2908a893057fd81361eea93845d6c325556/gevent-24.10.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:1c3a828b033fb02b7c31da4d75014a1f82e6c072fc0523456569a57f8b025861", size = 5484356, upload-time = "2024-10-18T16:38:31.709Z" }, - { url = "https://files.pythonhosted.org/packages/7c/97/e680b2b2f0c291ae4db9813ffbf02c22c2a0f14c8f1a613971385e29ef67/gevent-24.10.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f2ae3efbbd120cdf4a68b7abc27a37e61e6f443c5a06ec2c6ad94c37cd8471ec", size = 6903191, upload-time = "2024-10-18T16:02:53.888Z" }, - { url = "https://files.pythonhosted.org/packages/1b/1c/b4181957da062d1c060974ec6cb798cc24aeeb28e8cd2ece84eb4b4991f7/gevent-24.10.3-cp313-cp313-win_amd64.whl", hash = "sha256:9e1210334a9bc9f76c3d008e0785ca62214f8a54e1325f6c2ecab3b6a572a015", size = 1545117, upload-time = "2024-10-18T15:45:47.375Z" }, - { url = "https://files.pythonhosted.org/packages/89/2b/bf4af9950b8f9abd5b4025858f6311930de550e3498bbfeb47c914701a1d/gevent-24.10.3-pp310-pypy310_pp73-macosx_11_0_universal2.whl", hash = "sha256:e534e6a968d74463b11de6c9c67f4b4bf61775fb00f2e6e0f7fcdd412ceade18", size = 1271541, upload-time = "2024-10-18T15:37:53.146Z" }, + { url = "https://files.pythonhosted.org/packages/6b/6f/a2100e7883c7bdfc2b45cb60b310ca748762a21596258b9dd01c5c093dbc/gevent-24.10.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:d7a1ad0f2da582f5bd238bca067e1c6c482c30c15a6e4d14aaa3215cbb2232f3", size = 3014382 }, + { url = "https://files.pythonhosted.org/packages/7a/b1/460e4884ed6185d9eb9c4c2e9639d2b254197e46513301c0f63dec22dc90/gevent-24.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4e526fdc279c655c1e809b0c34b45844182c2a6b219802da5e411bd2cf5a8ad", size = 4853460 }, + { url = "https://files.pythonhosted.org/packages/ca/f6/7ded98760d381229183ecce8db2edcce96f13e23807d31a90c66dae85304/gevent-24.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57a5c4e0bdac482c5f02f240d0354e61362df73501ef6ebafce8ef635cad7527", size = 4977636 }, + { url = "https://files.pythonhosted.org/packages/7d/21/7b928e6029eedb93ef94fc0aee701f497af2e601f0ec00aac0e72e3f450e/gevent-24.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d67daed8383326dc8b5e58d88e148d29b6b52274a489e383530b0969ae7b9cb9", size = 5058031 }, + { url = "https://files.pythonhosted.org/packages/00/98/12c03fd004fbeeca01276ffc589f5a368fd741d02582ab7006d1bdef57e7/gevent-24.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e24ffea72e27987979c009536fd0868e52239b44afe6cf7135ce8aafd0f108e", size = 6683694 }, + { url = "https://files.pythonhosted.org/packages/64/4c/ea14d971452d3da09e49267e052d8312f112c7835120aed78d22ef14efee/gevent-24.10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c1d80090485da1ea3d99205fe97908b31188c1f4857f08b333ffaf2de2e89d18", size = 5286063 }, + { url = "https://files.pythonhosted.org/packages/39/3f/397efff27e637d7306caa00d1560512c44028c25c70be1e72c46b79b1b66/gevent-24.10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f0c129f81d60cda614acb4b0c5731997ca05b031fb406fcb58ad53a7ade53b13", size = 6817462 }, + { url = "https://files.pythonhosted.org/packages/aa/5d/19939eaa7c5b7c0f37e0a0665a911ddfe1e35c25c512446fc356a065c16e/gevent-24.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:26ca7a6b42d35129617025ac801135118333cad75856ffc3217b38e707383eba", size = 1566631 }, + { url = "https://files.pythonhosted.org/packages/6e/01/1be5cf013826d8baae235976d6a94f3628014fd2db7c071aeec13f82b4d1/gevent-24.10.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:68c3a0d8402755eba7f69022e42e8021192a721ca8341908acc222ea597029b6", size = 2966909 }, + { url = "https://files.pythonhosted.org/packages/fe/3e/7fa9ab023f24d8689e2c77951981f8ea1f25089e0349a0bf8b35ee9b9277/gevent-24.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d850a453d66336272be4f1d3a8126777f3efdaea62d053b4829857f91e09755", size = 4913247 }, + { url = "https://files.pythonhosted.org/packages/db/63/6e40eaaa3c2abd1561faff11dc3e6781f8c25e975354b8835762834415af/gevent-24.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8e58ee3723f1fbe07d66892f1caa7481c306f653a6829b6fd16cb23d618a5915", size = 5049036 }, + { url = "https://files.pythonhosted.org/packages/94/89/158bc32cdc898dda0481040ac18650022e73133d93460c5af56ca622fe9a/gevent-24.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b52382124eca13135a3abe4f65c6bd428656975980a48e51b17aeab68bdb14db", size = 5107299 }, + { url = "https://files.pythonhosted.org/packages/64/91/1abe62ee350fdfac186d33f615d0d3a0b3b140e7ccf23c73547aa0deec44/gevent-24.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ca2266e08f43c0e22c028801dff7d92a0b102ef20e4caeb6a46abfb95f6a328", size = 6819625 }, + { url = "https://files.pythonhosted.org/packages/92/8b/0b2fe0d36b7c4d463e46cc68eaf6c14488bd7d86cc37e995c64a0ff7d02f/gevent-24.10.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d758f0d4dbf32502ec87bb9b536ca8055090a16f8305f0ada3ce6f34e70f2fd7", size = 5474079 }, + { url = "https://files.pythonhosted.org/packages/12/7b/9f5abbf0021a50321314f850697e0f46d2e5081168223af2d8544af9d19f/gevent-24.10.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0de6eb3d55c03138fda567d9bfed28487ce5d0928c5107549767a93efdf2be26", size = 6901323 }, + { url = "https://files.pythonhosted.org/packages/8a/63/607715c621ae78ed581b7ba36d076df63feeb352993d521327f865056771/gevent-24.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:385710355eadecdb70428a5ae3e7e5a45dcf888baa1426884588be9d25ac4290", size = 1549468 }, + { url = "https://files.pythonhosted.org/packages/d9/e4/4edbe17001bb3e6fade4ad2d85ca8f9e4eabcbde4aa29aa6889281616e3e/gevent-24.10.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3ad8fb70aa0ebc935729c9699ac31b210a49b689a7b27b7ac9f91676475f3f53", size = 2970952 }, + { url = "https://files.pythonhosted.org/packages/3c/a6/ce0824fe9398ba6b00028a74840f12be1165d5feaacdc028ea953db3d6c3/gevent-24.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f18689f7a70d2ed0e75bad5036ec3c89690a493d4cfac8d7cdb258ac04b132bd", size = 5172230 }, + { url = "https://files.pythonhosted.org/packages/25/d4/9002cfb585bfa52c860ed4b1349d1a6400bdf2df9f1bd21df5ff33eea33c/gevent-24.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f4f171d4d2018170454d84c934842e1b5f6ce7468ba298f6e7f7cff15000a3", size = 5338394 }, + { url = "https://files.pythonhosted.org/packages/0c/98/222f1a14f22ad2d1cbcc37edb74095264c1f9c7ab49e6423693383462b8a/gevent-24.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7021e26d70189b33c27173d4173f27bf4685d6b6f1c0ea50e5335f8491cb110c", size = 5437989 }, + { url = "https://files.pythonhosted.org/packages/bf/e8/cbb46afea3c7ecdc7289e15cb4a6f89903f4f9754a27ca320d3e465abc78/gevent-24.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34aea15f9c79f27a8faeaa361bc1e72c773a9b54a1996a2ec4eefc8bcd59a824", size = 6838539 }, + { url = "https://files.pythonhosted.org/packages/69/c3/e43e348f23da404a6d4368a14453ed097cdfca97d5212eaceb987d04a0e1/gevent-24.10.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8af65a4d4feaec6042c666d22c322a310fba3b47e841ad52f724b9c3ce5da48e", size = 5513842 }, + { url = "https://files.pythonhosted.org/packages/c2/76/84b7c19c072a80900118717a85236859127d630cdf8b079fe42f19649f12/gevent-24.10.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:89c4115e3f5ada55f92b61701a46043fe42f702b5af863b029e4c1a76f6cc2d4", size = 6927374 }, + { url = "https://files.pythonhosted.org/packages/5e/69/0ab1b04c363547058fb5035275c144957b80b36cb6aee715fe6181b0cee9/gevent-24.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:1ce6dab94c0b0d24425ba55712de2f8c9cb21267150ca63f5bb3a0e1f165da99", size = 1546701 }, + { url = "https://files.pythonhosted.org/packages/f7/2d/c783583d7999cd2f2e7aa2d6a1c333d663003ca61255a89ff6a891be95f4/gevent-24.10.3-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:f147e38423fbe96e8731f60a63475b3d2cab2f3d10578d8ee9d10c507c58a2ff", size = 2962857 }, + { url = "https://files.pythonhosted.org/packages/f3/77/d3ce96fd49406f61976e9a3b6c742b97bb274d3b30c68ff190c5b5f81afd/gevent-24.10.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18e6984ec96fc95fd67488555c38ece3015be1f38b1bcceb27b7d6c36b343008", size = 5141676 }, + { url = "https://files.pythonhosted.org/packages/49/f4/f99f893770c316b9d2f03bd684947126cbed0321b89fe5423838974c2025/gevent-24.10.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:051b22e2758accfddb0457728bfc9abf8c3f2ce6bca43f1ff6e07b5ed9e49bf4", size = 5310248 }, + { url = "https://files.pythonhosted.org/packages/e3/0c/67257ba906f76ed82e8f0bd8c00c2a0687b360a1050b70db7e58dff749ab/gevent-24.10.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb5edb6433764119a664bbb148d2aea9990950aa89cc3498f475c2408d523ea3", size = 5407304 }, + { url = "https://files.pythonhosted.org/packages/35/6c/3a72da7c224b0111728130c0f1abc3ee07feff91b37e0ea83db98f4a3eaf/gevent-24.10.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce417bcaaab496bc9c77f75566531e9d93816262037b8b2dbb88b0fdcd66587c", size = 6818624 }, + { url = "https://files.pythonhosted.org/packages/a3/96/cc5f6ecba032a45fc312fe0db2908a893057fd81361eea93845d6c325556/gevent-24.10.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:1c3a828b033fb02b7c31da4d75014a1f82e6c072fc0523456569a57f8b025861", size = 5484356 }, + { url = "https://files.pythonhosted.org/packages/7c/97/e680b2b2f0c291ae4db9813ffbf02c22c2a0f14c8f1a613971385e29ef67/gevent-24.10.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f2ae3efbbd120cdf4a68b7abc27a37e61e6f443c5a06ec2c6ad94c37cd8471ec", size = 6903191 }, + { url = "https://files.pythonhosted.org/packages/1b/1c/b4181957da062d1c060974ec6cb798cc24aeeb28e8cd2ece84eb4b4991f7/gevent-24.10.3-cp313-cp313-win_amd64.whl", hash = "sha256:9e1210334a9bc9f76c3d008e0785ca62214f8a54e1325f6c2ecab3b6a572a015", size = 1545117 }, + { url = "https://files.pythonhosted.org/packages/89/2b/bf4af9950b8f9abd5b4025858f6311930de550e3498bbfeb47c914701a1d/gevent-24.10.3-pp310-pypy310_pp73-macosx_11_0_universal2.whl", hash = "sha256:e534e6a968d74463b11de6c9c67f4b4bf61775fb00f2e6e0f7fcdd412ceade18", size = 1271541 }, ] [[package]] @@ -836,103 +828,103 @@ dependencies = [ { name = "gevent" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8c/14/d4eddae757de44985718a9e38d9e6f2a923d764ed97d0f1cbc1a8aa2b0ef/geventhttpclient-2.3.1.tar.gz", hash = "sha256:b40ddac8517c456818942c7812f555f84702105c82783238c9fcb8dc12675185", size = 69345, upload-time = "2024-04-18T21:39:50.83Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/14/d4eddae757de44985718a9e38d9e6f2a923d764ed97d0f1cbc1a8aa2b0ef/geventhttpclient-2.3.1.tar.gz", hash = "sha256:b40ddac8517c456818942c7812f555f84702105c82783238c9fcb8dc12675185", size = 69345 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/a5/5e49d6a581b3f1399425e22961c6e341e90c12fa2193ed0adee9afbd864c/geventhttpclient-2.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:da22ab7bf5af4ba3d07cffee6de448b42696e53e7ac1fe97ed289037733bf1c2", size = 71729, upload-time = "2024-04-18T21:38:06.866Z" }, - { url = "https://files.pythonhosted.org/packages/eb/23/4ff584e5f344dae64b5bc588b65c4ea81083f9d662b9f64cf5f28e5ae9cc/geventhttpclient-2.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2399e3d4e2fae8bbd91756189da6e9d84adf8f3eaace5eef0667874a705a29f8", size = 52062, upload-time = "2024-04-18T21:38:08.433Z" }, - { url = "https://files.pythonhosted.org/packages/bb/60/6bd8badb97b31a49f4c2b79466abce208a97dad95d447893c7546063fc8a/geventhttpclient-2.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3e33e87d0d5b9f5782c4e6d3cb7e3592fea41af52713137d04776df7646d71b", size = 51645, upload-time = "2024-04-18T21:38:10.139Z" }, - { url = "https://files.pythonhosted.org/packages/e1/62/47d431bf05f74aa683d63163a11432bda8f576c86dec8c3bc9d6a156ee03/geventhttpclient-2.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c071db313866c3d0510feb6c0f40ec086ccf7e4a845701b6316c82c06e8b9b29", size = 117838, upload-time = "2024-04-18T21:38:12.036Z" }, - { url = "https://files.pythonhosted.org/packages/6c/8b/e7c9ae813bb41883a96ad9afcf86465219c3bb682daa8b09448481edef8a/geventhttpclient-2.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f36f0c6ef88a27e60af8369d9c2189fe372c6f2943182a7568e0f2ad33bb69f1", size = 123272, upload-time = "2024-04-18T21:38:13.704Z" }, - { url = "https://files.pythonhosted.org/packages/4d/26/71e9b2526009faadda9f588dac04f8bf837a5b97628ab44145efc3fa796e/geventhttpclient-2.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4624843c03a5337282a42247d987c2531193e57255ee307b36eeb4f243a0c21", size = 114319, upload-time = "2024-04-18T21:38:15.097Z" }, - { url = "https://files.pythonhosted.org/packages/34/8c/1da2960293c42b7a6b01dbe3204b569e4cdb55b8289cb1c7154826500f19/geventhttpclient-2.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d614573621ba827c417786057e1e20e9f96c4f6b3878c55b1b7b54e1026693bc", size = 112705, upload-time = "2024-04-18T21:38:17.005Z" }, - { url = "https://files.pythonhosted.org/packages/a7/a1/4d08ecf0f213fdc63f78a217f87c07c1cb9891e68cdf74c8cbca76298bdb/geventhttpclient-2.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5d51330a40ac9762879d0e296c279c1beae8cfa6484bb196ac829242c416b709", size = 121236, upload-time = "2024-04-18T21:38:18.831Z" }, - { url = "https://files.pythonhosted.org/packages/4f/f7/42ece3e1f54602c518d74364a214da3b35b6be267b335564b7e9f0d37705/geventhttpclient-2.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bc9f2162d4e8cb86bb5322d99bfd552088a3eacd540a841298f06bb8bc1f1f03", size = 117859, upload-time = "2024-04-18T21:38:20.917Z" }, - { url = "https://files.pythonhosted.org/packages/1f/8e/de026b3697bffe5fa1a4938a3882107e378eea826905acf8e46c69b71ffd/geventhttpclient-2.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:06e59d3397e63c65ecc7a7561a5289f0cf2e2c2252e29632741e792f57f5d124", size = 127268, upload-time = "2024-04-18T21:38:22.676Z" }, - { url = "https://files.pythonhosted.org/packages/54/bf/1ee99a322467e6825a24612d306a46ca94b51088170d1b5de0df1c82ab2a/geventhttpclient-2.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4436eef515b3e0c1d4a453ae32e047290e780a623c1eddb11026ae9d5fb03d42", size = 116426, upload-time = "2024-04-18T21:38:24.228Z" }, - { url = "https://files.pythonhosted.org/packages/72/54/10c8ec745b3dcbfd52af62977fec85829749c0325e1a5429d050a4b45e75/geventhttpclient-2.3.1-cp310-cp310-win32.whl", hash = "sha256:5d1cf7d8a4f8e15cc8fd7d88ac4cdb058d6274203a42587e594cc9f0850ac862", size = 47599, upload-time = "2024-04-18T21:38:26.385Z" }, - { url = "https://files.pythonhosted.org/packages/da/0d/36a47cdeaa83c3b4efdbd18d77720fa27dc40600998f4dedd7c4a1259862/geventhttpclient-2.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:4deaebc121036f7ea95430c2d0f80ab085b15280e6ab677a6360b70e57020e7f", size = 48302, upload-time = "2024-04-18T21:38:28.297Z" }, - { url = "https://files.pythonhosted.org/packages/56/ad/1fcbbea0465f04d4425960e3737d4d8ae6407043cfc88688fb17b9064160/geventhttpclient-2.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f0ae055b9ce1704f2ce72c0847df28f4e14dbb3eea79256cda6c909d82688ea3", size = 71733, upload-time = "2024-04-18T21:38:30.357Z" }, - { url = "https://files.pythonhosted.org/packages/06/1a/10e547adb675beea407ff7117ecb4e5063534569ac14bb4360279d2888dd/geventhttpclient-2.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f087af2ac439495b5388841d6f3c4de8d2573ca9870593d78f7b554aa5cfa7f5", size = 52060, upload-time = "2024-04-18T21:38:32.561Z" }, - { url = "https://files.pythonhosted.org/packages/e0/c0/9960ac6e8818a00702743cd2a9637d6f26909ac7ac59ca231f446e367b20/geventhttpclient-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:76c367d175810facfe56281e516c9a5a4a191eff76641faaa30aa33882ed4b2f", size = 51649, upload-time = "2024-04-18T21:38:34.265Z" }, - { url = "https://files.pythonhosted.org/packages/58/3a/b032cd8f885dafdfa002a8a0e4e21b633713798ec08e19010b815fbfead6/geventhttpclient-2.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a58376d0d461fe0322ff2ad362553b437daee1eeb92b4c0e3b1ffef9e77defbe", size = 117987, upload-time = "2024-04-18T21:38:37.661Z" }, - { url = "https://files.pythonhosted.org/packages/94/36/6493a5cbc20c269a51186946947f3ca2eae687e05831289891027bd038c3/geventhttpclient-2.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f440cc704f8a9869848a109b2c401805c17c070539b2014e7b884ecfc8591e33", size = 123356, upload-time = "2024-04-18T21:38:39.705Z" }, - { url = "https://files.pythonhosted.org/packages/2f/07/b66d9a13b97a7e59d84b4faf704113aa963aaf3a0f71c9138c8740d57d5c/geventhttpclient-2.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f10c62994f9052f23948c19de930b2d1f063240462c8bd7077c2b3290e61f4fa", size = 114460, upload-time = "2024-04-18T21:38:40.947Z" }, - { url = "https://files.pythonhosted.org/packages/4e/72/1467b9e1ef63aecfe3b42333fb7607f66129dffaeca231f97e4be6f71803/geventhttpclient-2.3.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52c45d9f3dd9627844c12e9ca347258c7be585bed54046336220e25ea6eac155", size = 112808, upload-time = "2024-04-18T21:38:42.684Z" }, - { url = "https://files.pythonhosted.org/packages/ce/ef/64894efd67cb3459074c734736ecacff398cd841a5538dc70e3e77d35500/geventhttpclient-2.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:77c1a2c6e3854bf87cd5588b95174640c8a881716bd07fa0d131d082270a6795", size = 122049, upload-time = "2024-04-18T21:38:44.184Z" }, - { url = "https://files.pythonhosted.org/packages/c5/c8/1b13b4ea4bb88d7c2db56d070a52daf4757b3139afd83885e81455cb422f/geventhttpclient-2.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ce649d4e25c2d56023471df0bf1e8e2ab67dfe4ff12ce3e8fe7e6fae30cd672a", size = 118755, upload-time = "2024-04-18T21:38:45.654Z" }, - { url = "https://files.pythonhosted.org/packages/d1/06/95ac63fa1ee118a4d5824aa0a6b0dc3a2934a2f4ce695bf6747e1744d813/geventhttpclient-2.3.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:265d9f31b4ac8f688eebef0bd4c814ffb37a16f769ad0c8c8b8c24a84db8eab5", size = 128053, upload-time = "2024-04-18T21:38:47.247Z" }, - { url = "https://files.pythonhosted.org/packages/8a/27/3d6dbbd128e1b965bae198bffa4b5552cd635397e3d2bbcc7d9592890ca9/geventhttpclient-2.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2de436a9d61dae877e4e811fb3e2594e2a1df1b18f4280878f318aef48a562b9", size = 117316, upload-time = "2024-04-18T21:38:49.086Z" }, - { url = "https://files.pythonhosted.org/packages/ed/9a/8b65daf417ff982fa1928ebc6ebdfb081750d426f877f0056288aaa689e8/geventhttpclient-2.3.1-cp311-cp311-win32.whl", hash = "sha256:83e22178b9480b0a95edf0053d4f30b717d0b696b3c262beabe6964d9c5224b1", size = 47598, upload-time = "2024-04-18T21:38:50.919Z" }, - { url = "https://files.pythonhosted.org/packages/ab/83/ed0d14787861cf30beddd3aadc29ad07d75555de43c629ba514ddd2978d0/geventhttpclient-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:97b072a282233384c1302a7dee88ad8bfedc916f06b1bc1da54f84980f1406a9", size = 48301, upload-time = "2024-04-18T21:38:52.14Z" }, - { url = "https://files.pythonhosted.org/packages/82/ee/bf3d26170a518d2b1254f44202f2fa4490496b476ee24046ff6c34e79c08/geventhttpclient-2.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e1c90abcc2735cd8dd2d2572a13da32f6625392dc04862decb5c6476a3ddee22", size = 71742, upload-time = "2024-04-18T21:38:54.167Z" }, - { url = "https://files.pythonhosted.org/packages/77/72/bd64b2a491094a3fbf7f3c314bb3c3918afb652783a8a9db07b86072da35/geventhttpclient-2.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5deb41c2f51247b4e568c14964f59d7b8e537eff51900564c88af3200004e678", size = 52070, upload-time = "2024-04-18T21:38:55.484Z" }, - { url = "https://files.pythonhosted.org/packages/85/96/e25becfde16c5551ba04ed2beac1f018e2efc70275ec19ae3765ff634ff2/geventhttpclient-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c6f1a56a66a90c4beae2f009b5e9d42db9a58ced165aa35441ace04d69cb7b37", size = 51650, upload-time = "2024-04-18T21:38:57.022Z" }, - { url = "https://files.pythonhosted.org/packages/5d/b8/fe6e938a369b3742103d04e5771e1ec7b18c047ac30b06a8e9704e2d34fc/geventhttpclient-2.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ee6e741849c29e3129b1ec3828ac3a5e5dcb043402f852ea92c52334fb8cabf", size = 118507, upload-time = "2024-04-18T21:38:58.936Z" }, - { url = "https://files.pythonhosted.org/packages/68/0b/381d01de049b02dc70addbcc1c8e24d15500bff6a9e89103c4aa8eb352c3/geventhttpclient-2.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0d0972096a63b1ddaa73fa3dab2c7a136e3ab8bf7999a2f85a5dee851fa77cdd", size = 124061, upload-time = "2024-04-18T21:39:00.473Z" }, - { url = "https://files.pythonhosted.org/packages/c6/e6/7c97b5bf41cc403b2936a0887a85550b3153aa4b60c0c5062c49cd6286f2/geventhttpclient-2.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00675ba682fb7d19d659c14686fa8a52a65e3f301b56c2a4ee6333b380dd9467", size = 115060, upload-time = "2024-04-18T21:39:02.323Z" }, - { url = "https://files.pythonhosted.org/packages/45/1f/3e02464449c74a8146f27218471578c1dfabf18731cf047520b76e1b6331/geventhttpclient-2.3.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea77b67c186df90473416f4403839728f70ef6cf1689cec97b4f6bbde392a8a8", size = 113762, upload-time = "2024-04-18T21:39:03.769Z" }, - { url = "https://files.pythonhosted.org/packages/4f/a4/08551776f7d6b219d6f73ca25be88806007b16af51a1dbfed7192528e1c3/geventhttpclient-2.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ddcc3f0fdffd9a3801e1005b73026202cffed8199863fdef9315bea9a860a032", size = 122018, upload-time = "2024-04-18T21:39:05.781Z" }, - { url = "https://files.pythonhosted.org/packages/70/14/ba91417ac7cbce8d553f72c885a19c6b9d7f9dc7de81b7814551cf020a57/geventhttpclient-2.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:c9f1ef4ec048563cc621a47ff01a4f10048ff8b676d7a4d75e5433ed8e703e56", size = 118884, upload-time = "2024-04-18T21:39:08.001Z" }, - { url = "https://files.pythonhosted.org/packages/7c/78/e1f2c30e11bda8347a74b3a7254f727ff53ea260244da77d76b96779a006/geventhttpclient-2.3.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:a364b30bec7a0a00dbe256e2b6807e4dc866bead7ac84aaa51ca5e2c3d15c258", size = 128224, upload-time = "2024-04-18T21:39:09.31Z" }, - { url = "https://files.pythonhosted.org/packages/ac/2f/b7fd96e9cfa9d9719b0c9feb50b4cbb341d1940e34fd3305006efa8c3e33/geventhttpclient-2.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:25d255383d3d6a6fbd643bb51ae1a7e4f6f7b0dbd5f3225b537d0bd0432eaf39", size = 117758, upload-time = "2024-04-18T21:39:11.287Z" }, - { url = "https://files.pythonhosted.org/packages/fb/e0/1384c9a76379ab257b75df92283797861dcae592dd98e471df254f87c635/geventhttpclient-2.3.1-cp312-cp312-win32.whl", hash = "sha256:ad0b507e354d2f398186dcb12fe526d0594e7c9387b514fb843f7a14fdf1729a", size = 47595, upload-time = "2024-04-18T21:39:12.535Z" }, - { url = "https://files.pythonhosted.org/packages/54/e3/6b8dbb24e3941e20abbe7736e59290c5d4182057ea1d984d46c853208bcd/geventhttpclient-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:7924e0883bc2b177cfe27aa65af6bb9dd57f3e26905c7675a2d1f3ef69df7cca", size = 48271, upload-time = "2024-04-18T21:39:14.479Z" }, - { url = "https://files.pythonhosted.org/packages/ee/9f/251b1b7e665523137a8711f0f0029196cf18b57741135f01aea80a56f16c/geventhttpclient-2.3.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c31431e38df45b3c79bf3c9427c796adb8263d622bc6fa25e2f6ba916c2aad93", size = 49827, upload-time = "2024-04-18T21:39:36.14Z" }, - { url = "https://files.pythonhosted.org/packages/74/c7/ad4c23de669191e1c83cfa28c51d3b50fc246d72e1ee40d4d5b330532492/geventhttpclient-2.3.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:855ab1e145575769b180b57accb0573a77cd6a7392f40a6ef7bc9a4926ebd77b", size = 54017, upload-time = "2024-04-18T21:39:37.577Z" }, - { url = "https://files.pythonhosted.org/packages/04/7b/59fc8c8fbd10596abfc46dc103654e3d9676de64229d8eee4b0a4ac2e890/geventhttpclient-2.3.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a374aad77c01539e786d0c7829bec2eba034ccd45733c1bf9811ad18d2a8ecd", size = 58359, upload-time = "2024-04-18T21:39:39.437Z" }, - { url = "https://files.pythonhosted.org/packages/94/b7/743552b0ecda75458c83d55d62937e29c9ee9a42598f57d4025d5de70004/geventhttpclient-2.3.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66c1e97460608304f400485ac099736fff3566d3d8db2038533d466f8cf5de5a", size = 54262, upload-time = "2024-04-18T21:39:40.866Z" }, - { url = "https://files.pythonhosted.org/packages/18/60/10f6215b6cc76b5845a7f4b9c3d1f47d7ecd84ce8769b1e27e0482d605d7/geventhttpclient-2.3.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4f843f81ee44ba4c553a1b3f73115e0ad8f00044023c24db29f5b1df3da08465", size = 48343, upload-time = "2024-04-18T21:39:42.173Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a5/5e49d6a581b3f1399425e22961c6e341e90c12fa2193ed0adee9afbd864c/geventhttpclient-2.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:da22ab7bf5af4ba3d07cffee6de448b42696e53e7ac1fe97ed289037733bf1c2", size = 71729 }, + { url = "https://files.pythonhosted.org/packages/eb/23/4ff584e5f344dae64b5bc588b65c4ea81083f9d662b9f64cf5f28e5ae9cc/geventhttpclient-2.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2399e3d4e2fae8bbd91756189da6e9d84adf8f3eaace5eef0667874a705a29f8", size = 52062 }, + { url = "https://files.pythonhosted.org/packages/bb/60/6bd8badb97b31a49f4c2b79466abce208a97dad95d447893c7546063fc8a/geventhttpclient-2.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3e33e87d0d5b9f5782c4e6d3cb7e3592fea41af52713137d04776df7646d71b", size = 51645 }, + { url = "https://files.pythonhosted.org/packages/e1/62/47d431bf05f74aa683d63163a11432bda8f576c86dec8c3bc9d6a156ee03/geventhttpclient-2.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c071db313866c3d0510feb6c0f40ec086ccf7e4a845701b6316c82c06e8b9b29", size = 117838 }, + { url = "https://files.pythonhosted.org/packages/6c/8b/e7c9ae813bb41883a96ad9afcf86465219c3bb682daa8b09448481edef8a/geventhttpclient-2.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f36f0c6ef88a27e60af8369d9c2189fe372c6f2943182a7568e0f2ad33bb69f1", size = 123272 }, + { url = "https://files.pythonhosted.org/packages/4d/26/71e9b2526009faadda9f588dac04f8bf837a5b97628ab44145efc3fa796e/geventhttpclient-2.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4624843c03a5337282a42247d987c2531193e57255ee307b36eeb4f243a0c21", size = 114319 }, + { url = "https://files.pythonhosted.org/packages/34/8c/1da2960293c42b7a6b01dbe3204b569e4cdb55b8289cb1c7154826500f19/geventhttpclient-2.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d614573621ba827c417786057e1e20e9f96c4f6b3878c55b1b7b54e1026693bc", size = 112705 }, + { url = "https://files.pythonhosted.org/packages/a7/a1/4d08ecf0f213fdc63f78a217f87c07c1cb9891e68cdf74c8cbca76298bdb/geventhttpclient-2.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5d51330a40ac9762879d0e296c279c1beae8cfa6484bb196ac829242c416b709", size = 121236 }, + { url = "https://files.pythonhosted.org/packages/4f/f7/42ece3e1f54602c518d74364a214da3b35b6be267b335564b7e9f0d37705/geventhttpclient-2.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bc9f2162d4e8cb86bb5322d99bfd552088a3eacd540a841298f06bb8bc1f1f03", size = 117859 }, + { url = "https://files.pythonhosted.org/packages/1f/8e/de026b3697bffe5fa1a4938a3882107e378eea826905acf8e46c69b71ffd/geventhttpclient-2.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:06e59d3397e63c65ecc7a7561a5289f0cf2e2c2252e29632741e792f57f5d124", size = 127268 }, + { url = "https://files.pythonhosted.org/packages/54/bf/1ee99a322467e6825a24612d306a46ca94b51088170d1b5de0df1c82ab2a/geventhttpclient-2.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4436eef515b3e0c1d4a453ae32e047290e780a623c1eddb11026ae9d5fb03d42", size = 116426 }, + { url = "https://files.pythonhosted.org/packages/72/54/10c8ec745b3dcbfd52af62977fec85829749c0325e1a5429d050a4b45e75/geventhttpclient-2.3.1-cp310-cp310-win32.whl", hash = "sha256:5d1cf7d8a4f8e15cc8fd7d88ac4cdb058d6274203a42587e594cc9f0850ac862", size = 47599 }, + { url = "https://files.pythonhosted.org/packages/da/0d/36a47cdeaa83c3b4efdbd18d77720fa27dc40600998f4dedd7c4a1259862/geventhttpclient-2.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:4deaebc121036f7ea95430c2d0f80ab085b15280e6ab677a6360b70e57020e7f", size = 48302 }, + { url = "https://files.pythonhosted.org/packages/56/ad/1fcbbea0465f04d4425960e3737d4d8ae6407043cfc88688fb17b9064160/geventhttpclient-2.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f0ae055b9ce1704f2ce72c0847df28f4e14dbb3eea79256cda6c909d82688ea3", size = 71733 }, + { url = "https://files.pythonhosted.org/packages/06/1a/10e547adb675beea407ff7117ecb4e5063534569ac14bb4360279d2888dd/geventhttpclient-2.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f087af2ac439495b5388841d6f3c4de8d2573ca9870593d78f7b554aa5cfa7f5", size = 52060 }, + { url = "https://files.pythonhosted.org/packages/e0/c0/9960ac6e8818a00702743cd2a9637d6f26909ac7ac59ca231f446e367b20/geventhttpclient-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:76c367d175810facfe56281e516c9a5a4a191eff76641faaa30aa33882ed4b2f", size = 51649 }, + { url = "https://files.pythonhosted.org/packages/58/3a/b032cd8f885dafdfa002a8a0e4e21b633713798ec08e19010b815fbfead6/geventhttpclient-2.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a58376d0d461fe0322ff2ad362553b437daee1eeb92b4c0e3b1ffef9e77defbe", size = 117987 }, + { url = "https://files.pythonhosted.org/packages/94/36/6493a5cbc20c269a51186946947f3ca2eae687e05831289891027bd038c3/geventhttpclient-2.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f440cc704f8a9869848a109b2c401805c17c070539b2014e7b884ecfc8591e33", size = 123356 }, + { url = "https://files.pythonhosted.org/packages/2f/07/b66d9a13b97a7e59d84b4faf704113aa963aaf3a0f71c9138c8740d57d5c/geventhttpclient-2.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f10c62994f9052f23948c19de930b2d1f063240462c8bd7077c2b3290e61f4fa", size = 114460 }, + { url = "https://files.pythonhosted.org/packages/4e/72/1467b9e1ef63aecfe3b42333fb7607f66129dffaeca231f97e4be6f71803/geventhttpclient-2.3.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52c45d9f3dd9627844c12e9ca347258c7be585bed54046336220e25ea6eac155", size = 112808 }, + { url = "https://files.pythonhosted.org/packages/ce/ef/64894efd67cb3459074c734736ecacff398cd841a5538dc70e3e77d35500/geventhttpclient-2.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:77c1a2c6e3854bf87cd5588b95174640c8a881716bd07fa0d131d082270a6795", size = 122049 }, + { url = "https://files.pythonhosted.org/packages/c5/c8/1b13b4ea4bb88d7c2db56d070a52daf4757b3139afd83885e81455cb422f/geventhttpclient-2.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ce649d4e25c2d56023471df0bf1e8e2ab67dfe4ff12ce3e8fe7e6fae30cd672a", size = 118755 }, + { url = "https://files.pythonhosted.org/packages/d1/06/95ac63fa1ee118a4d5824aa0a6b0dc3a2934a2f4ce695bf6747e1744d813/geventhttpclient-2.3.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:265d9f31b4ac8f688eebef0bd4c814ffb37a16f769ad0c8c8b8c24a84db8eab5", size = 128053 }, + { url = "https://files.pythonhosted.org/packages/8a/27/3d6dbbd128e1b965bae198bffa4b5552cd635397e3d2bbcc7d9592890ca9/geventhttpclient-2.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2de436a9d61dae877e4e811fb3e2594e2a1df1b18f4280878f318aef48a562b9", size = 117316 }, + { url = "https://files.pythonhosted.org/packages/ed/9a/8b65daf417ff982fa1928ebc6ebdfb081750d426f877f0056288aaa689e8/geventhttpclient-2.3.1-cp311-cp311-win32.whl", hash = "sha256:83e22178b9480b0a95edf0053d4f30b717d0b696b3c262beabe6964d9c5224b1", size = 47598 }, + { url = "https://files.pythonhosted.org/packages/ab/83/ed0d14787861cf30beddd3aadc29ad07d75555de43c629ba514ddd2978d0/geventhttpclient-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:97b072a282233384c1302a7dee88ad8bfedc916f06b1bc1da54f84980f1406a9", size = 48301 }, + { url = "https://files.pythonhosted.org/packages/82/ee/bf3d26170a518d2b1254f44202f2fa4490496b476ee24046ff6c34e79c08/geventhttpclient-2.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e1c90abcc2735cd8dd2d2572a13da32f6625392dc04862decb5c6476a3ddee22", size = 71742 }, + { url = "https://files.pythonhosted.org/packages/77/72/bd64b2a491094a3fbf7f3c314bb3c3918afb652783a8a9db07b86072da35/geventhttpclient-2.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5deb41c2f51247b4e568c14964f59d7b8e537eff51900564c88af3200004e678", size = 52070 }, + { url = "https://files.pythonhosted.org/packages/85/96/e25becfde16c5551ba04ed2beac1f018e2efc70275ec19ae3765ff634ff2/geventhttpclient-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c6f1a56a66a90c4beae2f009b5e9d42db9a58ced165aa35441ace04d69cb7b37", size = 51650 }, + { url = "https://files.pythonhosted.org/packages/5d/b8/fe6e938a369b3742103d04e5771e1ec7b18c047ac30b06a8e9704e2d34fc/geventhttpclient-2.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ee6e741849c29e3129b1ec3828ac3a5e5dcb043402f852ea92c52334fb8cabf", size = 118507 }, + { url = "https://files.pythonhosted.org/packages/68/0b/381d01de049b02dc70addbcc1c8e24d15500bff6a9e89103c4aa8eb352c3/geventhttpclient-2.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0d0972096a63b1ddaa73fa3dab2c7a136e3ab8bf7999a2f85a5dee851fa77cdd", size = 124061 }, + { url = "https://files.pythonhosted.org/packages/c6/e6/7c97b5bf41cc403b2936a0887a85550b3153aa4b60c0c5062c49cd6286f2/geventhttpclient-2.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00675ba682fb7d19d659c14686fa8a52a65e3f301b56c2a4ee6333b380dd9467", size = 115060 }, + { url = "https://files.pythonhosted.org/packages/45/1f/3e02464449c74a8146f27218471578c1dfabf18731cf047520b76e1b6331/geventhttpclient-2.3.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea77b67c186df90473416f4403839728f70ef6cf1689cec97b4f6bbde392a8a8", size = 113762 }, + { url = "https://files.pythonhosted.org/packages/4f/a4/08551776f7d6b219d6f73ca25be88806007b16af51a1dbfed7192528e1c3/geventhttpclient-2.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ddcc3f0fdffd9a3801e1005b73026202cffed8199863fdef9315bea9a860a032", size = 122018 }, + { url = "https://files.pythonhosted.org/packages/70/14/ba91417ac7cbce8d553f72c885a19c6b9d7f9dc7de81b7814551cf020a57/geventhttpclient-2.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:c9f1ef4ec048563cc621a47ff01a4f10048ff8b676d7a4d75e5433ed8e703e56", size = 118884 }, + { url = "https://files.pythonhosted.org/packages/7c/78/e1f2c30e11bda8347a74b3a7254f727ff53ea260244da77d76b96779a006/geventhttpclient-2.3.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:a364b30bec7a0a00dbe256e2b6807e4dc866bead7ac84aaa51ca5e2c3d15c258", size = 128224 }, + { url = "https://files.pythonhosted.org/packages/ac/2f/b7fd96e9cfa9d9719b0c9feb50b4cbb341d1940e34fd3305006efa8c3e33/geventhttpclient-2.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:25d255383d3d6a6fbd643bb51ae1a7e4f6f7b0dbd5f3225b537d0bd0432eaf39", size = 117758 }, + { url = "https://files.pythonhosted.org/packages/fb/e0/1384c9a76379ab257b75df92283797861dcae592dd98e471df254f87c635/geventhttpclient-2.3.1-cp312-cp312-win32.whl", hash = "sha256:ad0b507e354d2f398186dcb12fe526d0594e7c9387b514fb843f7a14fdf1729a", size = 47595 }, + { url = "https://files.pythonhosted.org/packages/54/e3/6b8dbb24e3941e20abbe7736e59290c5d4182057ea1d984d46c853208bcd/geventhttpclient-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:7924e0883bc2b177cfe27aa65af6bb9dd57f3e26905c7675a2d1f3ef69df7cca", size = 48271 }, + { url = "https://files.pythonhosted.org/packages/ee/9f/251b1b7e665523137a8711f0f0029196cf18b57741135f01aea80a56f16c/geventhttpclient-2.3.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c31431e38df45b3c79bf3c9427c796adb8263d622bc6fa25e2f6ba916c2aad93", size = 49827 }, + { url = "https://files.pythonhosted.org/packages/74/c7/ad4c23de669191e1c83cfa28c51d3b50fc246d72e1ee40d4d5b330532492/geventhttpclient-2.3.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:855ab1e145575769b180b57accb0573a77cd6a7392f40a6ef7bc9a4926ebd77b", size = 54017 }, + { url = "https://files.pythonhosted.org/packages/04/7b/59fc8c8fbd10596abfc46dc103654e3d9676de64229d8eee4b0a4ac2e890/geventhttpclient-2.3.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a374aad77c01539e786d0c7829bec2eba034ccd45733c1bf9811ad18d2a8ecd", size = 58359 }, + { url = "https://files.pythonhosted.org/packages/94/b7/743552b0ecda75458c83d55d62937e29c9ee9a42598f57d4025d5de70004/geventhttpclient-2.3.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66c1e97460608304f400485ac099736fff3566d3d8db2038533d466f8cf5de5a", size = 54262 }, + { url = "https://files.pythonhosted.org/packages/18/60/10f6215b6cc76b5845a7f4b9c3d1f47d7ecd84ce8769b1e27e0482d605d7/geventhttpclient-2.3.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4f843f81ee44ba4c553a1b3f73115e0ad8f00044023c24db29f5b1df3da08465", size = 48343 }, ] [[package]] name = "greenlet" version = "3.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2f/ff/df5fede753cc10f6a5be0931204ea30c35fa2f2ea7a35b25bdaf4fe40e46/greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467", size = 186022, upload-time = "2024-09-20T18:21:04.506Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/ff/df5fede753cc10f6a5be0931204ea30c35fa2f2ea7a35b25bdaf4fe40e46/greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467", size = 186022 } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/90/5234a78dc0ef6496a6eb97b67a42a8e96742a56f7dc808cb954a85390448/greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563", size = 271235, upload-time = "2024-09-20T17:07:18.761Z" }, - { url = "https://files.pythonhosted.org/packages/7c/16/cd631fa0ab7d06ef06387135b7549fdcc77d8d859ed770a0d28e47b20972/greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83", size = 637168, upload-time = "2024-09-20T17:36:43.774Z" }, - { url = "https://files.pythonhosted.org/packages/2f/b1/aed39043a6fec33c284a2c9abd63ce191f4f1a07319340ffc04d2ed3256f/greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0", size = 648826, upload-time = "2024-09-20T17:39:16.921Z" }, - { url = "https://files.pythonhosted.org/packages/76/25/40e0112f7f3ebe54e8e8ed91b2b9f970805143efef16d043dfc15e70f44b/greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120", size = 644443, upload-time = "2024-09-20T17:44:21.896Z" }, - { url = "https://files.pythonhosted.org/packages/fb/2f/3850b867a9af519794784a7eeed1dd5bc68ffbcc5b28cef703711025fd0a/greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc", size = 643295, upload-time = "2024-09-20T17:08:37.951Z" }, - { url = "https://files.pythonhosted.org/packages/cf/69/79e4d63b9387b48939096e25115b8af7cd8a90397a304f92436bcb21f5b2/greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617", size = 599544, upload-time = "2024-09-20T17:08:27.894Z" }, - { url = "https://files.pythonhosted.org/packages/46/1d/44dbcb0e6c323bd6f71b8c2f4233766a5faf4b8948873225d34a0b7efa71/greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7", size = 1125456, upload-time = "2024-09-20T17:44:11.755Z" }, - { url = "https://files.pythonhosted.org/packages/e0/1d/a305dce121838d0278cee39d5bb268c657f10a5363ae4b726848f833f1bb/greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6", size = 1149111, upload-time = "2024-09-20T17:09:22.104Z" }, - { url = "https://files.pythonhosted.org/packages/96/28/d62835fb33fb5652f2e98d34c44ad1a0feacc8b1d3f1aecab035f51f267d/greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80", size = 298392, upload-time = "2024-09-20T17:28:51.988Z" }, - { url = "https://files.pythonhosted.org/packages/28/62/1c2665558618553c42922ed47a4e6d6527e2fa3516a8256c2f431c5d0441/greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70", size = 272479, upload-time = "2024-09-20T17:07:22.332Z" }, - { url = "https://files.pythonhosted.org/packages/76/9d/421e2d5f07285b6e4e3a676b016ca781f63cfe4a0cd8eaecf3fd6f7a71ae/greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159", size = 640404, upload-time = "2024-09-20T17:36:45.588Z" }, - { url = "https://files.pythonhosted.org/packages/e5/de/6e05f5c59262a584e502dd3d261bbdd2c97ab5416cc9c0b91ea38932a901/greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e", size = 652813, upload-time = "2024-09-20T17:39:19.052Z" }, - { url = "https://files.pythonhosted.org/packages/49/93/d5f93c84241acdea15a8fd329362c2c71c79e1a507c3f142a5d67ea435ae/greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1", size = 648517, upload-time = "2024-09-20T17:44:24.101Z" }, - { url = "https://files.pythonhosted.org/packages/15/85/72f77fc02d00470c86a5c982b8daafdf65d38aefbbe441cebff3bf7037fc/greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383", size = 647831, upload-time = "2024-09-20T17:08:40.577Z" }, - { url = "https://files.pythonhosted.org/packages/f7/4b/1c9695aa24f808e156c8f4813f685d975ca73c000c2a5056c514c64980f6/greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a", size = 602413, upload-time = "2024-09-20T17:08:31.728Z" }, - { url = "https://files.pythonhosted.org/packages/76/70/ad6e5b31ef330f03b12559d19fda2606a522d3849cde46b24f223d6d1619/greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511", size = 1129619, upload-time = "2024-09-20T17:44:14.222Z" }, - { url = "https://files.pythonhosted.org/packages/f4/fb/201e1b932e584066e0f0658b538e73c459b34d44b4bd4034f682423bc801/greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395", size = 1155198, upload-time = "2024-09-20T17:09:23.903Z" }, - { url = "https://files.pythonhosted.org/packages/12/da/b9ed5e310bb8b89661b80cbcd4db5a067903bbcd7fc854923f5ebb4144f0/greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39", size = 298930, upload-time = "2024-09-20T17:25:18.656Z" }, - { url = "https://files.pythonhosted.org/packages/7d/ec/bad1ac26764d26aa1353216fcbfa4670050f66d445448aafa227f8b16e80/greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d", size = 274260, upload-time = "2024-09-20T17:08:07.301Z" }, - { url = "https://files.pythonhosted.org/packages/66/d4/c8c04958870f482459ab5956c2942c4ec35cac7fe245527f1039837c17a9/greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79", size = 649064, upload-time = "2024-09-20T17:36:47.628Z" }, - { url = "https://files.pythonhosted.org/packages/51/41/467b12a8c7c1303d20abcca145db2be4e6cd50a951fa30af48b6ec607581/greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa", size = 663420, upload-time = "2024-09-20T17:39:21.258Z" }, - { url = "https://files.pythonhosted.org/packages/27/8f/2a93cd9b1e7107d5c7b3b7816eeadcac2ebcaf6d6513df9abaf0334777f6/greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441", size = 658035, upload-time = "2024-09-20T17:44:26.501Z" }, - { url = "https://files.pythonhosted.org/packages/57/5c/7c6f50cb12be092e1dccb2599be5a942c3416dbcfb76efcf54b3f8be4d8d/greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36", size = 660105, upload-time = "2024-09-20T17:08:42.048Z" }, - { url = "https://files.pythonhosted.org/packages/f1/66/033e58a50fd9ec9df00a8671c74f1f3a320564c6415a4ed82a1c651654ba/greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9", size = 613077, upload-time = "2024-09-20T17:08:33.707Z" }, - { url = "https://files.pythonhosted.org/packages/19/c5/36384a06f748044d06bdd8776e231fadf92fc896bd12cb1c9f5a1bda9578/greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0", size = 1135975, upload-time = "2024-09-20T17:44:15.989Z" }, - { url = "https://files.pythonhosted.org/packages/38/f9/c0a0eb61bdf808d23266ecf1d63309f0e1471f284300ce6dac0ae1231881/greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942", size = 1163955, upload-time = "2024-09-20T17:09:25.539Z" }, - { url = "https://files.pythonhosted.org/packages/43/21/a5d9df1d21514883333fc86584c07c2b49ba7c602e670b174bd73cfc9c7f/greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01", size = 299655, upload-time = "2024-09-20T17:21:22.427Z" }, - { url = "https://files.pythonhosted.org/packages/f3/57/0db4940cd7bb461365ca8d6fd53e68254c9dbbcc2b452e69d0d41f10a85e/greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1", size = 272990, upload-time = "2024-09-20T17:08:26.312Z" }, - { url = "https://files.pythonhosted.org/packages/1c/ec/423d113c9f74e5e402e175b157203e9102feeb7088cee844d735b28ef963/greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff", size = 649175, upload-time = "2024-09-20T17:36:48.983Z" }, - { url = "https://files.pythonhosted.org/packages/a9/46/ddbd2db9ff209186b7b7c621d1432e2f21714adc988703dbdd0e65155c77/greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a", size = 663425, upload-time = "2024-09-20T17:39:22.705Z" }, - { url = "https://files.pythonhosted.org/packages/bc/f9/9c82d6b2b04aa37e38e74f0c429aece5eeb02bab6e3b98e7db89b23d94c6/greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e", size = 657736, upload-time = "2024-09-20T17:44:28.544Z" }, - { url = "https://files.pythonhosted.org/packages/d9/42/b87bc2a81e3a62c3de2b0d550bf91a86939442b7ff85abb94eec3fc0e6aa/greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4", size = 660347, upload-time = "2024-09-20T17:08:45.56Z" }, - { url = "https://files.pythonhosted.org/packages/37/fa/71599c3fd06336cdc3eac52e6871cfebab4d9d70674a9a9e7a482c318e99/greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e", size = 615583, upload-time = "2024-09-20T17:08:36.85Z" }, - { url = "https://files.pythonhosted.org/packages/4e/96/e9ef85de031703ee7a4483489b40cf307f93c1824a02e903106f2ea315fe/greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1", size = 1133039, upload-time = "2024-09-20T17:44:18.287Z" }, - { url = "https://files.pythonhosted.org/packages/87/76/b2b6362accd69f2d1889db61a18c94bc743e961e3cab344c2effaa4b4a25/greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c", size = 1160716, upload-time = "2024-09-20T17:09:27.112Z" }, - { url = "https://files.pythonhosted.org/packages/1f/1b/54336d876186920e185066d8c3024ad55f21d7cc3683c856127ddb7b13ce/greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761", size = 299490, upload-time = "2024-09-20T17:17:09.501Z" }, - { url = "https://files.pythonhosted.org/packages/5f/17/bea55bf36990e1638a2af5ba10c1640273ef20f627962cf97107f1e5d637/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011", size = 643731, upload-time = "2024-09-20T17:36:50.376Z" }, - { url = "https://files.pythonhosted.org/packages/78/d2/aa3d2157f9ab742a08e0fd8f77d4699f37c22adfbfeb0c610a186b5f75e0/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13", size = 649304, upload-time = "2024-09-20T17:39:24.55Z" }, - { url = "https://files.pythonhosted.org/packages/f1/8e/d0aeffe69e53ccff5a28fa86f07ad1d2d2d6537a9506229431a2a02e2f15/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475", size = 646537, upload-time = "2024-09-20T17:44:31.102Z" }, - { url = "https://files.pythonhosted.org/packages/05/79/e15408220bbb989469c8871062c97c6c9136770657ba779711b90870d867/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b", size = 642506, upload-time = "2024-09-20T17:08:47.852Z" }, - { url = "https://files.pythonhosted.org/packages/18/87/470e01a940307796f1d25f8167b551a968540fbe0551c0ebb853cb527dd6/greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822", size = 602753, upload-time = "2024-09-20T17:08:38.079Z" }, - { url = "https://files.pythonhosted.org/packages/e2/72/576815ba674eddc3c25028238f74d7b8068902b3968cbe456771b166455e/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01", size = 1122731, upload-time = "2024-09-20T17:44:20.556Z" }, - { url = "https://files.pythonhosted.org/packages/ac/38/08cc303ddddc4b3d7c628c3039a61a3aae36c241ed01393d00c2fd663473/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6", size = 1142112, upload-time = "2024-09-20T17:09:28.753Z" }, + { url = "https://files.pythonhosted.org/packages/25/90/5234a78dc0ef6496a6eb97b67a42a8e96742a56f7dc808cb954a85390448/greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563", size = 271235 }, + { url = "https://files.pythonhosted.org/packages/7c/16/cd631fa0ab7d06ef06387135b7549fdcc77d8d859ed770a0d28e47b20972/greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83", size = 637168 }, + { url = "https://files.pythonhosted.org/packages/2f/b1/aed39043a6fec33c284a2c9abd63ce191f4f1a07319340ffc04d2ed3256f/greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0", size = 648826 }, + { url = "https://files.pythonhosted.org/packages/76/25/40e0112f7f3ebe54e8e8ed91b2b9f970805143efef16d043dfc15e70f44b/greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120", size = 644443 }, + { url = "https://files.pythonhosted.org/packages/fb/2f/3850b867a9af519794784a7eeed1dd5bc68ffbcc5b28cef703711025fd0a/greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc", size = 643295 }, + { url = "https://files.pythonhosted.org/packages/cf/69/79e4d63b9387b48939096e25115b8af7cd8a90397a304f92436bcb21f5b2/greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617", size = 599544 }, + { url = "https://files.pythonhosted.org/packages/46/1d/44dbcb0e6c323bd6f71b8c2f4233766a5faf4b8948873225d34a0b7efa71/greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7", size = 1125456 }, + { url = "https://files.pythonhosted.org/packages/e0/1d/a305dce121838d0278cee39d5bb268c657f10a5363ae4b726848f833f1bb/greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6", size = 1149111 }, + { url = "https://files.pythonhosted.org/packages/96/28/d62835fb33fb5652f2e98d34c44ad1a0feacc8b1d3f1aecab035f51f267d/greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80", size = 298392 }, + { url = "https://files.pythonhosted.org/packages/28/62/1c2665558618553c42922ed47a4e6d6527e2fa3516a8256c2f431c5d0441/greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70", size = 272479 }, + { url = "https://files.pythonhosted.org/packages/76/9d/421e2d5f07285b6e4e3a676b016ca781f63cfe4a0cd8eaecf3fd6f7a71ae/greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159", size = 640404 }, + { url = "https://files.pythonhosted.org/packages/e5/de/6e05f5c59262a584e502dd3d261bbdd2c97ab5416cc9c0b91ea38932a901/greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e", size = 652813 }, + { url = "https://files.pythonhosted.org/packages/49/93/d5f93c84241acdea15a8fd329362c2c71c79e1a507c3f142a5d67ea435ae/greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1", size = 648517 }, + { url = "https://files.pythonhosted.org/packages/15/85/72f77fc02d00470c86a5c982b8daafdf65d38aefbbe441cebff3bf7037fc/greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383", size = 647831 }, + { url = "https://files.pythonhosted.org/packages/f7/4b/1c9695aa24f808e156c8f4813f685d975ca73c000c2a5056c514c64980f6/greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a", size = 602413 }, + { url = "https://files.pythonhosted.org/packages/76/70/ad6e5b31ef330f03b12559d19fda2606a522d3849cde46b24f223d6d1619/greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511", size = 1129619 }, + { url = "https://files.pythonhosted.org/packages/f4/fb/201e1b932e584066e0f0658b538e73c459b34d44b4bd4034f682423bc801/greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395", size = 1155198 }, + { url = "https://files.pythonhosted.org/packages/12/da/b9ed5e310bb8b89661b80cbcd4db5a067903bbcd7fc854923f5ebb4144f0/greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39", size = 298930 }, + { url = "https://files.pythonhosted.org/packages/7d/ec/bad1ac26764d26aa1353216fcbfa4670050f66d445448aafa227f8b16e80/greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d", size = 274260 }, + { url = "https://files.pythonhosted.org/packages/66/d4/c8c04958870f482459ab5956c2942c4ec35cac7fe245527f1039837c17a9/greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79", size = 649064 }, + { url = "https://files.pythonhosted.org/packages/51/41/467b12a8c7c1303d20abcca145db2be4e6cd50a951fa30af48b6ec607581/greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa", size = 663420 }, + { url = "https://files.pythonhosted.org/packages/27/8f/2a93cd9b1e7107d5c7b3b7816eeadcac2ebcaf6d6513df9abaf0334777f6/greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441", size = 658035 }, + { url = "https://files.pythonhosted.org/packages/57/5c/7c6f50cb12be092e1dccb2599be5a942c3416dbcfb76efcf54b3f8be4d8d/greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36", size = 660105 }, + { url = "https://files.pythonhosted.org/packages/f1/66/033e58a50fd9ec9df00a8671c74f1f3a320564c6415a4ed82a1c651654ba/greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9", size = 613077 }, + { url = "https://files.pythonhosted.org/packages/19/c5/36384a06f748044d06bdd8776e231fadf92fc896bd12cb1c9f5a1bda9578/greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0", size = 1135975 }, + { url = "https://files.pythonhosted.org/packages/38/f9/c0a0eb61bdf808d23266ecf1d63309f0e1471f284300ce6dac0ae1231881/greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942", size = 1163955 }, + { url = "https://files.pythonhosted.org/packages/43/21/a5d9df1d21514883333fc86584c07c2b49ba7c602e670b174bd73cfc9c7f/greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01", size = 299655 }, + { url = "https://files.pythonhosted.org/packages/f3/57/0db4940cd7bb461365ca8d6fd53e68254c9dbbcc2b452e69d0d41f10a85e/greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1", size = 272990 }, + { url = "https://files.pythonhosted.org/packages/1c/ec/423d113c9f74e5e402e175b157203e9102feeb7088cee844d735b28ef963/greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff", size = 649175 }, + { url = "https://files.pythonhosted.org/packages/a9/46/ddbd2db9ff209186b7b7c621d1432e2f21714adc988703dbdd0e65155c77/greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a", size = 663425 }, + { url = "https://files.pythonhosted.org/packages/bc/f9/9c82d6b2b04aa37e38e74f0c429aece5eeb02bab6e3b98e7db89b23d94c6/greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e", size = 657736 }, + { url = "https://files.pythonhosted.org/packages/d9/42/b87bc2a81e3a62c3de2b0d550bf91a86939442b7ff85abb94eec3fc0e6aa/greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4", size = 660347 }, + { url = "https://files.pythonhosted.org/packages/37/fa/71599c3fd06336cdc3eac52e6871cfebab4d9d70674a9a9e7a482c318e99/greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e", size = 615583 }, + { url = "https://files.pythonhosted.org/packages/4e/96/e9ef85de031703ee7a4483489b40cf307f93c1824a02e903106f2ea315fe/greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1", size = 1133039 }, + { url = "https://files.pythonhosted.org/packages/87/76/b2b6362accd69f2d1889db61a18c94bc743e961e3cab344c2effaa4b4a25/greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c", size = 1160716 }, + { url = "https://files.pythonhosted.org/packages/1f/1b/54336d876186920e185066d8c3024ad55f21d7cc3683c856127ddb7b13ce/greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761", size = 299490 }, + { url = "https://files.pythonhosted.org/packages/5f/17/bea55bf36990e1638a2af5ba10c1640273ef20f627962cf97107f1e5d637/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011", size = 643731 }, + { url = "https://files.pythonhosted.org/packages/78/d2/aa3d2157f9ab742a08e0fd8f77d4699f37c22adfbfeb0c610a186b5f75e0/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13", size = 649304 }, + { url = "https://files.pythonhosted.org/packages/f1/8e/d0aeffe69e53ccff5a28fa86f07ad1d2d2d6537a9506229431a2a02e2f15/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475", size = 646537 }, + { url = "https://files.pythonhosted.org/packages/05/79/e15408220bbb989469c8871062c97c6c9136770657ba779711b90870d867/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b", size = 642506 }, + { url = "https://files.pythonhosted.org/packages/18/87/470e01a940307796f1d25f8167b551a968540fbe0551c0ebb853cb527dd6/greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822", size = 602753 }, + { url = "https://files.pythonhosted.org/packages/e2/72/576815ba674eddc3c25028238f74d7b8068902b3968cbe456771b166455e/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01", size = 1122731 }, + { url = "https://files.pythonhosted.org/packages/ac/38/08cc303ddddc4b3d7c628c3039a61a3aae36c241ed01393d00c2fd663473/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6", size = 1142112 }, ] [[package]] @@ -942,33 +934,33 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031, upload-time = "2024-08-10T20:25:27.378Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload-time = "2024-08-10T20:25:24.996Z" }, + { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029 }, ] [[package]] name = "h11" version = "0.14.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418, upload-time = "2022-09-25T15:40:01.519Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259, upload-time = "2022-09-25T15:39:59.68Z" }, + { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, ] [[package]] name = "hf-xet" version = "1.1.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/0a/a0f56735940fde6dd627602fec9ab3bad23f66a272397560abd65aba416e/hf_xet-1.1.7.tar.gz", hash = "sha256:20cec8db4561338824a3b5f8c19774055b04a8df7fff0cb1ff2cb1a0c1607b80", size = 477719, upload-time = "2025-08-06T00:30:55.741Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/0a/a0f56735940fde6dd627602fec9ab3bad23f66a272397560abd65aba416e/hf_xet-1.1.7.tar.gz", hash = "sha256:20cec8db4561338824a3b5f8c19774055b04a8df7fff0cb1ff2cb1a0c1607b80", size = 477719 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/7c/8d7803995caf14e7d19a392a486a040f923e2cfeff824e9b800b92072f76/hf_xet-1.1.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:60dae4b44d520819e54e216a2505685248ec0adbdb2dd4848b17aa85a0375cde", size = 2761743, upload-time = "2025-08-06T00:30:50.634Z" }, - { url = "https://files.pythonhosted.org/packages/51/a3/fa5897099454aa287022a34a30e68dbff0e617760f774f8bd1db17f06bd4/hf_xet-1.1.7-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:b109f4c11e01c057fc82004c9e51e6cdfe2cb230637644ade40c599739067b2e", size = 2624331, upload-time = "2025-08-06T00:30:49.212Z" }, - { url = "https://files.pythonhosted.org/packages/86/50/2446a132267e60b8a48b2e5835d6e24fd988000d0f5b9b15ebd6d64ef769/hf_xet-1.1.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6efaaf1a5a9fc3a501d3e71e88a6bfebc69ee3a716d0e713a931c8b8d920038f", size = 3183844, upload-time = "2025-08-06T00:30:47.582Z" }, - { url = "https://files.pythonhosted.org/packages/20/8f/ccc670616bb9beee867c6bb7139f7eab2b1370fe426503c25f5cbb27b148/hf_xet-1.1.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:751571540f9c1fbad9afcf222a5fb96daf2384bf821317b8bfb0c59d86078513", size = 3074209, upload-time = "2025-08-06T00:30:45.509Z" }, - { url = "https://files.pythonhosted.org/packages/21/0a/4c30e1eb77205565b854f5e4a82cf1f056214e4dc87f2918ebf83d47ae14/hf_xet-1.1.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:18b61bbae92d56ae731b92087c44efcac216071182c603fc535f8e29ec4b09b8", size = 3239602, upload-time = "2025-08-06T00:30:52.41Z" }, - { url = "https://files.pythonhosted.org/packages/f5/1e/fc7e9baf14152662ef0b35fa52a6e889f770a7ed14ac239de3c829ecb47e/hf_xet-1.1.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:713f2bff61b252f8523739969f247aa354ad8e6d869b8281e174e2ea1bb8d604", size = 3348184, upload-time = "2025-08-06T00:30:54.105Z" }, - { url = "https://files.pythonhosted.org/packages/a3/73/e354eae84ceff117ec3560141224724794828927fcc013c5b449bf0b8745/hf_xet-1.1.7-cp37-abi3-win_amd64.whl", hash = "sha256:2e356da7d284479ae0f1dea3cf5a2f74fdf925d6dca84ac4341930d892c7cb34", size = 2820008, upload-time = "2025-08-06T00:30:57.056Z" }, + { url = "https://files.pythonhosted.org/packages/b1/7c/8d7803995caf14e7d19a392a486a040f923e2cfeff824e9b800b92072f76/hf_xet-1.1.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:60dae4b44d520819e54e216a2505685248ec0adbdb2dd4848b17aa85a0375cde", size = 2761743 }, + { url = "https://files.pythonhosted.org/packages/51/a3/fa5897099454aa287022a34a30e68dbff0e617760f774f8bd1db17f06bd4/hf_xet-1.1.7-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:b109f4c11e01c057fc82004c9e51e6cdfe2cb230637644ade40c599739067b2e", size = 2624331 }, + { url = "https://files.pythonhosted.org/packages/86/50/2446a132267e60b8a48b2e5835d6e24fd988000d0f5b9b15ebd6d64ef769/hf_xet-1.1.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6efaaf1a5a9fc3a501d3e71e88a6bfebc69ee3a716d0e713a931c8b8d920038f", size = 3183844 }, + { url = "https://files.pythonhosted.org/packages/20/8f/ccc670616bb9beee867c6bb7139f7eab2b1370fe426503c25f5cbb27b148/hf_xet-1.1.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:751571540f9c1fbad9afcf222a5fb96daf2384bf821317b8bfb0c59d86078513", size = 3074209 }, + { url = "https://files.pythonhosted.org/packages/21/0a/4c30e1eb77205565b854f5e4a82cf1f056214e4dc87f2918ebf83d47ae14/hf_xet-1.1.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:18b61bbae92d56ae731b92087c44efcac216071182c603fc535f8e29ec4b09b8", size = 3239602 }, + { url = "https://files.pythonhosted.org/packages/f5/1e/fc7e9baf14152662ef0b35fa52a6e889f770a7ed14ac239de3c829ecb47e/hf_xet-1.1.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:713f2bff61b252f8523739969f247aa354ad8e6d869b8281e174e2ea1bb8d604", size = 3348184 }, + { url = "https://files.pythonhosted.org/packages/a3/73/e354eae84ceff117ec3560141224724794828927fcc013c5b449bf0b8745/hf_xet-1.1.7-cp37-abi3-win_amd64.whl", hash = "sha256:2e356da7d284479ae0f1dea3cf5a2f74fdf925d6dca84ac4341930d892c7cb34", size = 2820008 }, ] [[package]] @@ -979,45 +971,45 @@ dependencies = [ { name = "certifi" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/18/56/78a38490b834fa0942cbe6d39bd8a7fd76316e8940319305a98d2b320366/httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535", size = 81036, upload-time = "2023-11-10T13:37:42.496Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/56/78a38490b834fa0942cbe6d39bd8a7fd76316e8940319305a98d2b320366/httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535", size = 81036 } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/ba/78b0a99c4da0ff8b0f59defa2f13ca4668189b134bd9840b6202a93d9a0f/httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7", size = 76943, upload-time = "2023-11-10T13:37:40.937Z" }, + { url = "https://files.pythonhosted.org/packages/56/ba/78b0a99c4da0ff8b0f59defa2f13ca4668189b134bd9840b6202a93d9a0f/httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7", size = 76943 }, ] [[package]] name = "httptools" version = "0.6.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/9a/ce5e1f7e131522e6d3426e8e7a490b3a01f39a6696602e1c4f33f9e94277/httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", size = 240639, upload-time = "2024-10-16T19:45:08.902Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/9a/ce5e1f7e131522e6d3426e8e7a490b3a01f39a6696602e1c4f33f9e94277/httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", size = 240639 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/6f/972f8eb0ea7d98a1c6be436e2142d51ad2a64ee18e02b0e7ff1f62171ab1/httptools-0.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3c73ce323711a6ffb0d247dcd5a550b8babf0f757e86a52558fe5b86d6fefcc0", size = 198780, upload-time = "2024-10-16T19:44:06.882Z" }, - { url = "https://files.pythonhosted.org/packages/6a/b0/17c672b4bc5c7ba7f201eada4e96c71d0a59fbc185e60e42580093a86f21/httptools-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:345c288418f0944a6fe67be8e6afa9262b18c7626c3ef3c28adc5eabc06a68da", size = 103297, upload-time = "2024-10-16T19:44:08.129Z" }, - { url = "https://files.pythonhosted.org/packages/92/5e/b4a826fe91971a0b68e8c2bd4e7db3e7519882f5a8ccdb1194be2b3ab98f/httptools-0.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deee0e3343f98ee8047e9f4c5bc7cedbf69f5734454a94c38ee829fb2d5fa3c1", size = 443130, upload-time = "2024-10-16T19:44:09.45Z" }, - { url = "https://files.pythonhosted.org/packages/b0/51/ce61e531e40289a681a463e1258fa1e05e0be54540e40d91d065a264cd8f/httptools-0.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca80b7485c76f768a3bc83ea58373f8db7b015551117375e4918e2aa77ea9b50", size = 442148, upload-time = "2024-10-16T19:44:11.539Z" }, - { url = "https://files.pythonhosted.org/packages/ea/9e/270b7d767849b0c96f275c695d27ca76c30671f8eb8cc1bab6ced5c5e1d0/httptools-0.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:90d96a385fa941283ebd231464045187a31ad932ebfa541be8edf5b3c2328959", size = 415949, upload-time = "2024-10-16T19:44:13.388Z" }, - { url = "https://files.pythonhosted.org/packages/81/86/ced96e3179c48c6f656354e106934e65c8963d48b69be78f355797f0e1b3/httptools-0.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:59e724f8b332319e2875efd360e61ac07f33b492889284a3e05e6d13746876f4", size = 417591, upload-time = "2024-10-16T19:44:15.258Z" }, - { url = "https://files.pythonhosted.org/packages/75/73/187a3f620ed3175364ddb56847d7a608a6fc42d551e133197098c0143eca/httptools-0.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:c26f313951f6e26147833fc923f78f95604bbec812a43e5ee37f26dc9e5a686c", size = 88344, upload-time = "2024-10-16T19:44:16.54Z" }, - { url = "https://files.pythonhosted.org/packages/7b/26/bb526d4d14c2774fe07113ca1db7255737ffbb119315839af2065abfdac3/httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069", size = 199029, upload-time = "2024-10-16T19:44:18.427Z" }, - { url = "https://files.pythonhosted.org/packages/a6/17/3e0d3e9b901c732987a45f4f94d4e2c62b89a041d93db89eafb262afd8d5/httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a", size = 103492, upload-time = "2024-10-16T19:44:19.515Z" }, - { url = "https://files.pythonhosted.org/packages/b7/24/0fe235d7b69c42423c7698d086d4db96475f9b50b6ad26a718ef27a0bce6/httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975", size = 462891, upload-time = "2024-10-16T19:44:21.067Z" }, - { url = "https://files.pythonhosted.org/packages/b1/2f/205d1f2a190b72da6ffb5f41a3736c26d6fa7871101212b15e9b5cd8f61d/httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636", size = 459788, upload-time = "2024-10-16T19:44:22.958Z" }, - { url = "https://files.pythonhosted.org/packages/6e/4c/d09ce0eff09057a206a74575ae8f1e1e2f0364d20e2442224f9e6612c8b9/httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721", size = 433214, upload-time = "2024-10-16T19:44:24.513Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d2/84c9e23edbccc4a4c6f96a1b8d99dfd2350289e94f00e9ccc7aadde26fb5/httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988", size = 434120, upload-time = "2024-10-16T19:44:26.295Z" }, - { url = "https://files.pythonhosted.org/packages/d0/46/4d8e7ba9581416de1c425b8264e2cadd201eb709ec1584c381f3e98f51c1/httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17", size = 88565, upload-time = "2024-10-16T19:44:29.188Z" }, - { url = "https://files.pythonhosted.org/packages/bb/0e/d0b71465c66b9185f90a091ab36389a7352985fe857e352801c39d6127c8/httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2", size = 200683, upload-time = "2024-10-16T19:44:30.175Z" }, - { url = "https://files.pythonhosted.org/packages/e2/b8/412a9bb28d0a8988de3296e01efa0bd62068b33856cdda47fe1b5e890954/httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44", size = 104337, upload-time = "2024-10-16T19:44:31.786Z" }, - { url = "https://files.pythonhosted.org/packages/9b/01/6fb20be3196ffdc8eeec4e653bc2a275eca7f36634c86302242c4fbb2760/httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1", size = 508796, upload-time = "2024-10-16T19:44:32.825Z" }, - { url = "https://files.pythonhosted.org/packages/f7/d8/b644c44acc1368938317d76ac991c9bba1166311880bcc0ac297cb9d6bd7/httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2", size = 510837, upload-time = "2024-10-16T19:44:33.974Z" }, - { url = "https://files.pythonhosted.org/packages/52/d8/254d16a31d543073a0e57f1c329ca7378d8924e7e292eda72d0064987486/httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81", size = 485289, upload-time = "2024-10-16T19:44:35.111Z" }, - { url = "https://files.pythonhosted.org/packages/5f/3c/4aee161b4b7a971660b8be71a92c24d6c64372c1ab3ae7f366b3680df20f/httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f", size = 489779, upload-time = "2024-10-16T19:44:36.253Z" }, - { url = "https://files.pythonhosted.org/packages/12/b7/5cae71a8868e555f3f67a50ee7f673ce36eac970f029c0c5e9d584352961/httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970", size = 88634, upload-time = "2024-10-16T19:44:37.357Z" }, - { url = "https://files.pythonhosted.org/packages/94/a3/9fe9ad23fd35f7de6b91eeb60848986058bd8b5a5c1e256f5860a160cc3e/httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660", size = 197214, upload-time = "2024-10-16T19:44:38.738Z" }, - { url = "https://files.pythonhosted.org/packages/ea/d9/82d5e68bab783b632023f2fa31db20bebb4e89dfc4d2293945fd68484ee4/httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083", size = 102431, upload-time = "2024-10-16T19:44:39.818Z" }, - { url = "https://files.pythonhosted.org/packages/96/c1/cb499655cbdbfb57b577734fde02f6fa0bbc3fe9fb4d87b742b512908dff/httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3", size = 473121, upload-time = "2024-10-16T19:44:41.189Z" }, - { url = "https://files.pythonhosted.org/packages/af/71/ee32fd358f8a3bb199b03261f10921716990808a675d8160b5383487a317/httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071", size = 473805, upload-time = "2024-10-16T19:44:42.384Z" }, - { url = "https://files.pythonhosted.org/packages/8a/0a/0d4df132bfca1507114198b766f1737d57580c9ad1cf93c1ff673e3387be/httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5", size = 448858, upload-time = "2024-10-16T19:44:43.959Z" }, - { url = "https://files.pythonhosted.org/packages/1e/6a/787004fdef2cabea27bad1073bf6a33f2437b4dbd3b6fb4a9d71172b1c7c/httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0", size = 452042, upload-time = "2024-10-16T19:44:45.071Z" }, - { url = "https://files.pythonhosted.org/packages/4d/dc/7decab5c404d1d2cdc1bb330b1bf70e83d6af0396fd4fc76fc60c0d522bf/httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8", size = 87682, upload-time = "2024-10-16T19:44:46.46Z" }, + { url = "https://files.pythonhosted.org/packages/3b/6f/972f8eb0ea7d98a1c6be436e2142d51ad2a64ee18e02b0e7ff1f62171ab1/httptools-0.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3c73ce323711a6ffb0d247dcd5a550b8babf0f757e86a52558fe5b86d6fefcc0", size = 198780 }, + { url = "https://files.pythonhosted.org/packages/6a/b0/17c672b4bc5c7ba7f201eada4e96c71d0a59fbc185e60e42580093a86f21/httptools-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:345c288418f0944a6fe67be8e6afa9262b18c7626c3ef3c28adc5eabc06a68da", size = 103297 }, + { url = "https://files.pythonhosted.org/packages/92/5e/b4a826fe91971a0b68e8c2bd4e7db3e7519882f5a8ccdb1194be2b3ab98f/httptools-0.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deee0e3343f98ee8047e9f4c5bc7cedbf69f5734454a94c38ee829fb2d5fa3c1", size = 443130 }, + { url = "https://files.pythonhosted.org/packages/b0/51/ce61e531e40289a681a463e1258fa1e05e0be54540e40d91d065a264cd8f/httptools-0.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca80b7485c76f768a3bc83ea58373f8db7b015551117375e4918e2aa77ea9b50", size = 442148 }, + { url = "https://files.pythonhosted.org/packages/ea/9e/270b7d767849b0c96f275c695d27ca76c30671f8eb8cc1bab6ced5c5e1d0/httptools-0.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:90d96a385fa941283ebd231464045187a31ad932ebfa541be8edf5b3c2328959", size = 415949 }, + { url = "https://files.pythonhosted.org/packages/81/86/ced96e3179c48c6f656354e106934e65c8963d48b69be78f355797f0e1b3/httptools-0.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:59e724f8b332319e2875efd360e61ac07f33b492889284a3e05e6d13746876f4", size = 417591 }, + { url = "https://files.pythonhosted.org/packages/75/73/187a3f620ed3175364ddb56847d7a608a6fc42d551e133197098c0143eca/httptools-0.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:c26f313951f6e26147833fc923f78f95604bbec812a43e5ee37f26dc9e5a686c", size = 88344 }, + { url = "https://files.pythonhosted.org/packages/7b/26/bb526d4d14c2774fe07113ca1db7255737ffbb119315839af2065abfdac3/httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069", size = 199029 }, + { url = "https://files.pythonhosted.org/packages/a6/17/3e0d3e9b901c732987a45f4f94d4e2c62b89a041d93db89eafb262afd8d5/httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a", size = 103492 }, + { url = "https://files.pythonhosted.org/packages/b7/24/0fe235d7b69c42423c7698d086d4db96475f9b50b6ad26a718ef27a0bce6/httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975", size = 462891 }, + { url = "https://files.pythonhosted.org/packages/b1/2f/205d1f2a190b72da6ffb5f41a3736c26d6fa7871101212b15e9b5cd8f61d/httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636", size = 459788 }, + { url = "https://files.pythonhosted.org/packages/6e/4c/d09ce0eff09057a206a74575ae8f1e1e2f0364d20e2442224f9e6612c8b9/httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721", size = 433214 }, + { url = "https://files.pythonhosted.org/packages/3e/d2/84c9e23edbccc4a4c6f96a1b8d99dfd2350289e94f00e9ccc7aadde26fb5/httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988", size = 434120 }, + { url = "https://files.pythonhosted.org/packages/d0/46/4d8e7ba9581416de1c425b8264e2cadd201eb709ec1584c381f3e98f51c1/httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17", size = 88565 }, + { url = "https://files.pythonhosted.org/packages/bb/0e/d0b71465c66b9185f90a091ab36389a7352985fe857e352801c39d6127c8/httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2", size = 200683 }, + { url = "https://files.pythonhosted.org/packages/e2/b8/412a9bb28d0a8988de3296e01efa0bd62068b33856cdda47fe1b5e890954/httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44", size = 104337 }, + { url = "https://files.pythonhosted.org/packages/9b/01/6fb20be3196ffdc8eeec4e653bc2a275eca7f36634c86302242c4fbb2760/httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1", size = 508796 }, + { url = "https://files.pythonhosted.org/packages/f7/d8/b644c44acc1368938317d76ac991c9bba1166311880bcc0ac297cb9d6bd7/httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2", size = 510837 }, + { url = "https://files.pythonhosted.org/packages/52/d8/254d16a31d543073a0e57f1c329ca7378d8924e7e292eda72d0064987486/httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81", size = 485289 }, + { url = "https://files.pythonhosted.org/packages/5f/3c/4aee161b4b7a971660b8be71a92c24d6c64372c1ab3ae7f366b3680df20f/httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f", size = 489779 }, + { url = "https://files.pythonhosted.org/packages/12/b7/5cae71a8868e555f3f67a50ee7f673ce36eac970f029c0c5e9d584352961/httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970", size = 88634 }, + { url = "https://files.pythonhosted.org/packages/94/a3/9fe9ad23fd35f7de6b91eeb60848986058bd8b5a5c1e256f5860a160cc3e/httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660", size = 197214 }, + { url = "https://files.pythonhosted.org/packages/ea/d9/82d5e68bab783b632023f2fa31db20bebb4e89dfc4d2293945fd68484ee4/httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083", size = 102431 }, + { url = "https://files.pythonhosted.org/packages/96/c1/cb499655cbdbfb57b577734fde02f6fa0bbc3fe9fb4d87b742b512908dff/httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3", size = 473121 }, + { url = "https://files.pythonhosted.org/packages/af/71/ee32fd358f8a3bb199b03261f10921716990808a675d8160b5383487a317/httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071", size = 473805 }, + { url = "https://files.pythonhosted.org/packages/8a/0a/0d4df132bfca1507114198b766f1737d57580c9ad1cf93c1ff673e3387be/httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5", size = 448858 }, + { url = "https://files.pythonhosted.org/packages/1e/6a/787004fdef2cabea27bad1073bf6a33f2437b4dbd3b6fb4a9d71172b1c7c/httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0", size = 452042 }, + { url = "https://files.pythonhosted.org/packages/4d/dc/7decab5c404d1d2cdc1bb330b1bf70e83d6af0396fd4fc76fc60c0d522bf/httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8", size = 87682 }, ] [[package]] @@ -1030,9 +1022,9 @@ dependencies = [ { name = "httpcore" }, { name = "idna" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, ] [[package]] @@ -1049,9 +1041,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/45/c9/bdbe19339f76d12985bc03572f330a01a93c04dffecaaea3061bdd7fb892/huggingface_hub-0.34.4.tar.gz", hash = "sha256:a4228daa6fb001be3f4f4bdaf9a0db00e1739235702848df00885c9b5742c85c", size = 459768, upload-time = "2025-08-08T09:14:52.365Z" } +sdist = { url = "https://files.pythonhosted.org/packages/45/c9/bdbe19339f76d12985bc03572f330a01a93c04dffecaaea3061bdd7fb892/huggingface_hub-0.34.4.tar.gz", hash = "sha256:a4228daa6fb001be3f4f4bdaf9a0db00e1739235702848df00885c9b5742c85c", size = 459768 } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/7b/bb06b061991107cd8783f300adff3e7b7f284e330fd82f507f2a1417b11d/huggingface_hub-0.34.4-py3-none-any.whl", hash = "sha256:9b365d781739c93ff90c359844221beef048403f1bc1f1c123c191257c3c890a", size = 561452, upload-time = "2025-08-08T09:14:50.159Z" }, + { url = "https://files.pythonhosted.org/packages/39/7b/bb06b061991107cd8783f300adff3e7b7f284e330fd82f507f2a1417b11d/huggingface_hub-0.34.4-py3-none-any.whl", hash = "sha256:9b365d781739c93ff90c359844221beef048403f1bc1f1c123c191257c3c890a", size = 561452 }, ] [[package]] @@ -1061,18 +1053,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyreadline3", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702, upload-time = "2021-09-17T21:40:43.31Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794, upload-time = "2021-09-17T21:40:39.897Z" }, + { url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794 }, ] [[package]] name = "idna" version = "3.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426, upload-time = "2023-11-25T15:40:54.902Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567, upload-time = "2023-11-25T15:40:52.604Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567 }, ] [[package]] @@ -1083,14 +1075,14 @@ dependencies = [ { name = "numpy" }, { name = "pillow" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/25/38/f4c568318c656352d211eec6954460dc3af0b7583a6682308f8a66e4c19b/imageio-2.33.1.tar.gz", hash = "sha256:78722d40b137bd98f5ec7312119f8aea9ad2049f76f434748eb306b6937cc1ce", size = 387374, upload-time = "2023-12-11T02:26:44.715Z" } +sdist = { url = "https://files.pythonhosted.org/packages/25/38/f4c568318c656352d211eec6954460dc3af0b7583a6682308f8a66e4c19b/imageio-2.33.1.tar.gz", hash = "sha256:78722d40b137bd98f5ec7312119f8aea9ad2049f76f434748eb306b6937cc1ce", size = 387374 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/69/3aaa69cb0748e33e644fda114c9abd3186ce369edd4fca11107e9f39c6a7/imageio-2.33.1-py3-none-any.whl", hash = "sha256:c5094c48ccf6b2e6da8b4061cd95e1209380afafcbeae4a4e280938cce227e1d", size = 313345, upload-time = "2023-12-11T02:26:42.724Z" }, + { url = "https://files.pythonhosted.org/packages/c0/69/3aaa69cb0748e33e644fda114c9abd3186ce369edd4fca11107e9f39c6a7/imageio-2.33.1-py3-none-any.whl", hash = "sha256:c5094c48ccf6b2e6da8b4061cd95e1209380afafcbeae4a4e280938cce227e1d", size = 313345 }, ] [[package]] name = "immich-ml" -version = "1.129.0" +version = "2.0.1" source = { editable = "." } dependencies = [ { name = "aiocache" }, @@ -1106,6 +1098,7 @@ dependencies = [ { name = "pydantic" }, { name = "pydantic-settings" }, { name = "python-multipart" }, + { name = "rapidocr" }, { name = "rich" }, { name = "tokenizers" }, { name = "uvicorn", extra = ["standard"] }, @@ -1191,6 +1184,7 @@ requires-dist = [ { name = "pydantic", specifier = ">=2.0.0,<3" }, { name = "pydantic-settings", specifier = ">=2.5.2,<3" }, { name = "python-multipart", specifier = ">=0.0.6,<1.0" }, + { name = "rapidocr", specifier = ">=3.1.0" }, { name = "rich", specifier = ">=13.4.2" }, { name = "rknn-toolkit-lite2", marker = "extra == 'rknn'", specifier = ">=2.3.0,<3" }, { name = "tokenizers", specifier = ">=0.15.0,<1.0" }, @@ -1244,9 +1238,9 @@ types = [ name = "iniconfig" version = "2.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646, upload-time = "2023-01-07T11:08:11.254Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892, upload-time = "2023-01-07T11:08:09.864Z" }, + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, ] [[package]] @@ -1271,15 +1265,15 @@ dependencies = [ { name = "scipy", version = "1.16.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0b/8d/0f4af90999ca96cf8cb846eb5ae27c5ef5b390f9c090dd19e4fa76364c13/insightface-0.7.3.tar.gz", hash = "sha256:f191f719612ebb37018f41936814500544cd0f86e6fcd676c023f354c668ddf7", size = 439490, upload-time = "2023-04-02T08:01:54.541Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/8d/0f4af90999ca96cf8cb846eb5ae27c5ef5b390f9c090dd19e4fa76364c13/insightface-0.7.3.tar.gz", hash = "sha256:f191f719612ebb37018f41936814500544cd0f86e6fcd676c023f354c668ddf7", size = 439490 } [[package]] name = "itsdangerous" version = "2.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7f/a1/d3fb83e7a61fa0c0d3d08ad0a94ddbeff3731c05212617dff3a94e097f08/itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a", size = 56143, upload-time = "2022-03-24T15:12:15.102Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7f/a1/d3fb83e7a61fa0c0d3d08ad0a94ddbeff3731c05212617dff3a94e097f08/itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a", size = 56143 } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/5f/447e04e828f47465eeab35b5d408b7ebaaaee207f48b7136c5a7267a30ae/itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", size = 15749, upload-time = "2022-03-24T15:12:13.2Z" }, + { url = "https://files.pythonhosted.org/packages/68/5f/447e04e828f47465eeab35b5d408b7ebaaaee207f48b7136c5a7267a30ae/itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", size = 15749 }, ] [[package]] @@ -1289,85 +1283,85 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ed/55/39036716d19cab0747a5020fc7e907f362fbf48c984b14e62127f7e68e5d/jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", size = 240245, upload-time = "2024-05-05T23:42:02.455Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/55/39036716d19cab0747a5020fc7e907f362fbf48c984b14e62127f7e68e5d/jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", size = 240245 } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271, upload-time = "2024-05-05T23:41:59.928Z" }, + { url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271 }, ] [[package]] name = "joblib" version = "1.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/0f/d3b33b9f106dddef461f6df1872b7881321b247f3d255b87f61a7636f7fe/joblib-1.3.2.tar.gz", hash = "sha256:92f865e621e17784e7955080b6d042489e3b8e294949cc44c6eac304f59772b1", size = 1987720, upload-time = "2023-08-09T09:23:40.503Z" } +sdist = { url = "https://files.pythonhosted.org/packages/15/0f/d3b33b9f106dddef461f6df1872b7881321b247f3d255b87f61a7636f7fe/joblib-1.3.2.tar.gz", hash = "sha256:92f865e621e17784e7955080b6d042489e3b8e294949cc44c6eac304f59772b1", size = 1987720 } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/40/d551139c85db202f1f384ba8bcf96aca2f329440a844f924c8a0040b6d02/joblib-1.3.2-py3-none-any.whl", hash = "sha256:ef4331c65f239985f3f2220ecc87db222f08fd22097a3dd5698f693875f8cbb9", size = 302207, upload-time = "2023-08-09T09:23:34.583Z" }, + { url = "https://files.pythonhosted.org/packages/10/40/d551139c85db202f1f384ba8bcf96aca2f329440a844f924c8a0040b6d02/joblib-1.3.2-py3-none-any.whl", hash = "sha256:ef4331c65f239985f3f2220ecc87db222f08fd22097a3dd5698f693875f8cbb9", size = 302207 }, ] [[package]] name = "kiwisolver" version = "1.4.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b9/2d/226779e405724344fc678fcc025b812587617ea1a48b9442628b688e85ea/kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec", size = 97552, upload-time = "2023-08-24T09:30:39.861Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/2d/226779e405724344fc678fcc025b812587617ea1a48b9442628b688e85ea/kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec", size = 97552 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/56/cb02dcefdaab40df636b91e703b172966b444605a0ea313549f3ffc05bd3/kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af", size = 127397, upload-time = "2023-08-24T09:28:18.105Z" }, - { url = "https://files.pythonhosted.org/packages/0e/c1/d084f8edb26533a191415d5173157080837341f9a06af9dd1a75f727abb4/kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3", size = 68125, upload-time = "2023-08-24T09:28:19.218Z" }, - { url = "https://files.pythonhosted.org/packages/23/11/6fb190bae4b279d712a834e7b1da89f6dcff6791132f7399aa28a57c3565/kiwisolver-1.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4", size = 66211, upload-time = "2023-08-24T09:28:20.241Z" }, - { url = "https://files.pythonhosted.org/packages/b3/13/5e9e52feb33e9e063f76b2c5eb09cb977f5bba622df3210081bfb26ec9a3/kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1", size = 1637145, upload-time = "2023-08-24T09:28:21.439Z" }, - { url = "https://files.pythonhosted.org/packages/6f/40/4ab1fdb57fced80ce5903f04ae1aed7c1d5939dda4fd0c0aa526c12fe28a/kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff", size = 1617849, upload-time = "2023-08-24T09:28:23.004Z" }, - { url = "https://files.pythonhosted.org/packages/49/ca/61ef43bd0832c7253b370735b0c38972c140c8774889b884372a629a8189/kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a", size = 1400921, upload-time = "2023-08-24T09:28:24.331Z" }, - { url = "https://files.pythonhosted.org/packages/68/6f/854f6a845c00b4257482468e08d8bc386f4929ee499206142378ba234419/kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa", size = 1513009, upload-time = "2023-08-24T09:28:25.636Z" }, - { url = "https://files.pythonhosted.org/packages/50/65/76f303377167d12eb7a9b423d6771b39fe5c4373e4a42f075805b1f581ae/kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c", size = 1444819, upload-time = "2023-08-24T09:28:27.547Z" }, - { url = "https://files.pythonhosted.org/packages/7e/ee/98cdf9dde129551467138b6e18cc1cc901e75ecc7ffb898c6f49609f33b1/kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b", size = 1817054, upload-time = "2023-08-24T09:28:28.839Z" }, - { url = "https://files.pythonhosted.org/packages/e6/5b/ab569016ec4abc7b496f6cb8a3ab511372c99feb6a23d948cda97e0db6da/kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770", size = 1918613, upload-time = "2023-08-24T09:28:30.351Z" }, - { url = "https://files.pythonhosted.org/packages/93/ac/39b9f99d2474b1ac7af1ddfe5756ddf9b6a8f24c5f3a32cd4c010317fc6b/kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0", size = 1872650, upload-time = "2023-08-24T09:28:32.303Z" }, - { url = "https://files.pythonhosted.org/packages/40/5b/be568548266516b114d1776120281ea9236c732fb6032a1f8f3b1e5e921c/kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525", size = 1827415, upload-time = "2023-08-24T09:28:34.141Z" }, - { url = "https://files.pythonhosted.org/packages/d4/80/c0c13d2a17a12937a19ef378bf35e94399fd171ed6ec05bcee0f038e1eaf/kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b", size = 1838094, upload-time = "2023-08-24T09:28:35.97Z" }, - { url = "https://files.pythonhosted.org/packages/70/d1/5ab93ee00ca5af708929cc12fbe665b6f1ed4ad58088e70dc00e87e0d107/kiwisolver-1.4.5-cp310-cp310-win32.whl", hash = "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238", size = 46585, upload-time = "2023-08-24T09:28:37.326Z" }, - { url = "https://files.pythonhosted.org/packages/4a/a1/8a9c9be45c642fa12954855d8b3a02d9fd8551165a558835a19508fec2e6/kiwisolver-1.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276", size = 56095, upload-time = "2023-08-24T09:28:38.325Z" }, - { url = "https://files.pythonhosted.org/packages/2a/eb/9e099ad7c47c279995d2d20474e1821100a5f10f847739bd65b1c1f02442/kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5", size = 127403, upload-time = "2023-08-24T09:28:39.3Z" }, - { url = "https://files.pythonhosted.org/packages/a6/94/695922e71288855fc7cace3bdb52edda9d7e50edba77abb0c9d7abb51e96/kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90", size = 68156, upload-time = "2023-08-24T09:28:40.301Z" }, - { url = "https://files.pythonhosted.org/packages/4a/fe/23d7fa78f7c66086d196406beb1fb2eaf629dd7adc01c3453033303d17fa/kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797", size = 66166, upload-time = "2023-08-24T09:28:41.235Z" }, - { url = "https://files.pythonhosted.org/packages/f1/68/f472bf16c9141bb1bea5c0b8c66c68fc1ccb048efdbd8f0872b92125724e/kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9", size = 1334300, upload-time = "2023-08-24T09:28:42.409Z" }, - { url = "https://files.pythonhosted.org/packages/8d/26/b4569d1f29751fca22ee915b4ebfef5974f4ef239b3335fc072882bd62d9/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437", size = 1426579, upload-time = "2023-08-24T09:28:43.677Z" }, - { url = "https://files.pythonhosted.org/packages/f3/a3/804fc7c8bf233806ec0321c9da35971578620f2ab4fafe67d76100b3ce52/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9", size = 1541360, upload-time = "2023-08-24T09:28:45.939Z" }, - { url = "https://files.pythonhosted.org/packages/07/ef/286e1d26524854f6fbd6540e8364d67a8857d61038ac743e11edc42fe217/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da", size = 1470091, upload-time = "2023-08-24T09:28:47.959Z" }, - { url = "https://files.pythonhosted.org/packages/17/ba/17a706b232308e65f57deeccae503c268292e6a091313f6ce833a23093ea/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e", size = 1426259, upload-time = "2023-08-24T09:28:49.224Z" }, - { url = "https://files.pythonhosted.org/packages/d0/f3/a0925611c9d6c2f37c5935a39203cadec6883aa914e013b46c84c4c2e641/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8", size = 1847516, upload-time = "2023-08-24T09:28:50.979Z" }, - { url = "https://files.pythonhosted.org/packages/da/85/82d59bb8f7c4c9bb2785138b72462cb1b161668f8230c58bbb28c0403cd5/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d", size = 1946228, upload-time = "2023-08-24T09:28:52.812Z" }, - { url = "https://files.pythonhosted.org/packages/34/3c/6a37f444c0233993881e5db3a6a1775925d4d9d2f2609bb325bb1348ed94/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0", size = 1901716, upload-time = "2023-08-24T09:28:54.115Z" }, - { url = "https://files.pythonhosted.org/packages/cd/7e/180425790efc00adfd47db14e1e341cb4826516982334129012b971121a6/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f", size = 1852871, upload-time = "2023-08-24T09:28:55.433Z" }, - { url = "https://files.pythonhosted.org/packages/1b/9a/13c68b2edb1fa74321e60893a9a5829788e135138e68060cf44e2d92d2c3/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f", size = 1870265, upload-time = "2023-08-24T09:28:56.855Z" }, - { url = "https://files.pythonhosted.org/packages/9f/0a/fa56a0fdee5da2b4c79899c0f6bd1aefb29d9438c2d66430e78793571c6b/kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac", size = 46649, upload-time = "2023-08-24T09:28:58.021Z" }, - { url = "https://files.pythonhosted.org/packages/1e/37/d3c2d4ba2719059a0f12730947bbe1ad5ee8bff89e8c35319dcb2c9ddb4c/kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355", size = 56116, upload-time = "2023-08-24T09:28:58.994Z" }, - { url = "https://files.pythonhosted.org/packages/f3/7a/debbce859be1a2711eb8437818107137192007b88d17b5cfdb556f457b42/kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a", size = 125484, upload-time = "2023-08-24T09:28:59.975Z" }, - { url = "https://files.pythonhosted.org/packages/2d/e0/bf8df75ba93b9e035cc6757dd5dcaf63084fdc1c846ae134e818bd7e0f03/kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192", size = 67332, upload-time = "2023-08-24T09:29:01.733Z" }, - { url = "https://files.pythonhosted.org/packages/26/61/58bb691f6880588be3a4801d199bd776032ece07203faf3e4a8b377f7d9b/kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45", size = 64987, upload-time = "2023-08-24T09:29:02.789Z" }, - { url = "https://files.pythonhosted.org/packages/8e/a3/96ac5413068b237c006f54dd8d70114e8756d70e3da7613c5aef20627e22/kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7", size = 1370613, upload-time = "2023-08-24T09:29:03.912Z" }, - { url = "https://files.pythonhosted.org/packages/4d/12/f48539e6e17068b59c7f12f4d6214b973431b8e3ac83af525cafd27cebec/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db", size = 1463183, upload-time = "2023-08-24T09:29:05.244Z" }, - { url = "https://files.pythonhosted.org/packages/f3/70/26c99be8eb034cc8e3f62e0760af1fbdc97a842a7cbc252f7978507d41c2/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff", size = 1581248, upload-time = "2023-08-24T09:29:06.531Z" }, - { url = "https://files.pythonhosted.org/packages/17/f6/f75f20e543639b09b2de7fc864274a5a9b96cda167a6210a1d9d19306b9d/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228", size = 1508815, upload-time = "2023-08-24T09:29:07.867Z" }, - { url = "https://files.pythonhosted.org/packages/e3/d5/bc0f22ac108743062ab703f8d6d71c9c7b077b8839fa358700bfb81770b8/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16", size = 1466042, upload-time = "2023-08-24T09:29:09.403Z" }, - { url = "https://files.pythonhosted.org/packages/75/18/98142500f21d6838bcab49ec919414a1f0c6d049d21ddadf139124db6a70/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9", size = 1885159, upload-time = "2023-08-24T09:29:10.66Z" }, - { url = "https://files.pythonhosted.org/packages/21/49/a241eff9e0ee013368c1d17957f9d345b0957493c3a43d82ebb558c90b0a/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162", size = 1981694, upload-time = "2023-08-24T09:29:12.469Z" }, - { url = "https://files.pythonhosted.org/packages/90/90/9490c3de4788123041b1d600d64434f1eed809a2ce9f688075a22166b289/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4", size = 1941579, upload-time = "2023-08-24T09:29:13.743Z" }, - { url = "https://files.pythonhosted.org/packages/b7/bb/a0cc488ef2aa92d7d304318c8549d3ec8dfe6dd3c2c67a44e3922b77bc4f/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3", size = 1888168, upload-time = "2023-08-24T09:29:15.097Z" }, - { url = "https://files.pythonhosted.org/packages/4f/e9/9c0de8e45fef3d63f85eed3b1757f9aa511065942866331ef8b99421f433/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a", size = 1908464, upload-time = "2023-08-24T09:29:16.539Z" }, - { url = "https://files.pythonhosted.org/packages/a3/60/4f0fd50b08f5be536ea0cef518ac7255d9dab43ca40f3b93b60e3ddf80dd/kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20", size = 46473, upload-time = "2023-08-24T09:29:17.956Z" }, - { url = "https://files.pythonhosted.org/packages/63/50/2746566bdf4a6a842d117367d05c90cfb87ac04e9e2845aa1fa21f071362/kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9", size = 56004, upload-time = "2023-08-24T09:29:19.329Z" }, + { url = "https://files.pythonhosted.org/packages/f1/56/cb02dcefdaab40df636b91e703b172966b444605a0ea313549f3ffc05bd3/kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af", size = 127397 }, + { url = "https://files.pythonhosted.org/packages/0e/c1/d084f8edb26533a191415d5173157080837341f9a06af9dd1a75f727abb4/kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3", size = 68125 }, + { url = "https://files.pythonhosted.org/packages/23/11/6fb190bae4b279d712a834e7b1da89f6dcff6791132f7399aa28a57c3565/kiwisolver-1.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4", size = 66211 }, + { url = "https://files.pythonhosted.org/packages/b3/13/5e9e52feb33e9e063f76b2c5eb09cb977f5bba622df3210081bfb26ec9a3/kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1", size = 1637145 }, + { url = "https://files.pythonhosted.org/packages/6f/40/4ab1fdb57fced80ce5903f04ae1aed7c1d5939dda4fd0c0aa526c12fe28a/kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff", size = 1617849 }, + { url = "https://files.pythonhosted.org/packages/49/ca/61ef43bd0832c7253b370735b0c38972c140c8774889b884372a629a8189/kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a", size = 1400921 }, + { url = "https://files.pythonhosted.org/packages/68/6f/854f6a845c00b4257482468e08d8bc386f4929ee499206142378ba234419/kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa", size = 1513009 }, + { url = "https://files.pythonhosted.org/packages/50/65/76f303377167d12eb7a9b423d6771b39fe5c4373e4a42f075805b1f581ae/kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c", size = 1444819 }, + { url = "https://files.pythonhosted.org/packages/7e/ee/98cdf9dde129551467138b6e18cc1cc901e75ecc7ffb898c6f49609f33b1/kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b", size = 1817054 }, + { url = "https://files.pythonhosted.org/packages/e6/5b/ab569016ec4abc7b496f6cb8a3ab511372c99feb6a23d948cda97e0db6da/kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770", size = 1918613 }, + { url = "https://files.pythonhosted.org/packages/93/ac/39b9f99d2474b1ac7af1ddfe5756ddf9b6a8f24c5f3a32cd4c010317fc6b/kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0", size = 1872650 }, + { url = "https://files.pythonhosted.org/packages/40/5b/be568548266516b114d1776120281ea9236c732fb6032a1f8f3b1e5e921c/kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525", size = 1827415 }, + { url = "https://files.pythonhosted.org/packages/d4/80/c0c13d2a17a12937a19ef378bf35e94399fd171ed6ec05bcee0f038e1eaf/kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b", size = 1838094 }, + { url = "https://files.pythonhosted.org/packages/70/d1/5ab93ee00ca5af708929cc12fbe665b6f1ed4ad58088e70dc00e87e0d107/kiwisolver-1.4.5-cp310-cp310-win32.whl", hash = "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238", size = 46585 }, + { url = "https://files.pythonhosted.org/packages/4a/a1/8a9c9be45c642fa12954855d8b3a02d9fd8551165a558835a19508fec2e6/kiwisolver-1.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276", size = 56095 }, + { url = "https://files.pythonhosted.org/packages/2a/eb/9e099ad7c47c279995d2d20474e1821100a5f10f847739bd65b1c1f02442/kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5", size = 127403 }, + { url = "https://files.pythonhosted.org/packages/a6/94/695922e71288855fc7cace3bdb52edda9d7e50edba77abb0c9d7abb51e96/kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90", size = 68156 }, + { url = "https://files.pythonhosted.org/packages/4a/fe/23d7fa78f7c66086d196406beb1fb2eaf629dd7adc01c3453033303d17fa/kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797", size = 66166 }, + { url = "https://files.pythonhosted.org/packages/f1/68/f472bf16c9141bb1bea5c0b8c66c68fc1ccb048efdbd8f0872b92125724e/kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9", size = 1334300 }, + { url = "https://files.pythonhosted.org/packages/8d/26/b4569d1f29751fca22ee915b4ebfef5974f4ef239b3335fc072882bd62d9/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437", size = 1426579 }, + { url = "https://files.pythonhosted.org/packages/f3/a3/804fc7c8bf233806ec0321c9da35971578620f2ab4fafe67d76100b3ce52/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9", size = 1541360 }, + { url = "https://files.pythonhosted.org/packages/07/ef/286e1d26524854f6fbd6540e8364d67a8857d61038ac743e11edc42fe217/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da", size = 1470091 }, + { url = "https://files.pythonhosted.org/packages/17/ba/17a706b232308e65f57deeccae503c268292e6a091313f6ce833a23093ea/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e", size = 1426259 }, + { url = "https://files.pythonhosted.org/packages/d0/f3/a0925611c9d6c2f37c5935a39203cadec6883aa914e013b46c84c4c2e641/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8", size = 1847516 }, + { url = "https://files.pythonhosted.org/packages/da/85/82d59bb8f7c4c9bb2785138b72462cb1b161668f8230c58bbb28c0403cd5/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d", size = 1946228 }, + { url = "https://files.pythonhosted.org/packages/34/3c/6a37f444c0233993881e5db3a6a1775925d4d9d2f2609bb325bb1348ed94/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0", size = 1901716 }, + { url = "https://files.pythonhosted.org/packages/cd/7e/180425790efc00adfd47db14e1e341cb4826516982334129012b971121a6/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f", size = 1852871 }, + { url = "https://files.pythonhosted.org/packages/1b/9a/13c68b2edb1fa74321e60893a9a5829788e135138e68060cf44e2d92d2c3/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f", size = 1870265 }, + { url = "https://files.pythonhosted.org/packages/9f/0a/fa56a0fdee5da2b4c79899c0f6bd1aefb29d9438c2d66430e78793571c6b/kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac", size = 46649 }, + { url = "https://files.pythonhosted.org/packages/1e/37/d3c2d4ba2719059a0f12730947bbe1ad5ee8bff89e8c35319dcb2c9ddb4c/kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355", size = 56116 }, + { url = "https://files.pythonhosted.org/packages/f3/7a/debbce859be1a2711eb8437818107137192007b88d17b5cfdb556f457b42/kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a", size = 125484 }, + { url = "https://files.pythonhosted.org/packages/2d/e0/bf8df75ba93b9e035cc6757dd5dcaf63084fdc1c846ae134e818bd7e0f03/kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192", size = 67332 }, + { url = "https://files.pythonhosted.org/packages/26/61/58bb691f6880588be3a4801d199bd776032ece07203faf3e4a8b377f7d9b/kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45", size = 64987 }, + { url = "https://files.pythonhosted.org/packages/8e/a3/96ac5413068b237c006f54dd8d70114e8756d70e3da7613c5aef20627e22/kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7", size = 1370613 }, + { url = "https://files.pythonhosted.org/packages/4d/12/f48539e6e17068b59c7f12f4d6214b973431b8e3ac83af525cafd27cebec/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db", size = 1463183 }, + { url = "https://files.pythonhosted.org/packages/f3/70/26c99be8eb034cc8e3f62e0760af1fbdc97a842a7cbc252f7978507d41c2/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff", size = 1581248 }, + { url = "https://files.pythonhosted.org/packages/17/f6/f75f20e543639b09b2de7fc864274a5a9b96cda167a6210a1d9d19306b9d/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228", size = 1508815 }, + { url = "https://files.pythonhosted.org/packages/e3/d5/bc0f22ac108743062ab703f8d6d71c9c7b077b8839fa358700bfb81770b8/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16", size = 1466042 }, + { url = "https://files.pythonhosted.org/packages/75/18/98142500f21d6838bcab49ec919414a1f0c6d049d21ddadf139124db6a70/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9", size = 1885159 }, + { url = "https://files.pythonhosted.org/packages/21/49/a241eff9e0ee013368c1d17957f9d345b0957493c3a43d82ebb558c90b0a/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162", size = 1981694 }, + { url = "https://files.pythonhosted.org/packages/90/90/9490c3de4788123041b1d600d64434f1eed809a2ce9f688075a22166b289/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4", size = 1941579 }, + { url = "https://files.pythonhosted.org/packages/b7/bb/a0cc488ef2aa92d7d304318c8549d3ec8dfe6dd3c2c67a44e3922b77bc4f/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3", size = 1888168 }, + { url = "https://files.pythonhosted.org/packages/4f/e9/9c0de8e45fef3d63f85eed3b1757f9aa511065942866331ef8b99421f433/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a", size = 1908464 }, + { url = "https://files.pythonhosted.org/packages/a3/60/4f0fd50b08f5be536ea0cef518ac7255d9dab43ca40f3b93b60e3ddf80dd/kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20", size = 46473 }, + { url = "https://files.pythonhosted.org/packages/63/50/2746566bdf4a6a842d117367d05c90cfb87ac04e9e2845aa1fa21f071362/kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9", size = 56004 }, ] [[package]] name = "lazy-loader" version = "0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0e/3a/1630a735bfdf9eb857a3b9a53317a1e1658ea97a1b4b39dcb0f71dae81f8/lazy_loader-0.3.tar.gz", hash = "sha256:3b68898e34f5b2a29daaaac172c6555512d0f32074f147e2254e4a6d9d838f37", size = 12268, upload-time = "2023-06-30T21:12:55.362Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/3a/1630a735bfdf9eb857a3b9a53317a1e1658ea97a1b4b39dcb0f71dae81f8/lazy_loader-0.3.tar.gz", hash = "sha256:3b68898e34f5b2a29daaaac172c6555512d0f32074f147e2254e4a6d9d838f37", size = 12268 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/c3/65b3814e155836acacf720e5be3b5757130346670ac454fee29d3eda1381/lazy_loader-0.3-py3-none-any.whl", hash = "sha256:1e9e76ee8631e264c62ce10006718e80b2cfc74340d17d1031e0f84af7478554", size = 9087, upload-time = "2023-06-30T21:12:51.09Z" }, + { url = "https://files.pythonhosted.org/packages/a1/c3/65b3814e155836acacf720e5be3b5757130346670ac454fee29d3eda1381/lazy_loader-0.3-py3-none-any.whl", hash = "sha256:1e9e76ee8631e264c62ce10006718e80b2cfc74340d17d1031e0f84af7478554", size = 9087 }, ] [[package]] name = "locust" -version = "2.40.2" +version = "2.39.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "configargparse" }, @@ -1379,7 +1373,6 @@ dependencies = [ { name = "locust-cloud" }, { name = "msgpack" }, { name = "psutil" }, - { name = "pytest" }, { name = "python-engineio" }, { name = "python-socketio", extra = ["client"] }, { name = "pywin32", marker = "sys_platform == 'win32'" }, @@ -1387,12 +1380,12 @@ dependencies = [ { name = "requests" }, { name = "setuptools" }, { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version < '3.12'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, { name = "werkzeug" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/27/e0/a99401e233ad1b9ad26265ad8f45f2466abb6ef954e7747e8484864eb6df/locust-2.40.2.tar.gz", hash = "sha256:9ffdf900d1ad949d4c5809e2a4e526bba582175f025f24da2755f43f4b5cb23e", size = 1411854, upload-time = "2025-09-08T12:55:28.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/95/c8/10aa5445c404eed389b56877e6714c1787190cc09dd70059ce3765979ec5/locust-2.39.1.tar.gz", hash = "sha256:6bdd19e27edf9a1c84391d6cf6e9a737dfb832be7dfbf39053191ae31b9cc498", size = 1409902 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/e7/85ddb125d91b3a2bfa2a52eeae2d4c7da062239aaa475d6aebddb5688f41/locust-2.40.2-py3-none-any.whl", hash = "sha256:c8f0060d2bd8479034e9e61e6473669c4c8216930d99ee61ec0e627340b89d3e", size = 1430483, upload-time = "2025-09-08T12:55:25.659Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b3/b2f4b2ca88b1e72eba7be2b2982533b887f8b709d222db78eb9602aa5121/locust-2.39.1-py3-none-any.whl", hash = "sha256:fd5148f2f1a4ed34aee968abc4393674e69d1b5e1b54db50a397f6eb09ce0b04", size = 1428155 }, ] [[package]] @@ -1407,9 +1400,9 @@ dependencies = [ { name = "python-socketio", extra = ["client"] }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/84/ad/10b299b134068a4250a9156e6832a717406abe1dfea2482a07ae7bdca8f3/locust_cloud-1.26.3.tar.gz", hash = "sha256:587acfd4d2dee715fb5f0c3c2d922770babf0b7cff7b2927afbb693a9cd193cc", size = 456042, upload-time = "2025-07-15T19:51:53.791Z" } +sdist = { url = "https://files.pythonhosted.org/packages/84/ad/10b299b134068a4250a9156e6832a717406abe1dfea2482a07ae7bdca8f3/locust_cloud-1.26.3.tar.gz", hash = "sha256:587acfd4d2dee715fb5f0c3c2d922770babf0b7cff7b2927afbb693a9cd193cc", size = 456042 } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/6a/276fc50a9d170e7cbb6715735480cb037abb526639bca85491576e6eee4a/locust_cloud-1.26.3-py3-none-any.whl", hash = "sha256:8cb4b8bb9adcd5b99327bc8ed1d98cf67a29d9d29512651e6e94869de6f1faa8", size = 410023, upload-time = "2025-07-15T19:51:52.056Z" }, + { url = "https://files.pythonhosted.org/packages/50/6a/276fc50a9d170e7cbb6715735480cb037abb526639bca85491576e6eee4a/locust_cloud-1.26.3-py3-none-any.whl", hash = "sha256:8cb4b8bb9adcd5b99327bc8ed1d98cf67a29d9d29512651e6e94869de6f1faa8", size = 410023 }, ] [[package]] @@ -1419,47 +1412,47 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, ] [[package]] name = "markupsafe" version = "2.1.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6d/7c/59a3248f411813f8ccba92a55feaac4bf360d29e2ff05ee7d8e1ef2d7dbf/MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad", size = 19132, upload-time = "2023-06-02T21:43:45.578Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/7c/59a3248f411813f8ccba92a55feaac4bf360d29e2ff05ee7d8e1ef2d7dbf/MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad", size = 19132 } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/1d/713d443799d935f4d26a4f1510c9e61b1d288592fb869845e5cc92a1e055/MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa", size = 17846, upload-time = "2023-06-02T21:42:33.954Z" }, - { url = "https://files.pythonhosted.org/packages/f7/9c/86cbd8e0e1d81f0ba420f20539dd459c50537c7751e28102dbfee2b6f28c/MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57", size = 13720, upload-time = "2023-06-02T21:42:35.102Z" }, - { url = "https://files.pythonhosted.org/packages/a6/56/f1d4ee39e898a9e63470cbb7fae1c58cce6874f25f54220b89213a47f273/MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f", size = 26498, upload-time = "2023-06-02T21:42:36.608Z" }, - { url = "https://files.pythonhosted.org/packages/12/b3/d9ed2c0971e1435b8a62354b18d3060b66c8cb1d368399ec0b9baa7c0ee5/MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52", size = 25691, upload-time = "2023-06-02T21:42:37.778Z" }, - { url = "https://files.pythonhosted.org/packages/bf/b7/c5ba9b7ad9ad21fc4a60df226615cf43ead185d328b77b0327d603d00cc5/MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00", size = 25366, upload-time = "2023-06-02T21:42:39.441Z" }, - { url = "https://files.pythonhosted.org/packages/71/61/f5673d7aac2cf7f203859008bb3fc2b25187aa330067c5e9955e5c5ebbab/MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6", size = 30505, upload-time = "2023-06-02T21:42:41.088Z" }, - { url = "https://files.pythonhosted.org/packages/47/26/932140621773bfd4df3223fbdd9e78de3477f424f0d2987c313b1cb655ff/MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779", size = 29616, upload-time = "2023-06-02T21:42:42.273Z" }, - { url = "https://files.pythonhosted.org/packages/3c/c8/74d13c999cbb49e3460bf769025659a37ef4a8e884de629720ab4e42dcdb/MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7", size = 29891, upload-time = "2023-06-02T21:42:43.635Z" }, - { url = "https://files.pythonhosted.org/packages/96/e4/4db3b1abc5a1fe7295aa0683eafd13832084509c3b8236f3faf8dd4eff75/MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431", size = 16525, upload-time = "2023-06-02T21:42:45.271Z" }, - { url = "https://files.pythonhosted.org/packages/84/a8/c4aebb8a14a1d39d5135eb8233a0b95831cdc42c4088358449c3ed657044/MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559", size = 17083, upload-time = "2023-06-02T21:42:46.948Z" }, - { url = "https://files.pythonhosted.org/packages/fe/09/c31503cb8150cf688c1534a7135cc39bb9092f8e0e6369ec73494d16ee0e/MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c", size = 17862, upload-time = "2023-06-02T21:42:48.569Z" }, - { url = "https://files.pythonhosted.org/packages/c0/c7/171f5ac6b065e1425e8fabf4a4dfbeca76fd8070072c6a41bd5c07d90d8b/MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575", size = 13738, upload-time = "2023-06-02T21:42:49.727Z" }, - { url = "https://files.pythonhosted.org/packages/a2/f7/9175ad1b8152092f7c3b78c513c1bdfe9287e0564447d1c2d3d1a2471540/MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee", size = 28891, upload-time = "2023-06-02T21:42:51.33Z" }, - { url = "https://files.pythonhosted.org/packages/fe/21/2eff1de472ca6c99ec3993eab11308787b9879af9ca8bbceb4868cf4f2ca/MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2", size = 28096, upload-time = "2023-06-02T21:42:52.966Z" }, - { url = "https://files.pythonhosted.org/packages/f4/a0/103f94793c3bf829a18d2415117334ece115aeca56f2df1c47fa02c6dbd6/MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9", size = 27631, upload-time = "2023-06-02T21:42:54.518Z" }, - { url = "https://files.pythonhosted.org/packages/43/70/f24470f33b2035b035ef0c0ffebf57006beb2272cf3df068fc5154e04ead/MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc", size = 33863, upload-time = "2023-06-02T21:42:55.777Z" }, - { url = "https://files.pythonhosted.org/packages/32/d4/ce98c4ca713d91c4a17c1a184785cc00b9e9c25699d618956c2b9999500a/MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9", size = 32591, upload-time = "2023-06-02T21:42:57.415Z" }, - { url = "https://files.pythonhosted.org/packages/bb/82/f88ccb3ca6204a4536cf7af5abdad7c3657adac06ab33699aa67279e0744/MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac", size = 33186, upload-time = "2023-06-02T21:42:59.107Z" }, - { url = "https://files.pythonhosted.org/packages/44/53/93405d37bb04a10c43b1bdd6f548097478d494d7eadb4b364e3e1337f0cc/MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb", size = 16537, upload-time = "2023-06-02T21:43:00.927Z" }, - { url = "https://files.pythonhosted.org/packages/be/bb/08b85bc194034efbf572e70c3951549c8eca0ada25363afc154386b5390a/MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686", size = 17089, upload-time = "2023-06-02T21:43:02.355Z" }, - { url = "https://files.pythonhosted.org/packages/89/5a/ee546f2aa73a1d6fcfa24272f356fe06d29acca81e76b8d32ca53e429a2e/MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc", size = 17849, upload-time = "2023-09-07T16:00:43.795Z" }, - { url = "https://files.pythonhosted.org/packages/3a/72/9f683a059bde096776e8acf9aa34cbbba21ddc399861fe3953790d4f2cde/MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823", size = 13700, upload-time = "2023-09-07T16:00:45.384Z" }, - { url = "https://files.pythonhosted.org/packages/9d/78/92f15eb9b1e8f1668a9787ba103cf6f8d19a9efed8150245404836145c24/MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11", size = 29319, upload-time = "2023-09-07T16:00:46.48Z" }, - { url = "https://files.pythonhosted.org/packages/51/94/9a04085114ff2c24f7424dbc890a281d73c5a74ea935dc2e69c66a3bd558/MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd", size = 28314, upload-time = "2023-09-07T16:00:47.64Z" }, - { url = "https://files.pythonhosted.org/packages/ec/53/fcb3214bd370185e223b209ce6bb010fb887ea57173ca4f75bd211b24e10/MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939", size = 27696, upload-time = "2023-09-07T16:00:48.92Z" }, - { url = "https://files.pythonhosted.org/packages/e7/33/54d29854716725d7826079b8984dd235fac76dab1c32321e555d493e61f5/MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c", size = 33746, upload-time = "2023-09-07T16:00:50.081Z" }, - { url = "https://files.pythonhosted.org/packages/11/40/ea7f85e2681d29bc9301c757257de561923924f24de1802d9c3baa396bb4/MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c", size = 32131, upload-time = "2023-09-07T16:00:51.822Z" }, - { url = "https://files.pythonhosted.org/packages/41/f1/bc770c37ecd58638c18f8ec85df205dacb818ccf933692082fd93010a4bc/MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1", size = 32878, upload-time = "2023-09-07T16:00:53.575Z" }, - { url = "https://files.pythonhosted.org/packages/49/74/bf95630aab0a9ed6a67556cd4e54f6aeb0e74f4cb0fd2f229154873a4be4/MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007", size = 16426, upload-time = "2023-09-07T16:00:55.987Z" }, - { url = "https://files.pythonhosted.org/packages/44/44/dbaf65876e258facd65f586dde158387ab89963e7f2235551afc9c2e24c2/MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb", size = 16979, upload-time = "2023-09-07T16:00:57.77Z" }, + { url = "https://files.pythonhosted.org/packages/20/1d/713d443799d935f4d26a4f1510c9e61b1d288592fb869845e5cc92a1e055/MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa", size = 17846 }, + { url = "https://files.pythonhosted.org/packages/f7/9c/86cbd8e0e1d81f0ba420f20539dd459c50537c7751e28102dbfee2b6f28c/MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57", size = 13720 }, + { url = "https://files.pythonhosted.org/packages/a6/56/f1d4ee39e898a9e63470cbb7fae1c58cce6874f25f54220b89213a47f273/MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f", size = 26498 }, + { url = "https://files.pythonhosted.org/packages/12/b3/d9ed2c0971e1435b8a62354b18d3060b66c8cb1d368399ec0b9baa7c0ee5/MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52", size = 25691 }, + { url = "https://files.pythonhosted.org/packages/bf/b7/c5ba9b7ad9ad21fc4a60df226615cf43ead185d328b77b0327d603d00cc5/MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00", size = 25366 }, + { url = "https://files.pythonhosted.org/packages/71/61/f5673d7aac2cf7f203859008bb3fc2b25187aa330067c5e9955e5c5ebbab/MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6", size = 30505 }, + { url = "https://files.pythonhosted.org/packages/47/26/932140621773bfd4df3223fbdd9e78de3477f424f0d2987c313b1cb655ff/MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779", size = 29616 }, + { url = "https://files.pythonhosted.org/packages/3c/c8/74d13c999cbb49e3460bf769025659a37ef4a8e884de629720ab4e42dcdb/MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7", size = 29891 }, + { url = "https://files.pythonhosted.org/packages/96/e4/4db3b1abc5a1fe7295aa0683eafd13832084509c3b8236f3faf8dd4eff75/MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431", size = 16525 }, + { url = "https://files.pythonhosted.org/packages/84/a8/c4aebb8a14a1d39d5135eb8233a0b95831cdc42c4088358449c3ed657044/MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559", size = 17083 }, + { url = "https://files.pythonhosted.org/packages/fe/09/c31503cb8150cf688c1534a7135cc39bb9092f8e0e6369ec73494d16ee0e/MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c", size = 17862 }, + { url = "https://files.pythonhosted.org/packages/c0/c7/171f5ac6b065e1425e8fabf4a4dfbeca76fd8070072c6a41bd5c07d90d8b/MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575", size = 13738 }, + { url = "https://files.pythonhosted.org/packages/a2/f7/9175ad1b8152092f7c3b78c513c1bdfe9287e0564447d1c2d3d1a2471540/MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee", size = 28891 }, + { url = "https://files.pythonhosted.org/packages/fe/21/2eff1de472ca6c99ec3993eab11308787b9879af9ca8bbceb4868cf4f2ca/MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2", size = 28096 }, + { url = "https://files.pythonhosted.org/packages/f4/a0/103f94793c3bf829a18d2415117334ece115aeca56f2df1c47fa02c6dbd6/MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9", size = 27631 }, + { url = "https://files.pythonhosted.org/packages/43/70/f24470f33b2035b035ef0c0ffebf57006beb2272cf3df068fc5154e04ead/MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc", size = 33863 }, + { url = "https://files.pythonhosted.org/packages/32/d4/ce98c4ca713d91c4a17c1a184785cc00b9e9c25699d618956c2b9999500a/MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9", size = 32591 }, + { url = "https://files.pythonhosted.org/packages/bb/82/f88ccb3ca6204a4536cf7af5abdad7c3657adac06ab33699aa67279e0744/MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac", size = 33186 }, + { url = "https://files.pythonhosted.org/packages/44/53/93405d37bb04a10c43b1bdd6f548097478d494d7eadb4b364e3e1337f0cc/MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb", size = 16537 }, + { url = "https://files.pythonhosted.org/packages/be/bb/08b85bc194034efbf572e70c3951549c8eca0ada25363afc154386b5390a/MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686", size = 17089 }, + { url = "https://files.pythonhosted.org/packages/89/5a/ee546f2aa73a1d6fcfa24272f356fe06d29acca81e76b8d32ca53e429a2e/MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc", size = 17849 }, + { url = "https://files.pythonhosted.org/packages/3a/72/9f683a059bde096776e8acf9aa34cbbba21ddc399861fe3953790d4f2cde/MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823", size = 13700 }, + { url = "https://files.pythonhosted.org/packages/9d/78/92f15eb9b1e8f1668a9787ba103cf6f8d19a9efed8150245404836145c24/MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11", size = 29319 }, + { url = "https://files.pythonhosted.org/packages/51/94/9a04085114ff2c24f7424dbc890a281d73c5a74ea935dc2e69c66a3bd558/MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd", size = 28314 }, + { url = "https://files.pythonhosted.org/packages/ec/53/fcb3214bd370185e223b209ce6bb010fb887ea57173ca4f75bd211b24e10/MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939", size = 27696 }, + { url = "https://files.pythonhosted.org/packages/e7/33/54d29854716725d7826079b8984dd235fac76dab1c32321e555d493e61f5/MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c", size = 33746 }, + { url = "https://files.pythonhosted.org/packages/11/40/ea7f85e2681d29bc9301c757257de561923924f24de1802d9c3baa396bb4/MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c", size = 32131 }, + { url = "https://files.pythonhosted.org/packages/41/f1/bc770c37ecd58638c18f8ec85df205dacb818ccf933692082fd93010a4bc/MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1", size = 32878 }, + { url = "https://files.pythonhosted.org/packages/49/74/bf95630aab0a9ed6a67556cd4e54f6aeb0e74f4cb0fd2f229154873a4be4/MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007", size = 16426 }, + { url = "https://files.pythonhosted.org/packages/44/44/dbaf65876e258facd65f586dde158387ab89963e7f2235551afc9c2e24c2/MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb", size = 16979 }, ] [[package]] @@ -1482,26 +1475,26 @@ dependencies = [ { name = "pyparsing", marker = "python_full_version < '3.11'" }, { name = "python-dateutil", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/ab/38a0e94cb01dacb50f06957c2bed1c83b8f9dac6618988a37b2487862944/matplotlib-3.8.2.tar.gz", hash = "sha256:01a978b871b881ee76017152f1f1a0cbf6bd5f7b8ff8c96df0df1bd57d8755a1", size = 35866957, upload-time = "2023-11-17T21:16:40.15Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/ab/38a0e94cb01dacb50f06957c2bed1c83b8f9dac6618988a37b2487862944/matplotlib-3.8.2.tar.gz", hash = "sha256:01a978b871b881ee76017152f1f1a0cbf6bd5f7b8ff8c96df0df1bd57d8755a1", size = 35866957 } wheels = [ - { url = "https://files.pythonhosted.org/packages/92/d0/fc5f6796a1956f5b9a33555611d01a3cec038f000c3d70ecb051b1631ac4/matplotlib-3.8.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:09796f89fb71a0c0e1e2f4bdaf63fb2cefc84446bb963ecdeb40dfee7dfa98c7", size = 7590640, upload-time = "2023-11-17T21:17:02.834Z" }, - { url = "https://files.pythonhosted.org/packages/57/44/007b592809f50883c910db9ec4b81b16dfa0136407250fb581824daabf03/matplotlib-3.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f9c6976748a25e8b9be51ea028df49b8e561eed7809146da7a47dbecebab367", size = 7484350, upload-time = "2023-11-17T21:17:12.281Z" }, - { url = "https://files.pythonhosted.org/packages/01/87/c7b24f3048234fe10184560263be2173311376dc3d1fa329de7f012d6ce5/matplotlib-3.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b78e4f2cedf303869b782071b55fdde5987fda3038e9d09e58c91cc261b5ad18", size = 11382388, upload-time = "2023-11-17T21:17:26.461Z" }, - { url = "https://files.pythonhosted.org/packages/19/e5/a4ea514515f270224435c69359abb7a3d152ed31b9ee3ba5e63017461945/matplotlib-3.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e208f46cf6576a7624195aa047cb344a7f802e113bb1a06cfd4bee431de5e31", size = 11611959, upload-time = "2023-11-17T21:17:40.541Z" }, - { url = "https://files.pythonhosted.org/packages/09/23/ab5a562c9acb81e351b084bea39f65b153918417fb434619cf5a19f44a55/matplotlib-3.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:46a569130ff53798ea5f50afce7406e91fdc471ca1e0e26ba976a8c734c9427a", size = 9536938, upload-time = "2023-11-17T21:17:49.925Z" }, - { url = "https://files.pythonhosted.org/packages/46/37/b5e27ab30ecc0a3694c8a78287b5ef35dad0c3095c144fcc43081170bfd6/matplotlib-3.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:830f00640c965c5b7f6bc32f0d4ce0c36dfe0379f7dd65b07a00c801713ec40a", size = 7643836, upload-time = "2023-11-17T21:17:58.379Z" }, - { url = "https://files.pythonhosted.org/packages/a9/0d/53afb186adafc7326d093b8333e8a79974c495095771659f4304626c4bc7/matplotlib-3.8.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d86593ccf546223eb75a39b44c32788e6f6440d13cfc4750c1c15d0fcb850b63", size = 7593458, upload-time = "2023-11-17T21:18:06.141Z" }, - { url = "https://files.pythonhosted.org/packages/ce/25/a557ee10ac9dce1300850024707ce1850a6958f1673a9194be878b99d631/matplotlib-3.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9a5430836811b7652991939012f43d2808a2db9b64ee240387e8c43e2e5578c8", size = 7486840, upload-time = "2023-11-17T21:18:13.706Z" }, - { url = "https://files.pythonhosted.org/packages/e7/3d/72712b3895ee180f6e342638a8591c31912fbcc09ce9084cc256da16d0a0/matplotlib-3.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9576723858a78751d5aacd2497b8aef29ffea6d1c95981505877f7ac28215c6", size = 11387332, upload-time = "2023-11-17T21:18:23.699Z" }, - { url = "https://files.pythonhosted.org/packages/92/1a/cd3e0c90d1a763ad90073e13b189b4702f11becf4e71dbbad70a7a149811/matplotlib-3.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ba9cbd8ac6cf422f3102622b20f8552d601bf8837e49a3afed188d560152788", size = 11616911, upload-time = "2023-11-17T21:18:35.27Z" }, - { url = "https://files.pythonhosted.org/packages/78/4a/bad239071477305a3758eb4810615e310a113399cddd7682998be9f01e97/matplotlib-3.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:03f9d160a29e0b65c0790bb07f4f45d6a181b1ac33eb1bb0dd225986450148f0", size = 9549260, upload-time = "2023-11-17T21:18:44.836Z" }, - { url = "https://files.pythonhosted.org/packages/26/5a/27fd341e4510257789f19a4b4be8bb90d1113b8f176c3dab562b4f21466e/matplotlib-3.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:3773002da767f0a9323ba1a9b9b5d00d6257dbd2a93107233167cfb581f64717", size = 7645742, upload-time = "2023-11-17T21:18:53.448Z" }, - { url = "https://files.pythonhosted.org/packages/e4/1b/864d28d5a72d586ac137f4ca54d5afc8b869720e30d508dbd9adcce4d231/matplotlib-3.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4c318c1e95e2f5926fba326f68177dee364aa791d6df022ceb91b8221bd0a627", size = 7590988, upload-time = "2023-11-17T21:19:01.119Z" }, - { url = "https://files.pythonhosted.org/packages/9a/b0/dd2b60f2dd90fbc21d1d3129c36a453c322d7995d5e3589f5b3c59ee528d/matplotlib-3.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:091275d18d942cf1ee9609c830a1bc36610607d8223b1b981c37d5c9fc3e46a4", size = 7483594, upload-time = "2023-11-17T21:19:09.865Z" }, - { url = "https://files.pythonhosted.org/packages/33/da/9942533ad9f96753bde0e5a5d48eacd6c21de8ea1ad16570e31bda8a017f/matplotlib-3.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b0f3b8ea0e99e233a4bcc44590f01604840d833c280ebb8fe5554fd3e6cfe8d", size = 11380843, upload-time = "2023-11-17T21:19:20.46Z" }, - { url = "https://files.pythonhosted.org/packages/fc/52/bfd36eb4745a3b21b3946c2c3a15679b620e14574fe2b98e9451b65ef578/matplotlib-3.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7b1704a530395aaf73912be741c04d181f82ca78084fbd80bc737be04848331", size = 11604608, upload-time = "2023-11-17T21:19:31.363Z" }, - { url = "https://files.pythonhosted.org/packages/6d/8c/0cdfbf604d4ea3dfa77435176c51e233cc408ad8f3efbf8d2c9f57cbdafb/matplotlib-3.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533b0e3b0c6768eef8cbe4b583731ce25a91ab54a22f830db2b031e83cca9213", size = 9545252, upload-time = "2023-11-17T21:19:42.271Z" }, - { url = "https://files.pythonhosted.org/packages/2e/51/c77a14869b7eb9d6fb440e811b754fc3950d6868c38ace57d0632b674415/matplotlib-3.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:0f4fc5d72b75e2c18e55eb32292659cf731d9d5b312a6eb036506304f4675630", size = 7645067, upload-time = "2023-11-17T21:19:50.091Z" }, + { url = "https://files.pythonhosted.org/packages/92/d0/fc5f6796a1956f5b9a33555611d01a3cec038f000c3d70ecb051b1631ac4/matplotlib-3.8.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:09796f89fb71a0c0e1e2f4bdaf63fb2cefc84446bb963ecdeb40dfee7dfa98c7", size = 7590640 }, + { url = "https://files.pythonhosted.org/packages/57/44/007b592809f50883c910db9ec4b81b16dfa0136407250fb581824daabf03/matplotlib-3.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f9c6976748a25e8b9be51ea028df49b8e561eed7809146da7a47dbecebab367", size = 7484350 }, + { url = "https://files.pythonhosted.org/packages/01/87/c7b24f3048234fe10184560263be2173311376dc3d1fa329de7f012d6ce5/matplotlib-3.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b78e4f2cedf303869b782071b55fdde5987fda3038e9d09e58c91cc261b5ad18", size = 11382388 }, + { url = "https://files.pythonhosted.org/packages/19/e5/a4ea514515f270224435c69359abb7a3d152ed31b9ee3ba5e63017461945/matplotlib-3.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e208f46cf6576a7624195aa047cb344a7f802e113bb1a06cfd4bee431de5e31", size = 11611959 }, + { url = "https://files.pythonhosted.org/packages/09/23/ab5a562c9acb81e351b084bea39f65b153918417fb434619cf5a19f44a55/matplotlib-3.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:46a569130ff53798ea5f50afce7406e91fdc471ca1e0e26ba976a8c734c9427a", size = 9536938 }, + { url = "https://files.pythonhosted.org/packages/46/37/b5e27ab30ecc0a3694c8a78287b5ef35dad0c3095c144fcc43081170bfd6/matplotlib-3.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:830f00640c965c5b7f6bc32f0d4ce0c36dfe0379f7dd65b07a00c801713ec40a", size = 7643836 }, + { url = "https://files.pythonhosted.org/packages/a9/0d/53afb186adafc7326d093b8333e8a79974c495095771659f4304626c4bc7/matplotlib-3.8.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d86593ccf546223eb75a39b44c32788e6f6440d13cfc4750c1c15d0fcb850b63", size = 7593458 }, + { url = "https://files.pythonhosted.org/packages/ce/25/a557ee10ac9dce1300850024707ce1850a6958f1673a9194be878b99d631/matplotlib-3.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9a5430836811b7652991939012f43d2808a2db9b64ee240387e8c43e2e5578c8", size = 7486840 }, + { url = "https://files.pythonhosted.org/packages/e7/3d/72712b3895ee180f6e342638a8591c31912fbcc09ce9084cc256da16d0a0/matplotlib-3.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9576723858a78751d5aacd2497b8aef29ffea6d1c95981505877f7ac28215c6", size = 11387332 }, + { url = "https://files.pythonhosted.org/packages/92/1a/cd3e0c90d1a763ad90073e13b189b4702f11becf4e71dbbad70a7a149811/matplotlib-3.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ba9cbd8ac6cf422f3102622b20f8552d601bf8837e49a3afed188d560152788", size = 11616911 }, + { url = "https://files.pythonhosted.org/packages/78/4a/bad239071477305a3758eb4810615e310a113399cddd7682998be9f01e97/matplotlib-3.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:03f9d160a29e0b65c0790bb07f4f45d6a181b1ac33eb1bb0dd225986450148f0", size = 9549260 }, + { url = "https://files.pythonhosted.org/packages/26/5a/27fd341e4510257789f19a4b4be8bb90d1113b8f176c3dab562b4f21466e/matplotlib-3.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:3773002da767f0a9323ba1a9b9b5d00d6257dbd2a93107233167cfb581f64717", size = 7645742 }, + { url = "https://files.pythonhosted.org/packages/e4/1b/864d28d5a72d586ac137f4ca54d5afc8b869720e30d508dbd9adcce4d231/matplotlib-3.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4c318c1e95e2f5926fba326f68177dee364aa791d6df022ceb91b8221bd0a627", size = 7590988 }, + { url = "https://files.pythonhosted.org/packages/9a/b0/dd2b60f2dd90fbc21d1d3129c36a453c322d7995d5e3589f5b3c59ee528d/matplotlib-3.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:091275d18d942cf1ee9609c830a1bc36610607d8223b1b981c37d5c9fc3e46a4", size = 7483594 }, + { url = "https://files.pythonhosted.org/packages/33/da/9942533ad9f96753bde0e5a5d48eacd6c21de8ea1ad16570e31bda8a017f/matplotlib-3.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b0f3b8ea0e99e233a4bcc44590f01604840d833c280ebb8fe5554fd3e6cfe8d", size = 11380843 }, + { url = "https://files.pythonhosted.org/packages/fc/52/bfd36eb4745a3b21b3946c2c3a15679b620e14574fe2b98e9451b65ef578/matplotlib-3.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7b1704a530395aaf73912be741c04d181f82ca78084fbd80bc737be04848331", size = 11604608 }, + { url = "https://files.pythonhosted.org/packages/6d/8c/0cdfbf604d4ea3dfa77435176c51e233cc408ad8f3efbf8d2c9f57cbdafb/matplotlib-3.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533b0e3b0c6768eef8cbe4b583731ce25a91ab54a22f830db2b031e83cca9213", size = 9545252 }, + { url = "https://files.pythonhosted.org/packages/2e/51/c77a14869b7eb9d6fb440e811b754fc3950d6868c38ace57d0632b674415/matplotlib-3.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:0f4fc5d72b75e2c18e55eb32292659cf731d9d5b312a6eb036506304f4675630", size = 7645067 }, ] [[package]] @@ -1533,121 +1526,121 @@ dependencies = [ { name = "pyparsing", marker = "python_full_version >= '3.11'" }, { name = "python-dateutil", marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/43/91/f2939bb60b7ebf12478b030e0d7f340247390f402b3b189616aad790c366/matplotlib-3.10.5.tar.gz", hash = "sha256:352ed6ccfb7998a00881692f38b4ca083c691d3e275b4145423704c34c909076", size = 34804044, upload-time = "2025-07-31T18:09:33.805Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/91/f2939bb60b7ebf12478b030e0d7f340247390f402b3b189616aad790c366/matplotlib-3.10.5.tar.gz", hash = "sha256:352ed6ccfb7998a00881692f38b4ca083c691d3e275b4145423704c34c909076", size = 34804044 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/89/5355cdfe43242cb4d1a64a67cb6831398b665ad90e9702c16247cbd8d5ab/matplotlib-3.10.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:5d4773a6d1c106ca05cb5a5515d277a6bb96ed09e5c8fab6b7741b8fcaa62c8f", size = 8229094, upload-time = "2025-07-31T18:07:36.507Z" }, - { url = "https://files.pythonhosted.org/packages/34/bc/ba802650e1c69650faed261a9df004af4c6f21759d7a1ec67fe972f093b3/matplotlib-3.10.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc88af74e7ba27de6cbe6faee916024ea35d895ed3d61ef6f58c4ce97da7185a", size = 8091464, upload-time = "2025-07-31T18:07:38.864Z" }, - { url = "https://files.pythonhosted.org/packages/ac/64/8d0c8937dee86c286625bddb1902efacc3e22f2b619f5b5a8df29fe5217b/matplotlib-3.10.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:64c4535419d5617f7363dad171a5a59963308e0f3f813c4bed6c9e6e2c131512", size = 8653163, upload-time = "2025-07-31T18:07:41.141Z" }, - { url = "https://files.pythonhosted.org/packages/11/dc/8dfc0acfbdc2fc2336c72561b7935cfa73db9ca70b875d8d3e1b3a6f371a/matplotlib-3.10.5-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a277033048ab22d34f88a3c5243938cef776493f6201a8742ed5f8b553201343", size = 9490635, upload-time = "2025-07-31T18:07:42.936Z" }, - { url = "https://files.pythonhosted.org/packages/54/02/e3fdfe0f2e9fb05f3a691d63876639dbf684170fdcf93231e973104153b4/matplotlib-3.10.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e4a6470a118a2e93022ecc7d3bd16b3114b2004ea2bf014fff875b3bc99b70c6", size = 9539036, upload-time = "2025-07-31T18:07:45.18Z" }, - { url = "https://files.pythonhosted.org/packages/c1/29/82bf486ff7f4dbedfb11ccc207d0575cbe3be6ea26f75be514252bde3d70/matplotlib-3.10.5-cp310-cp310-win_amd64.whl", hash = "sha256:7e44cada61bec8833c106547786814dd4a266c1b2964fd25daa3804f1b8d4467", size = 8093529, upload-time = "2025-07-31T18:07:49.553Z" }, - { url = "https://files.pythonhosted.org/packages/aa/c7/1f2db90a1d43710478bb1e9b57b162852f79234d28e4f48a28cc415aa583/matplotlib-3.10.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:dcfc39c452c6a9f9028d3e44d2d721484f665304857188124b505b2c95e1eecf", size = 8239216, upload-time = "2025-07-31T18:07:51.947Z" }, - { url = "https://files.pythonhosted.org/packages/82/6d/ca6844c77a4f89b1c9e4d481c412e1d1dbabf2aae2cbc5aa2da4a1d6683e/matplotlib-3.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:903352681b59f3efbf4546985142a9686ea1d616bb054b09a537a06e4b892ccf", size = 8102130, upload-time = "2025-07-31T18:07:53.65Z" }, - { url = "https://files.pythonhosted.org/packages/1d/1e/5e187a30cc673a3e384f3723e5f3c416033c1d8d5da414f82e4e731128ea/matplotlib-3.10.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:080c3676a56b8ee1c762bcf8fca3fe709daa1ee23e6ef06ad9f3fc17332f2d2a", size = 8666471, upload-time = "2025-07-31T18:07:55.304Z" }, - { url = "https://files.pythonhosted.org/packages/03/c0/95540d584d7d645324db99a845ac194e915ef75011a0d5e19e1b5cee7e69/matplotlib-3.10.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b4984d5064a35b6f66d2c11d668565f4389b1119cc64db7a4c1725bc11adffc", size = 9500518, upload-time = "2025-07-31T18:07:57.199Z" }, - { url = "https://files.pythonhosted.org/packages/ba/2e/e019352099ea58b4169adb9c6e1a2ad0c568c6377c2b677ee1f06de2adc7/matplotlib-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3967424121d3a46705c9fa9bdb0931de3228f13f73d7bb03c999c88343a89d89", size = 9552372, upload-time = "2025-07-31T18:07:59.41Z" }, - { url = "https://files.pythonhosted.org/packages/b7/81/3200b792a5e8b354f31f4101ad7834743ad07b6d620259f2059317b25e4d/matplotlib-3.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:33775bbeb75528555a15ac29396940128ef5613cf9a2d31fb1bfd18b3c0c0903", size = 8100634, upload-time = "2025-07-31T18:08:01.801Z" }, - { url = "https://files.pythonhosted.org/packages/52/46/a944f6f0c1f5476a0adfa501969d229ce5ae60cf9a663be0e70361381f89/matplotlib-3.10.5-cp311-cp311-win_arm64.whl", hash = "sha256:c61333a8e5e6240e73769d5826b9a31d8b22df76c0778f8480baf1b4b01c9420", size = 7978880, upload-time = "2025-07-31T18:08:03.407Z" }, - { url = "https://files.pythonhosted.org/packages/66/1e/c6f6bcd882d589410b475ca1fc22e34e34c82adff519caf18f3e6dd9d682/matplotlib-3.10.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:00b6feadc28a08bd3c65b2894f56cf3c94fc8f7adcbc6ab4516ae1e8ed8f62e2", size = 8253056, upload-time = "2025-07-31T18:08:05.385Z" }, - { url = "https://files.pythonhosted.org/packages/53/e6/d6f7d1b59413f233793dda14419776f5f443bcccb2dfc84b09f09fe05dbe/matplotlib-3.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee98a5c5344dc7f48dc261b6ba5d9900c008fc12beb3fa6ebda81273602cc389", size = 8110131, upload-time = "2025-07-31T18:08:07.293Z" }, - { url = "https://files.pythonhosted.org/packages/66/2b/bed8a45e74957549197a2ac2e1259671cd80b55ed9e1fe2b5c94d88a9202/matplotlib-3.10.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a17e57e33de901d221a07af32c08870ed4528db0b6059dce7d7e65c1122d4bea", size = 8669603, upload-time = "2025-07-31T18:08:09.064Z" }, - { url = "https://files.pythonhosted.org/packages/7e/a7/315e9435b10d057f5e52dfc603cd353167ae28bb1a4e033d41540c0067a4/matplotlib-3.10.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97b9d6443419085950ee4a5b1ee08c363e5c43d7176e55513479e53669e88468", size = 9508127, upload-time = "2025-07-31T18:08:10.845Z" }, - { url = "https://files.pythonhosted.org/packages/7f/d9/edcbb1f02ca99165365d2768d517898c22c6040187e2ae2ce7294437c413/matplotlib-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ceefe5d40807d29a66ae916c6a3915d60ef9f028ce1927b84e727be91d884369", size = 9566926, upload-time = "2025-07-31T18:08:13.186Z" }, - { url = "https://files.pythonhosted.org/packages/3b/d9/6dd924ad5616c97b7308e6320cf392c466237a82a2040381163b7500510a/matplotlib-3.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:c04cba0f93d40e45b3c187c6c52c17f24535b27d545f757a2fffebc06c12b98b", size = 8107599, upload-time = "2025-07-31T18:08:15.116Z" }, - { url = "https://files.pythonhosted.org/packages/0e/f3/522dc319a50f7b0279fbe74f86f7a3506ce414bc23172098e8d2bdf21894/matplotlib-3.10.5-cp312-cp312-win_arm64.whl", hash = "sha256:a41bcb6e2c8e79dc99c5511ae6f7787d2fb52efd3d805fff06d5d4f667db16b2", size = 7978173, upload-time = "2025-07-31T18:08:21.518Z" }, - { url = "https://files.pythonhosted.org/packages/8d/05/4f3c1f396075f108515e45cb8d334aff011a922350e502a7472e24c52d77/matplotlib-3.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:354204db3f7d5caaa10e5de74549ef6a05a4550fdd1c8f831ab9bca81efd39ed", size = 8253586, upload-time = "2025-07-31T18:08:23.107Z" }, - { url = "https://files.pythonhosted.org/packages/2f/2c/e084415775aac7016c3719fe7006cdb462582c6c99ac142f27303c56e243/matplotlib-3.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b072aac0c3ad563a2b3318124756cb6112157017f7431626600ecbe890df57a1", size = 8110715, upload-time = "2025-07-31T18:08:24.675Z" }, - { url = "https://files.pythonhosted.org/packages/52/1b/233e3094b749df16e3e6cd5a44849fd33852e692ad009cf7de00cf58ddf6/matplotlib-3.10.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d52fd5b684d541b5a51fb276b2b97b010c75bee9aa392f96b4a07aeb491e33c7", size = 8669397, upload-time = "2025-07-31T18:08:26.778Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ec/03f9e003a798f907d9f772eed9b7c6a9775d5bd00648b643ebfb88e25414/matplotlib-3.10.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee7a09ae2f4676276f5a65bd9f2bd91b4f9fbaedf49f40267ce3f9b448de501f", size = 9508646, upload-time = "2025-07-31T18:08:28.848Z" }, - { url = "https://files.pythonhosted.org/packages/91/e7/c051a7a386680c28487bca27d23b02d84f63e3d2a9b4d2fc478e6a42e37e/matplotlib-3.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ba6c3c9c067b83481d647af88b4e441d532acdb5ef22178a14935b0b881188f4", size = 9567424, upload-time = "2025-07-31T18:08:30.726Z" }, - { url = "https://files.pythonhosted.org/packages/36/c2/24302e93ff431b8f4173ee1dd88976c8d80483cadbc5d3d777cef47b3a1c/matplotlib-3.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:07442d2692c9bd1cceaa4afb4bbe5b57b98a7599de4dabfcca92d3eea70f9ebe", size = 8107809, upload-time = "2025-07-31T18:08:33.928Z" }, - { url = "https://files.pythonhosted.org/packages/0b/33/423ec6a668d375dad825197557ed8fbdb74d62b432c1ed8235465945475f/matplotlib-3.10.5-cp313-cp313-win_arm64.whl", hash = "sha256:48fe6d47380b68a37ccfcc94f009530e84d41f71f5dae7eda7c4a5a84aa0a674", size = 7978078, upload-time = "2025-07-31T18:08:36.764Z" }, - { url = "https://files.pythonhosted.org/packages/51/17/521fc16ec766455c7bb52cc046550cf7652f6765ca8650ff120aa2d197b6/matplotlib-3.10.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b80eb8621331449fc519541a7461987f10afa4f9cfd91afcd2276ebe19bd56c", size = 8295590, upload-time = "2025-07-31T18:08:38.521Z" }, - { url = "https://files.pythonhosted.org/packages/f8/12/23c28b2c21114c63999bae129fce7fd34515641c517ae48ce7b7dcd33458/matplotlib-3.10.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47a388908e469d6ca2a6015858fa924e0e8a2345a37125948d8e93a91c47933e", size = 8158518, upload-time = "2025-07-31T18:08:40.195Z" }, - { url = "https://files.pythonhosted.org/packages/81/f8/aae4eb25e8e7190759f3cb91cbeaa344128159ac92bb6b409e24f8711f78/matplotlib-3.10.5-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8b6b49167d208358983ce26e43aa4196073b4702858670f2eb111f9a10652b4b", size = 8691815, upload-time = "2025-07-31T18:08:42.238Z" }, - { url = "https://files.pythonhosted.org/packages/d0/ba/450c39ebdd486bd33a359fc17365ade46c6a96bf637bbb0df7824de2886c/matplotlib-3.10.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a8da0453a7fd8e3da114234ba70c5ba9ef0e98f190309ddfde0f089accd46ea", size = 9522814, upload-time = "2025-07-31T18:08:44.914Z" }, - { url = "https://files.pythonhosted.org/packages/89/11/9c66f6a990e27bb9aa023f7988d2d5809cb98aa39c09cbf20fba75a542ef/matplotlib-3.10.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52c6573dfcb7726a9907b482cd5b92e6b5499b284ffacb04ffbfe06b3e568124", size = 9573917, upload-time = "2025-07-31T18:08:47.038Z" }, - { url = "https://files.pythonhosted.org/packages/b3/69/8b49394de92569419e5e05e82e83df9b749a0ff550d07631ea96ed2eb35a/matplotlib-3.10.5-cp313-cp313t-win_amd64.whl", hash = "sha256:a23193db2e9d64ece69cac0c8231849db7dd77ce59c7b89948cf9d0ce655a3ce", size = 8181034, upload-time = "2025-07-31T18:08:48.943Z" }, - { url = "https://files.pythonhosted.org/packages/47/23/82dc435bb98a2fc5c20dffcac8f0b083935ac28286413ed8835df40d0baa/matplotlib-3.10.5-cp313-cp313t-win_arm64.whl", hash = "sha256:56da3b102cf6da2776fef3e71cd96fcf22103a13594a18ac9a9b31314e0be154", size = 8023337, upload-time = "2025-07-31T18:08:50.791Z" }, - { url = "https://files.pythonhosted.org/packages/ac/e0/26b6cfde31f5383503ee45dcb7e691d45dadf0b3f54639332b59316a97f8/matplotlib-3.10.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:96ef8f5a3696f20f55597ffa91c28e2e73088df25c555f8d4754931515512715", size = 8253591, upload-time = "2025-07-31T18:08:53.254Z" }, - { url = "https://files.pythonhosted.org/packages/c1/89/98488c7ef7ea20ea659af7499628c240a608b337af4be2066d644cfd0a0f/matplotlib-3.10.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:77fab633e94b9da60512d4fa0213daeb76d5a7b05156840c4fd0399b4b818837", size = 8112566, upload-time = "2025-07-31T18:08:55.116Z" }, - { url = "https://files.pythonhosted.org/packages/52/67/42294dfedc82aea55e1a767daf3263aacfb5a125f44ba189e685bab41b6f/matplotlib-3.10.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27f52634315e96b1debbfdc5c416592edcd9c4221bc2f520fd39c33db5d9f202", size = 9513281, upload-time = "2025-07-31T18:08:56.885Z" }, - { url = "https://files.pythonhosted.org/packages/e7/68/f258239e0cf34c2cbc816781c7ab6fca768452e6bf1119aedd2bd4a882a3/matplotlib-3.10.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:525f6e28c485c769d1f07935b660c864de41c37fd716bfa64158ea646f7084bb", size = 9780873, upload-time = "2025-07-31T18:08:59.241Z" }, - { url = "https://files.pythonhosted.org/packages/89/64/f4881554006bd12e4558bd66778bdd15d47b00a1f6c6e8b50f6208eda4b3/matplotlib-3.10.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1f5f3ec4c191253c5f2b7c07096a142c6a1c024d9f738247bfc8e3f9643fc975", size = 9568954, upload-time = "2025-07-31T18:09:01.244Z" }, - { url = "https://files.pythonhosted.org/packages/06/f8/42779d39c3f757e1f012f2dda3319a89fb602bd2ef98ce8faf0281f4febd/matplotlib-3.10.5-cp314-cp314-win_amd64.whl", hash = "sha256:707f9c292c4cd4716f19ab8a1f93f26598222cd931e0cd98fbbb1c5994bf7667", size = 8237465, upload-time = "2025-07-31T18:09:03.206Z" }, - { url = "https://files.pythonhosted.org/packages/cf/f8/153fd06b5160f0cd27c8b9dd797fcc9fb56ac6a0ebf3c1f765b6b68d3c8a/matplotlib-3.10.5-cp314-cp314-win_arm64.whl", hash = "sha256:21a95b9bf408178d372814de7baacd61c712a62cae560b5e6f35d791776f6516", size = 8108898, upload-time = "2025-07-31T18:09:05.231Z" }, - { url = "https://files.pythonhosted.org/packages/9a/ee/c4b082a382a225fe0d2a73f1f57cf6f6f132308805b493a54c8641006238/matplotlib-3.10.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a6b310f95e1102a8c7c817ef17b60ee5d1851b8c71b63d9286b66b177963039e", size = 8295636, upload-time = "2025-07-31T18:09:07.306Z" }, - { url = "https://files.pythonhosted.org/packages/30/73/2195fa2099718b21a20da82dfc753bf2af58d596b51aefe93e359dd5915a/matplotlib-3.10.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:94986a242747a0605cb3ff1cb98691c736f28a59f8ffe5175acaeb7397c49a5a", size = 8158575, upload-time = "2025-07-31T18:09:09.083Z" }, - { url = "https://files.pythonhosted.org/packages/f6/e9/a08cdb34618a91fa08f75e6738541da5cacde7c307cea18ff10f0d03fcff/matplotlib-3.10.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ff10ea43288f0c8bab608a305dc6c918cc729d429c31dcbbecde3b9f4d5b569", size = 9522815, upload-time = "2025-07-31T18:09:11.191Z" }, - { url = "https://files.pythonhosted.org/packages/4e/bb/34d8b7e0d1bb6d06ef45db01dfa560d5a67b1c40c0b998ce9ccde934bb09/matplotlib-3.10.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f6adb644c9d040ffb0d3434e440490a66cf73dbfa118a6f79cd7568431f7a012", size = 9783514, upload-time = "2025-07-31T18:09:13.307Z" }, - { url = "https://files.pythonhosted.org/packages/12/09/d330d1e55dcca2e11b4d304cc5227f52e2512e46828d6249b88e0694176e/matplotlib-3.10.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4fa40a8f98428f789a9dcacd625f59b7bc4e3ef6c8c7c80187a7a709475cf592", size = 9573932, upload-time = "2025-07-31T18:09:15.335Z" }, - { url = "https://files.pythonhosted.org/packages/eb/3b/f70258ac729aa004aca673800a53a2b0a26d49ca1df2eaa03289a1c40f81/matplotlib-3.10.5-cp314-cp314t-win_amd64.whl", hash = "sha256:95672a5d628b44207aab91ec20bf59c26da99de12b88f7e0b1fb0a84a86ff959", size = 8322003, upload-time = "2025-07-31T18:09:17.416Z" }, - { url = "https://files.pythonhosted.org/packages/5b/60/3601f8ce6d76a7c81c7f25a0e15fde0d6b66226dd187aa6d2838e6374161/matplotlib-3.10.5-cp314-cp314t-win_arm64.whl", hash = "sha256:2efaf97d72629e74252e0b5e3c46813e9eeaa94e011ecf8084a971a31a97f40b", size = 8153849, upload-time = "2025-07-31T18:09:19.673Z" }, - { url = "https://files.pythonhosted.org/packages/e4/eb/7d4c5de49eb78294e1a8e2be8a6ecff8b433e921b731412a56cd1abd3567/matplotlib-3.10.5-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b5fa2e941f77eb579005fb804026f9d0a1082276118d01cc6051d0d9626eaa7f", size = 8222360, upload-time = "2025-07-31T18:09:21.813Z" }, - { url = "https://files.pythonhosted.org/packages/16/8a/e435db90927b66b16d69f8f009498775f4469f8de4d14b87856965e58eba/matplotlib-3.10.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1fc0d2a3241cdcb9daaca279204a3351ce9df3c0e7e621c7e04ec28aaacaca30", size = 8087462, upload-time = "2025-07-31T18:09:23.504Z" }, - { url = "https://files.pythonhosted.org/packages/0b/dd/06c0e00064362f5647f318e00b435be2ff76a1bdced97c5eaf8347311fbe/matplotlib-3.10.5-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8dee65cb1424b7dc982fe87895b5613d4e691cc57117e8af840da0148ca6c1d7", size = 8659802, upload-time = "2025-07-31T18:09:25.256Z" }, - { url = "https://files.pythonhosted.org/packages/dc/d6/e921be4e1a5f7aca5194e1f016cb67ec294548e530013251f630713e456d/matplotlib-3.10.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:160e125da27a749481eaddc0627962990f6029811dbeae23881833a011a0907f", size = 8233224, upload-time = "2025-07-31T18:09:27.512Z" }, - { url = "https://files.pythonhosted.org/packages/ec/74/a2b9b04824b9c349c8f1b2d21d5af43fa7010039427f2b133a034cb09e59/matplotlib-3.10.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ac3d50760394d78a3c9be6b28318fe22b494c4fcf6407e8fd4794b538251899b", size = 8098539, upload-time = "2025-07-31T18:09:29.629Z" }, - { url = "https://files.pythonhosted.org/packages/fc/66/cd29ebc7f6c0d2a15d216fb572573e8fc38bd5d6dec3bd9d7d904c0949f7/matplotlib-3.10.5-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c49465bf689c4d59d174d0c7795fb42a21d4244d11d70e52b8011987367ac61", size = 8672192, upload-time = "2025-07-31T18:09:31.407Z" }, + { url = "https://files.pythonhosted.org/packages/d1/89/5355cdfe43242cb4d1a64a67cb6831398b665ad90e9702c16247cbd8d5ab/matplotlib-3.10.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:5d4773a6d1c106ca05cb5a5515d277a6bb96ed09e5c8fab6b7741b8fcaa62c8f", size = 8229094 }, + { url = "https://files.pythonhosted.org/packages/34/bc/ba802650e1c69650faed261a9df004af4c6f21759d7a1ec67fe972f093b3/matplotlib-3.10.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc88af74e7ba27de6cbe6faee916024ea35d895ed3d61ef6f58c4ce97da7185a", size = 8091464 }, + { url = "https://files.pythonhosted.org/packages/ac/64/8d0c8937dee86c286625bddb1902efacc3e22f2b619f5b5a8df29fe5217b/matplotlib-3.10.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:64c4535419d5617f7363dad171a5a59963308e0f3f813c4bed6c9e6e2c131512", size = 8653163 }, + { url = "https://files.pythonhosted.org/packages/11/dc/8dfc0acfbdc2fc2336c72561b7935cfa73db9ca70b875d8d3e1b3a6f371a/matplotlib-3.10.5-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a277033048ab22d34f88a3c5243938cef776493f6201a8742ed5f8b553201343", size = 9490635 }, + { url = "https://files.pythonhosted.org/packages/54/02/e3fdfe0f2e9fb05f3a691d63876639dbf684170fdcf93231e973104153b4/matplotlib-3.10.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e4a6470a118a2e93022ecc7d3bd16b3114b2004ea2bf014fff875b3bc99b70c6", size = 9539036 }, + { url = "https://files.pythonhosted.org/packages/c1/29/82bf486ff7f4dbedfb11ccc207d0575cbe3be6ea26f75be514252bde3d70/matplotlib-3.10.5-cp310-cp310-win_amd64.whl", hash = "sha256:7e44cada61bec8833c106547786814dd4a266c1b2964fd25daa3804f1b8d4467", size = 8093529 }, + { url = "https://files.pythonhosted.org/packages/aa/c7/1f2db90a1d43710478bb1e9b57b162852f79234d28e4f48a28cc415aa583/matplotlib-3.10.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:dcfc39c452c6a9f9028d3e44d2d721484f665304857188124b505b2c95e1eecf", size = 8239216 }, + { url = "https://files.pythonhosted.org/packages/82/6d/ca6844c77a4f89b1c9e4d481c412e1d1dbabf2aae2cbc5aa2da4a1d6683e/matplotlib-3.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:903352681b59f3efbf4546985142a9686ea1d616bb054b09a537a06e4b892ccf", size = 8102130 }, + { url = "https://files.pythonhosted.org/packages/1d/1e/5e187a30cc673a3e384f3723e5f3c416033c1d8d5da414f82e4e731128ea/matplotlib-3.10.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:080c3676a56b8ee1c762bcf8fca3fe709daa1ee23e6ef06ad9f3fc17332f2d2a", size = 8666471 }, + { url = "https://files.pythonhosted.org/packages/03/c0/95540d584d7d645324db99a845ac194e915ef75011a0d5e19e1b5cee7e69/matplotlib-3.10.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b4984d5064a35b6f66d2c11d668565f4389b1119cc64db7a4c1725bc11adffc", size = 9500518 }, + { url = "https://files.pythonhosted.org/packages/ba/2e/e019352099ea58b4169adb9c6e1a2ad0c568c6377c2b677ee1f06de2adc7/matplotlib-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3967424121d3a46705c9fa9bdb0931de3228f13f73d7bb03c999c88343a89d89", size = 9552372 }, + { url = "https://files.pythonhosted.org/packages/b7/81/3200b792a5e8b354f31f4101ad7834743ad07b6d620259f2059317b25e4d/matplotlib-3.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:33775bbeb75528555a15ac29396940128ef5613cf9a2d31fb1bfd18b3c0c0903", size = 8100634 }, + { url = "https://files.pythonhosted.org/packages/52/46/a944f6f0c1f5476a0adfa501969d229ce5ae60cf9a663be0e70361381f89/matplotlib-3.10.5-cp311-cp311-win_arm64.whl", hash = "sha256:c61333a8e5e6240e73769d5826b9a31d8b22df76c0778f8480baf1b4b01c9420", size = 7978880 }, + { url = "https://files.pythonhosted.org/packages/66/1e/c6f6bcd882d589410b475ca1fc22e34e34c82adff519caf18f3e6dd9d682/matplotlib-3.10.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:00b6feadc28a08bd3c65b2894f56cf3c94fc8f7adcbc6ab4516ae1e8ed8f62e2", size = 8253056 }, + { url = "https://files.pythonhosted.org/packages/53/e6/d6f7d1b59413f233793dda14419776f5f443bcccb2dfc84b09f09fe05dbe/matplotlib-3.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee98a5c5344dc7f48dc261b6ba5d9900c008fc12beb3fa6ebda81273602cc389", size = 8110131 }, + { url = "https://files.pythonhosted.org/packages/66/2b/bed8a45e74957549197a2ac2e1259671cd80b55ed9e1fe2b5c94d88a9202/matplotlib-3.10.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a17e57e33de901d221a07af32c08870ed4528db0b6059dce7d7e65c1122d4bea", size = 8669603 }, + { url = "https://files.pythonhosted.org/packages/7e/a7/315e9435b10d057f5e52dfc603cd353167ae28bb1a4e033d41540c0067a4/matplotlib-3.10.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97b9d6443419085950ee4a5b1ee08c363e5c43d7176e55513479e53669e88468", size = 9508127 }, + { url = "https://files.pythonhosted.org/packages/7f/d9/edcbb1f02ca99165365d2768d517898c22c6040187e2ae2ce7294437c413/matplotlib-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ceefe5d40807d29a66ae916c6a3915d60ef9f028ce1927b84e727be91d884369", size = 9566926 }, + { url = "https://files.pythonhosted.org/packages/3b/d9/6dd924ad5616c97b7308e6320cf392c466237a82a2040381163b7500510a/matplotlib-3.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:c04cba0f93d40e45b3c187c6c52c17f24535b27d545f757a2fffebc06c12b98b", size = 8107599 }, + { url = "https://files.pythonhosted.org/packages/0e/f3/522dc319a50f7b0279fbe74f86f7a3506ce414bc23172098e8d2bdf21894/matplotlib-3.10.5-cp312-cp312-win_arm64.whl", hash = "sha256:a41bcb6e2c8e79dc99c5511ae6f7787d2fb52efd3d805fff06d5d4f667db16b2", size = 7978173 }, + { url = "https://files.pythonhosted.org/packages/8d/05/4f3c1f396075f108515e45cb8d334aff011a922350e502a7472e24c52d77/matplotlib-3.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:354204db3f7d5caaa10e5de74549ef6a05a4550fdd1c8f831ab9bca81efd39ed", size = 8253586 }, + { url = "https://files.pythonhosted.org/packages/2f/2c/e084415775aac7016c3719fe7006cdb462582c6c99ac142f27303c56e243/matplotlib-3.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b072aac0c3ad563a2b3318124756cb6112157017f7431626600ecbe890df57a1", size = 8110715 }, + { url = "https://files.pythonhosted.org/packages/52/1b/233e3094b749df16e3e6cd5a44849fd33852e692ad009cf7de00cf58ddf6/matplotlib-3.10.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d52fd5b684d541b5a51fb276b2b97b010c75bee9aa392f96b4a07aeb491e33c7", size = 8669397 }, + { url = "https://files.pythonhosted.org/packages/e8/ec/03f9e003a798f907d9f772eed9b7c6a9775d5bd00648b643ebfb88e25414/matplotlib-3.10.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee7a09ae2f4676276f5a65bd9f2bd91b4f9fbaedf49f40267ce3f9b448de501f", size = 9508646 }, + { url = "https://files.pythonhosted.org/packages/91/e7/c051a7a386680c28487bca27d23b02d84f63e3d2a9b4d2fc478e6a42e37e/matplotlib-3.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ba6c3c9c067b83481d647af88b4e441d532acdb5ef22178a14935b0b881188f4", size = 9567424 }, + { url = "https://files.pythonhosted.org/packages/36/c2/24302e93ff431b8f4173ee1dd88976c8d80483cadbc5d3d777cef47b3a1c/matplotlib-3.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:07442d2692c9bd1cceaa4afb4bbe5b57b98a7599de4dabfcca92d3eea70f9ebe", size = 8107809 }, + { url = "https://files.pythonhosted.org/packages/0b/33/423ec6a668d375dad825197557ed8fbdb74d62b432c1ed8235465945475f/matplotlib-3.10.5-cp313-cp313-win_arm64.whl", hash = "sha256:48fe6d47380b68a37ccfcc94f009530e84d41f71f5dae7eda7c4a5a84aa0a674", size = 7978078 }, + { url = "https://files.pythonhosted.org/packages/51/17/521fc16ec766455c7bb52cc046550cf7652f6765ca8650ff120aa2d197b6/matplotlib-3.10.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b80eb8621331449fc519541a7461987f10afa4f9cfd91afcd2276ebe19bd56c", size = 8295590 }, + { url = "https://files.pythonhosted.org/packages/f8/12/23c28b2c21114c63999bae129fce7fd34515641c517ae48ce7b7dcd33458/matplotlib-3.10.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47a388908e469d6ca2a6015858fa924e0e8a2345a37125948d8e93a91c47933e", size = 8158518 }, + { url = "https://files.pythonhosted.org/packages/81/f8/aae4eb25e8e7190759f3cb91cbeaa344128159ac92bb6b409e24f8711f78/matplotlib-3.10.5-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8b6b49167d208358983ce26e43aa4196073b4702858670f2eb111f9a10652b4b", size = 8691815 }, + { url = "https://files.pythonhosted.org/packages/d0/ba/450c39ebdd486bd33a359fc17365ade46c6a96bf637bbb0df7824de2886c/matplotlib-3.10.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a8da0453a7fd8e3da114234ba70c5ba9ef0e98f190309ddfde0f089accd46ea", size = 9522814 }, + { url = "https://files.pythonhosted.org/packages/89/11/9c66f6a990e27bb9aa023f7988d2d5809cb98aa39c09cbf20fba75a542ef/matplotlib-3.10.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52c6573dfcb7726a9907b482cd5b92e6b5499b284ffacb04ffbfe06b3e568124", size = 9573917 }, + { url = "https://files.pythonhosted.org/packages/b3/69/8b49394de92569419e5e05e82e83df9b749a0ff550d07631ea96ed2eb35a/matplotlib-3.10.5-cp313-cp313t-win_amd64.whl", hash = "sha256:a23193db2e9d64ece69cac0c8231849db7dd77ce59c7b89948cf9d0ce655a3ce", size = 8181034 }, + { url = "https://files.pythonhosted.org/packages/47/23/82dc435bb98a2fc5c20dffcac8f0b083935ac28286413ed8835df40d0baa/matplotlib-3.10.5-cp313-cp313t-win_arm64.whl", hash = "sha256:56da3b102cf6da2776fef3e71cd96fcf22103a13594a18ac9a9b31314e0be154", size = 8023337 }, + { url = "https://files.pythonhosted.org/packages/ac/e0/26b6cfde31f5383503ee45dcb7e691d45dadf0b3f54639332b59316a97f8/matplotlib-3.10.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:96ef8f5a3696f20f55597ffa91c28e2e73088df25c555f8d4754931515512715", size = 8253591 }, + { url = "https://files.pythonhosted.org/packages/c1/89/98488c7ef7ea20ea659af7499628c240a608b337af4be2066d644cfd0a0f/matplotlib-3.10.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:77fab633e94b9da60512d4fa0213daeb76d5a7b05156840c4fd0399b4b818837", size = 8112566 }, + { url = "https://files.pythonhosted.org/packages/52/67/42294dfedc82aea55e1a767daf3263aacfb5a125f44ba189e685bab41b6f/matplotlib-3.10.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27f52634315e96b1debbfdc5c416592edcd9c4221bc2f520fd39c33db5d9f202", size = 9513281 }, + { url = "https://files.pythonhosted.org/packages/e7/68/f258239e0cf34c2cbc816781c7ab6fca768452e6bf1119aedd2bd4a882a3/matplotlib-3.10.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:525f6e28c485c769d1f07935b660c864de41c37fd716bfa64158ea646f7084bb", size = 9780873 }, + { url = "https://files.pythonhosted.org/packages/89/64/f4881554006bd12e4558bd66778bdd15d47b00a1f6c6e8b50f6208eda4b3/matplotlib-3.10.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1f5f3ec4c191253c5f2b7c07096a142c6a1c024d9f738247bfc8e3f9643fc975", size = 9568954 }, + { url = "https://files.pythonhosted.org/packages/06/f8/42779d39c3f757e1f012f2dda3319a89fb602bd2ef98ce8faf0281f4febd/matplotlib-3.10.5-cp314-cp314-win_amd64.whl", hash = "sha256:707f9c292c4cd4716f19ab8a1f93f26598222cd931e0cd98fbbb1c5994bf7667", size = 8237465 }, + { url = "https://files.pythonhosted.org/packages/cf/f8/153fd06b5160f0cd27c8b9dd797fcc9fb56ac6a0ebf3c1f765b6b68d3c8a/matplotlib-3.10.5-cp314-cp314-win_arm64.whl", hash = "sha256:21a95b9bf408178d372814de7baacd61c712a62cae560b5e6f35d791776f6516", size = 8108898 }, + { url = "https://files.pythonhosted.org/packages/9a/ee/c4b082a382a225fe0d2a73f1f57cf6f6f132308805b493a54c8641006238/matplotlib-3.10.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a6b310f95e1102a8c7c817ef17b60ee5d1851b8c71b63d9286b66b177963039e", size = 8295636 }, + { url = "https://files.pythonhosted.org/packages/30/73/2195fa2099718b21a20da82dfc753bf2af58d596b51aefe93e359dd5915a/matplotlib-3.10.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:94986a242747a0605cb3ff1cb98691c736f28a59f8ffe5175acaeb7397c49a5a", size = 8158575 }, + { url = "https://files.pythonhosted.org/packages/f6/e9/a08cdb34618a91fa08f75e6738541da5cacde7c307cea18ff10f0d03fcff/matplotlib-3.10.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ff10ea43288f0c8bab608a305dc6c918cc729d429c31dcbbecde3b9f4d5b569", size = 9522815 }, + { url = "https://files.pythonhosted.org/packages/4e/bb/34d8b7e0d1bb6d06ef45db01dfa560d5a67b1c40c0b998ce9ccde934bb09/matplotlib-3.10.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f6adb644c9d040ffb0d3434e440490a66cf73dbfa118a6f79cd7568431f7a012", size = 9783514 }, + { url = "https://files.pythonhosted.org/packages/12/09/d330d1e55dcca2e11b4d304cc5227f52e2512e46828d6249b88e0694176e/matplotlib-3.10.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4fa40a8f98428f789a9dcacd625f59b7bc4e3ef6c8c7c80187a7a709475cf592", size = 9573932 }, + { url = "https://files.pythonhosted.org/packages/eb/3b/f70258ac729aa004aca673800a53a2b0a26d49ca1df2eaa03289a1c40f81/matplotlib-3.10.5-cp314-cp314t-win_amd64.whl", hash = "sha256:95672a5d628b44207aab91ec20bf59c26da99de12b88f7e0b1fb0a84a86ff959", size = 8322003 }, + { url = "https://files.pythonhosted.org/packages/5b/60/3601f8ce6d76a7c81c7f25a0e15fde0d6b66226dd187aa6d2838e6374161/matplotlib-3.10.5-cp314-cp314t-win_arm64.whl", hash = "sha256:2efaf97d72629e74252e0b5e3c46813e9eeaa94e011ecf8084a971a31a97f40b", size = 8153849 }, + { url = "https://files.pythonhosted.org/packages/e4/eb/7d4c5de49eb78294e1a8e2be8a6ecff8b433e921b731412a56cd1abd3567/matplotlib-3.10.5-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b5fa2e941f77eb579005fb804026f9d0a1082276118d01cc6051d0d9626eaa7f", size = 8222360 }, + { url = "https://files.pythonhosted.org/packages/16/8a/e435db90927b66b16d69f8f009498775f4469f8de4d14b87856965e58eba/matplotlib-3.10.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1fc0d2a3241cdcb9daaca279204a3351ce9df3c0e7e621c7e04ec28aaacaca30", size = 8087462 }, + { url = "https://files.pythonhosted.org/packages/0b/dd/06c0e00064362f5647f318e00b435be2ff76a1bdced97c5eaf8347311fbe/matplotlib-3.10.5-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8dee65cb1424b7dc982fe87895b5613d4e691cc57117e8af840da0148ca6c1d7", size = 8659802 }, + { url = "https://files.pythonhosted.org/packages/dc/d6/e921be4e1a5f7aca5194e1f016cb67ec294548e530013251f630713e456d/matplotlib-3.10.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:160e125da27a749481eaddc0627962990f6029811dbeae23881833a011a0907f", size = 8233224 }, + { url = "https://files.pythonhosted.org/packages/ec/74/a2b9b04824b9c349c8f1b2d21d5af43fa7010039427f2b133a034cb09e59/matplotlib-3.10.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ac3d50760394d78a3c9be6b28318fe22b494c4fcf6407e8fd4794b538251899b", size = 8098539 }, + { url = "https://files.pythonhosted.org/packages/fc/66/cd29ebc7f6c0d2a15d216fb572573e8fc38bd5d6dec3bd9d7d904c0949f7/matplotlib-3.10.5-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c49465bf689c4d59d174d0c7795fb42a21d4244d11d70e52b8011987367ac61", size = 8672192 }, ] [[package]] name = "mdurl" version = "0.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, ] [[package]] name = "mpmath" version = "1.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106 } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198 }, ] [[package]] name = "msgpack" version = "1.0.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c2/d5/5662032db1571110b5b51647aed4b56dfbd01bfae789fa566a2be1f385d1/msgpack-1.0.7.tar.gz", hash = "sha256:572efc93db7a4d27e404501975ca6d2d9775705c2d922390d878fcf768d92c87", size = 166311, upload-time = "2023-09-28T13:20:36.726Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/d5/5662032db1571110b5b51647aed4b56dfbd01bfae789fa566a2be1f385d1/msgpack-1.0.7.tar.gz", hash = "sha256:572efc93db7a4d27e404501975ca6d2d9775705c2d922390d878fcf768d92c87", size = 166311 } wheels = [ - { url = "https://files.pythonhosted.org/packages/41/3a/2e2e902afcd751738e38d88af976fc4010b16e8e821945f4cbf32f75f9c3/msgpack-1.0.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04ad6069c86e531682f9e1e71b71c1c3937d6014a7c3e9edd2aa81ad58842862", size = 304827, upload-time = "2023-09-28T13:18:30.258Z" }, - { url = "https://files.pythonhosted.org/packages/86/a6/490792a524a82e855bdf3885ecb73d7b3a0b17744b3cf4a40aea13ceca38/msgpack-1.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cca1b62fe70d761a282496b96a5e51c44c213e410a964bdffe0928e611368329", size = 234959, upload-time = "2023-09-28T13:18:32.146Z" }, - { url = "https://files.pythonhosted.org/packages/ad/72/d39ed43bfb2ec6968d768318477adb90c474bdc59b2437170c6697ee4115/msgpack-1.0.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e50ebce52f41370707f1e21a59514e3375e3edd6e1832f5e5235237db933c98b", size = 231970, upload-time = "2023-09-28T13:18:34.134Z" }, - { url = "https://files.pythonhosted.org/packages/a2/90/2d769e693654f036acfb462b54dacb3ae345699999897ca34f6bd9534fe9/msgpack-1.0.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b4f35de6a304b5533c238bee86b670b75b03d31b7797929caa7a624b5dda6", size = 522440, upload-time = "2023-09-28T13:18:35.866Z" }, - { url = "https://files.pythonhosted.org/packages/46/95/d0440400485eab1bf50f1efe5118967b539f3191d994c3dfc220657594cd/msgpack-1.0.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28efb066cde83c479dfe5a48141a53bc7e5f13f785b92ddde336c716663039ee", size = 530797, upload-time = "2023-09-28T13:18:37.653Z" }, - { url = "https://files.pythonhosted.org/packages/76/33/35df717bc095c6e938b3c65ed117b95048abc24d1614427685123fb2f0af/msgpack-1.0.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cb14ce54d9b857be9591ac364cb08dc2d6a5c4318c1182cb1d02274029d590d", size = 520372, upload-time = "2023-09-28T13:18:39.685Z" }, - { url = "https://files.pythonhosted.org/packages/af/d1/abbdd58a43827fbec5d98427a7a535c620890289b9d927154465313d6967/msgpack-1.0.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b573a43ef7c368ba4ea06050a957c2a7550f729c31f11dd616d2ac4aba99888d", size = 527287, upload-time = "2023-09-28T13:18:41.051Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ac/66625b05091b97ca2c7418eb2d2af152f033d969519f9315556a4ed800fe/msgpack-1.0.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ccf9a39706b604d884d2cb1e27fe973bc55f2890c52f38df742bc1d79ab9f5e1", size = 560715, upload-time = "2023-09-28T13:18:42.883Z" }, - { url = "https://files.pythonhosted.org/packages/de/4e/a0e8611f94bac32d2c1c4ad05bb1c0ae61132e3398e0b44a93e6d7830968/msgpack-1.0.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cb70766519500281815dfd7a87d3a178acf7ce95390544b8c90587d76b227681", size = 532614, upload-time = "2023-09-28T13:18:44.679Z" }, - { url = "https://files.pythonhosted.org/packages/9b/07/0b3f089684ca330602b2994248eda2898a7232e4b63882b9271164ef672e/msgpack-1.0.7-cp310-cp310-win32.whl", hash = "sha256:b610ff0f24e9f11c9ae653c67ff8cc03c075131401b3e5ef4b82570d1728f8a9", size = 216340, upload-time = "2023-09-28T13:18:46.588Z" }, - { url = "https://files.pythonhosted.org/packages/4b/14/c62fbc8dff118f1558e43b9469d56a1f37bbb35febadc3163efaedd01500/msgpack-1.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:a40821a89dc373d6427e2b44b572efc36a2778d3f543299e2f24eb1a5de65415", size = 222828, upload-time = "2023-09-28T13:18:47.875Z" }, - { url = "https://files.pythonhosted.org/packages/f9/b3/309de40dc7406b7f3492332c5ee2b492a593c2a9bb97ea48ebf2f5279999/msgpack-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:576eb384292b139821c41995523654ad82d1916da6a60cff129c715a6223ea84", size = 305096, upload-time = "2023-09-28T13:18:49.678Z" }, - { url = "https://files.pythonhosted.org/packages/15/56/a677cd761a2cefb2e3ffe7e684633294dccb161d78e8ea6da9277e45b4a2/msgpack-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:730076207cb816138cf1af7f7237b208340a2c5e749707457d70705715c93b93", size = 235210, upload-time = "2023-09-28T13:18:51.039Z" }, - { url = "https://files.pythonhosted.org/packages/f5/4e/1ab4a982cbd90f988e49f849fc1212f2c04a59870c59daabf8950617e2aa/msgpack-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:85765fdf4b27eb5086f05ac0491090fc76f4f2b28e09d9350c31aac25a5aaff8", size = 231952, upload-time = "2023-09-28T13:18:52.871Z" }, - { url = "https://files.pythonhosted.org/packages/6d/74/bd02044eb628c7361ad2bd8c1a6147af5c6c2bbceb77b3b1da20f4a8a9c5/msgpack-1.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3476fae43db72bd11f29a5147ae2f3cb22e2f1a91d575ef130d2bf49afd21c46", size = 549511, upload-time = "2023-09-28T13:18:54.422Z" }, - { url = "https://files.pythonhosted.org/packages/df/09/dee50913ba5cc047f7fd7162f09453a676e7935c84b3bf3a398e12108677/msgpack-1.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d4c80667de2e36970ebf74f42d1088cc9ee7ef5f4e8c35eee1b40eafd33ca5b", size = 557980, upload-time = "2023-09-28T13:18:56.058Z" }, - { url = "https://files.pythonhosted.org/packages/26/a5/78a7d87f5f8ffe4c32167afa15d4957db649bab4822f909d8d765339bbab/msgpack-1.0.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b0bf0effb196ed76b7ad883848143427a73c355ae8e569fa538365064188b8e", size = 545547, upload-time = "2023-09-28T13:18:57.396Z" }, - { url = "https://files.pythonhosted.org/packages/d4/53/698c10913947f97f6fe7faad86a34e6aa1b66cea2df6f99105856bd346d9/msgpack-1.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f9a7c509542db4eceed3dcf21ee5267ab565a83555c9b88a8109dcecc4709002", size = 554669, upload-time = "2023-09-28T13:18:58.957Z" }, - { url = "https://files.pythonhosted.org/packages/f5/3f/9730c6cb574b15d349b80cd8523a7df4b82058528339f952ea1c32ac8a10/msgpack-1.0.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:84b0daf226913133f899ea9b30618722d45feffa67e4fe867b0b5ae83a34060c", size = 583353, upload-time = "2023-09-28T13:19:01.186Z" }, - { url = "https://files.pythonhosted.org/packages/4c/bc/dc184d943692671149848438fb3bed3a3de288ce7998cb91bc98f40f201b/msgpack-1.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ec79ff6159dffcc30853b2ad612ed572af86c92b5168aa3fc01a67b0fa40665e", size = 557455, upload-time = "2023-09-28T13:19:03.201Z" }, - { url = "https://files.pythonhosted.org/packages/cf/7b/1bc69d4a56c8d2f4f2dfbe4722d40344af9a85b6fb3b09cfb350ba6a42f6/msgpack-1.0.7-cp311-cp311-win32.whl", hash = "sha256:3e7bf4442b310ff154b7bb9d81eb2c016b7d597e364f97d72b1acc3817a0fdc1", size = 216367, upload-time = "2023-09-28T13:19:04.554Z" }, - { url = "https://files.pythonhosted.org/packages/b4/3d/c8dd23050eefa3d9b9c5b8329ed3308c2f2f80f65825e9ea4b7fa621cdab/msgpack-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:3f0c8c6dfa6605ab8ff0611995ee30d4f9fcff89966cf562733b4008a3d60d82", size = 222860, upload-time = "2023-09-28T13:19:06.397Z" }, - { url = "https://files.pythonhosted.org/packages/d7/47/20dff6b4512cf3575550c8801bc53fe7d540f4efef9c5c37af51760fcdcf/msgpack-1.0.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f0936e08e0003f66bfd97e74ee530427707297b0d0361247e9b4f59ab78ddc8b", size = 305759, upload-time = "2023-09-28T13:19:08.148Z" }, - { url = "https://files.pythonhosted.org/packages/6f/8a/34f1726d2c9feccec3d946776e9bce8f20ae09d8b91899fc20b296c942af/msgpack-1.0.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98bbd754a422a0b123c66a4c341de0474cad4a5c10c164ceed6ea090f3563db4", size = 235330, upload-time = "2023-09-28T13:19:09.417Z" }, - { url = "https://files.pythonhosted.org/packages/9c/f6/e64c72577d6953789c3cb051b059a4b56317056b3c65013952338ed8a34e/msgpack-1.0.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b291f0ee7961a597cbbcc77709374087fa2a9afe7bdb6a40dbbd9b127e79afee", size = 232537, upload-time = "2023-09-28T13:19:10.898Z" }, - { url = "https://files.pythonhosted.org/packages/89/75/1ed3a96e12941873fd957e016cc40c0c178861a872bd45e75b9a188eb422/msgpack-1.0.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebbbba226f0a108a7366bf4b59bf0f30a12fd5e75100c630267d94d7f0ad20e5", size = 546561, upload-time = "2023-09-28T13:19:12.779Z" }, - { url = "https://files.pythonhosted.org/packages/e5/0a/c6a1390f9c6a31da0fecbbfdb86b1cb39ad302d9e24f9cca3d9e14c364f0/msgpack-1.0.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e2d69948e4132813b8d1131f29f9101bc2c915f26089a6d632001a5c1349672", size = 559009, upload-time = "2023-09-28T13:19:14.373Z" }, - { url = "https://files.pythonhosted.org/packages/a5/74/99f6077754665613ea1f37b3d91c10129f6976b7721ab4d0973023808e5a/msgpack-1.0.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdf38ba2d393c7911ae989c3bbba510ebbcdf4ecbdbfec36272abe350c454075", size = 543882, upload-time = "2023-09-28T13:19:16.277Z" }, - { url = "https://files.pythonhosted.org/packages/9c/7e/dc0dc8de2bf27743b31691149258f9b1bd4bf3c44c105df3df9b97081cd1/msgpack-1.0.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:993584fc821c58d5993521bfdcd31a4adf025c7d745bbd4d12ccfecf695af5ba", size = 546949, upload-time = "2023-09-28T13:19:18.114Z" }, - { url = "https://files.pythonhosted.org/packages/78/61/91bae9474def032f6c333d62889bbeda9e1554c6b123375ceeb1767efd78/msgpack-1.0.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:52700dc63a4676669b341ba33520f4d6e43d3ca58d422e22ba66d1736b0a6e4c", size = 579836, upload-time = "2023-09-28T13:19:19.729Z" }, - { url = "https://files.pythonhosted.org/packages/5d/4d/d98592099d4f18945f89cf3e634dc0cb128bb33b1b93f85a84173d35e181/msgpack-1.0.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e45ae4927759289c30ccba8d9fdce62bb414977ba158286b5ddaf8df2cddb5c5", size = 556587, upload-time = "2023-09-28T13:19:21.666Z" }, - { url = "https://files.pythonhosted.org/packages/5e/44/6556ffe169bf2c0e974e2ea25fb82a7e55ebcf52a81b03a5e01820de5f84/msgpack-1.0.7-cp312-cp312-win32.whl", hash = "sha256:27dcd6f46a21c18fa5e5deed92a43d4554e3df8d8ca5a47bf0615d6a5f39dbc9", size = 216509, upload-time = "2023-09-28T13:19:23.161Z" }, - { url = "https://files.pythonhosted.org/packages/dc/c1/63903f30d51d165e132e5221a2a4a1bbfab7508b68131c871d70bffac78a/msgpack-1.0.7-cp312-cp312-win_amd64.whl", hash = "sha256:7687e22a31e976a0e7fc99c2f4d11ca45eff652a81eb8c8085e9609298916dcf", size = 223287, upload-time = "2023-09-28T13:19:25.097Z" }, + { url = "https://files.pythonhosted.org/packages/41/3a/2e2e902afcd751738e38d88af976fc4010b16e8e821945f4cbf32f75f9c3/msgpack-1.0.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04ad6069c86e531682f9e1e71b71c1c3937d6014a7c3e9edd2aa81ad58842862", size = 304827 }, + { url = "https://files.pythonhosted.org/packages/86/a6/490792a524a82e855bdf3885ecb73d7b3a0b17744b3cf4a40aea13ceca38/msgpack-1.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cca1b62fe70d761a282496b96a5e51c44c213e410a964bdffe0928e611368329", size = 234959 }, + { url = "https://files.pythonhosted.org/packages/ad/72/d39ed43bfb2ec6968d768318477adb90c474bdc59b2437170c6697ee4115/msgpack-1.0.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e50ebce52f41370707f1e21a59514e3375e3edd6e1832f5e5235237db933c98b", size = 231970 }, + { url = "https://files.pythonhosted.org/packages/a2/90/2d769e693654f036acfb462b54dacb3ae345699999897ca34f6bd9534fe9/msgpack-1.0.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b4f35de6a304b5533c238bee86b670b75b03d31b7797929caa7a624b5dda6", size = 522440 }, + { url = "https://files.pythonhosted.org/packages/46/95/d0440400485eab1bf50f1efe5118967b539f3191d994c3dfc220657594cd/msgpack-1.0.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28efb066cde83c479dfe5a48141a53bc7e5f13f785b92ddde336c716663039ee", size = 530797 }, + { url = "https://files.pythonhosted.org/packages/76/33/35df717bc095c6e938b3c65ed117b95048abc24d1614427685123fb2f0af/msgpack-1.0.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cb14ce54d9b857be9591ac364cb08dc2d6a5c4318c1182cb1d02274029d590d", size = 520372 }, + { url = "https://files.pythonhosted.org/packages/af/d1/abbdd58a43827fbec5d98427a7a535c620890289b9d927154465313d6967/msgpack-1.0.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b573a43ef7c368ba4ea06050a957c2a7550f729c31f11dd616d2ac4aba99888d", size = 527287 }, + { url = "https://files.pythonhosted.org/packages/0c/ac/66625b05091b97ca2c7418eb2d2af152f033d969519f9315556a4ed800fe/msgpack-1.0.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ccf9a39706b604d884d2cb1e27fe973bc55f2890c52f38df742bc1d79ab9f5e1", size = 560715 }, + { url = "https://files.pythonhosted.org/packages/de/4e/a0e8611f94bac32d2c1c4ad05bb1c0ae61132e3398e0b44a93e6d7830968/msgpack-1.0.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cb70766519500281815dfd7a87d3a178acf7ce95390544b8c90587d76b227681", size = 532614 }, + { url = "https://files.pythonhosted.org/packages/9b/07/0b3f089684ca330602b2994248eda2898a7232e4b63882b9271164ef672e/msgpack-1.0.7-cp310-cp310-win32.whl", hash = "sha256:b610ff0f24e9f11c9ae653c67ff8cc03c075131401b3e5ef4b82570d1728f8a9", size = 216340 }, + { url = "https://files.pythonhosted.org/packages/4b/14/c62fbc8dff118f1558e43b9469d56a1f37bbb35febadc3163efaedd01500/msgpack-1.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:a40821a89dc373d6427e2b44b572efc36a2778d3f543299e2f24eb1a5de65415", size = 222828 }, + { url = "https://files.pythonhosted.org/packages/f9/b3/309de40dc7406b7f3492332c5ee2b492a593c2a9bb97ea48ebf2f5279999/msgpack-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:576eb384292b139821c41995523654ad82d1916da6a60cff129c715a6223ea84", size = 305096 }, + { url = "https://files.pythonhosted.org/packages/15/56/a677cd761a2cefb2e3ffe7e684633294dccb161d78e8ea6da9277e45b4a2/msgpack-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:730076207cb816138cf1af7f7237b208340a2c5e749707457d70705715c93b93", size = 235210 }, + { url = "https://files.pythonhosted.org/packages/f5/4e/1ab4a982cbd90f988e49f849fc1212f2c04a59870c59daabf8950617e2aa/msgpack-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:85765fdf4b27eb5086f05ac0491090fc76f4f2b28e09d9350c31aac25a5aaff8", size = 231952 }, + { url = "https://files.pythonhosted.org/packages/6d/74/bd02044eb628c7361ad2bd8c1a6147af5c6c2bbceb77b3b1da20f4a8a9c5/msgpack-1.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3476fae43db72bd11f29a5147ae2f3cb22e2f1a91d575ef130d2bf49afd21c46", size = 549511 }, + { url = "https://files.pythonhosted.org/packages/df/09/dee50913ba5cc047f7fd7162f09453a676e7935c84b3bf3a398e12108677/msgpack-1.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d4c80667de2e36970ebf74f42d1088cc9ee7ef5f4e8c35eee1b40eafd33ca5b", size = 557980 }, + { url = "https://files.pythonhosted.org/packages/26/a5/78a7d87f5f8ffe4c32167afa15d4957db649bab4822f909d8d765339bbab/msgpack-1.0.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b0bf0effb196ed76b7ad883848143427a73c355ae8e569fa538365064188b8e", size = 545547 }, + { url = "https://files.pythonhosted.org/packages/d4/53/698c10913947f97f6fe7faad86a34e6aa1b66cea2df6f99105856bd346d9/msgpack-1.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f9a7c509542db4eceed3dcf21ee5267ab565a83555c9b88a8109dcecc4709002", size = 554669 }, + { url = "https://files.pythonhosted.org/packages/f5/3f/9730c6cb574b15d349b80cd8523a7df4b82058528339f952ea1c32ac8a10/msgpack-1.0.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:84b0daf226913133f899ea9b30618722d45feffa67e4fe867b0b5ae83a34060c", size = 583353 }, + { url = "https://files.pythonhosted.org/packages/4c/bc/dc184d943692671149848438fb3bed3a3de288ce7998cb91bc98f40f201b/msgpack-1.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ec79ff6159dffcc30853b2ad612ed572af86c92b5168aa3fc01a67b0fa40665e", size = 557455 }, + { url = "https://files.pythonhosted.org/packages/cf/7b/1bc69d4a56c8d2f4f2dfbe4722d40344af9a85b6fb3b09cfb350ba6a42f6/msgpack-1.0.7-cp311-cp311-win32.whl", hash = "sha256:3e7bf4442b310ff154b7bb9d81eb2c016b7d597e364f97d72b1acc3817a0fdc1", size = 216367 }, + { url = "https://files.pythonhosted.org/packages/b4/3d/c8dd23050eefa3d9b9c5b8329ed3308c2f2f80f65825e9ea4b7fa621cdab/msgpack-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:3f0c8c6dfa6605ab8ff0611995ee30d4f9fcff89966cf562733b4008a3d60d82", size = 222860 }, + { url = "https://files.pythonhosted.org/packages/d7/47/20dff6b4512cf3575550c8801bc53fe7d540f4efef9c5c37af51760fcdcf/msgpack-1.0.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f0936e08e0003f66bfd97e74ee530427707297b0d0361247e9b4f59ab78ddc8b", size = 305759 }, + { url = "https://files.pythonhosted.org/packages/6f/8a/34f1726d2c9feccec3d946776e9bce8f20ae09d8b91899fc20b296c942af/msgpack-1.0.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98bbd754a422a0b123c66a4c341de0474cad4a5c10c164ceed6ea090f3563db4", size = 235330 }, + { url = "https://files.pythonhosted.org/packages/9c/f6/e64c72577d6953789c3cb051b059a4b56317056b3c65013952338ed8a34e/msgpack-1.0.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b291f0ee7961a597cbbcc77709374087fa2a9afe7bdb6a40dbbd9b127e79afee", size = 232537 }, + { url = "https://files.pythonhosted.org/packages/89/75/1ed3a96e12941873fd957e016cc40c0c178861a872bd45e75b9a188eb422/msgpack-1.0.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebbbba226f0a108a7366bf4b59bf0f30a12fd5e75100c630267d94d7f0ad20e5", size = 546561 }, + { url = "https://files.pythonhosted.org/packages/e5/0a/c6a1390f9c6a31da0fecbbfdb86b1cb39ad302d9e24f9cca3d9e14c364f0/msgpack-1.0.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e2d69948e4132813b8d1131f29f9101bc2c915f26089a6d632001a5c1349672", size = 559009 }, + { url = "https://files.pythonhosted.org/packages/a5/74/99f6077754665613ea1f37b3d91c10129f6976b7721ab4d0973023808e5a/msgpack-1.0.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdf38ba2d393c7911ae989c3bbba510ebbcdf4ecbdbfec36272abe350c454075", size = 543882 }, + { url = "https://files.pythonhosted.org/packages/9c/7e/dc0dc8de2bf27743b31691149258f9b1bd4bf3c44c105df3df9b97081cd1/msgpack-1.0.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:993584fc821c58d5993521bfdcd31a4adf025c7d745bbd4d12ccfecf695af5ba", size = 546949 }, + { url = "https://files.pythonhosted.org/packages/78/61/91bae9474def032f6c333d62889bbeda9e1554c6b123375ceeb1767efd78/msgpack-1.0.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:52700dc63a4676669b341ba33520f4d6e43d3ca58d422e22ba66d1736b0a6e4c", size = 579836 }, + { url = "https://files.pythonhosted.org/packages/5d/4d/d98592099d4f18945f89cf3e634dc0cb128bb33b1b93f85a84173d35e181/msgpack-1.0.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e45ae4927759289c30ccba8d9fdce62bb414977ba158286b5ddaf8df2cddb5c5", size = 556587 }, + { url = "https://files.pythonhosted.org/packages/5e/44/6556ffe169bf2c0e974e2ea25fb82a7e55ebcf52a81b03a5e01820de5f84/msgpack-1.0.7-cp312-cp312-win32.whl", hash = "sha256:27dcd6f46a21c18fa5e5deed92a43d4554e3df8d8ca5a47bf0615d6a5f39dbc9", size = 216509 }, + { url = "https://files.pythonhosted.org/packages/dc/c1/63903f30d51d165e132e5221a2a4a1bbfab7508b68131c871d70bffac78a/msgpack-1.0.7-cp312-cp312-win_amd64.whl", hash = "sha256:7687e22a31e976a0e7fc99c2f4d11ca45eff652a81eb8c8085e9609298916dcf", size = 223287 }, ] [[package]] @@ -1660,89 +1653,102 @@ dependencies = [ { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/22/ea637422dedf0bf36f3ef238eab4e455e2a0dcc3082b5cc067615347ab8e/mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01", size = 3352570, upload-time = "2025-07-31T07:54:19.204Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/22/ea637422dedf0bf36f3ef238eab4e455e2a0dcc3082b5cc067615347ab8e/mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01", size = 3352570 } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/a9/3d7aa83955617cdf02f94e50aab5c830d205cfa4320cf124ff64acce3a8e/mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972", size = 11003299, upload-time = "2025-07-31T07:54:06.425Z" }, - { url = "https://files.pythonhosted.org/packages/83/e8/72e62ff837dd5caaac2b4a5c07ce769c8e808a00a65e5d8f94ea9c6f20ab/mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7", size = 10125451, upload-time = "2025-07-31T07:53:52.974Z" }, - { url = "https://files.pythonhosted.org/packages/7d/10/f3f3543f6448db11881776f26a0ed079865926b0c841818ee22de2c6bbab/mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df", size = 11916211, upload-time = "2025-07-31T07:53:18.879Z" }, - { url = "https://files.pythonhosted.org/packages/06/bf/63e83ed551282d67bb3f7fea2cd5561b08d2bb6eb287c096539feb5ddbc5/mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390", size = 12652687, upload-time = "2025-07-31T07:53:30.544Z" }, - { url = "https://files.pythonhosted.org/packages/69/66/68f2eeef11facf597143e85b694a161868b3b006a5fbad50e09ea117ef24/mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94", size = 12896322, upload-time = "2025-07-31T07:53:50.74Z" }, - { url = "https://files.pythonhosted.org/packages/a3/87/8e3e9c2c8bd0d7e071a89c71be28ad088aaecbadf0454f46a540bda7bca6/mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b", size = 9507962, upload-time = "2025-07-31T07:53:08.431Z" }, - { url = "https://files.pythonhosted.org/packages/46/cf/eadc80c4e0a70db1c08921dcc220357ba8ab2faecb4392e3cebeb10edbfa/mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58", size = 10921009, upload-time = "2025-07-31T07:53:23.037Z" }, - { url = "https://files.pythonhosted.org/packages/5d/c1/c869d8c067829ad30d9bdae051046561552516cfb3a14f7f0347b7d973ee/mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5", size = 10047482, upload-time = "2025-07-31T07:53:26.151Z" }, - { url = "https://files.pythonhosted.org/packages/98/b9/803672bab3fe03cee2e14786ca056efda4bb511ea02dadcedde6176d06d0/mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd", size = 11832883, upload-time = "2025-07-31T07:53:47.948Z" }, - { url = "https://files.pythonhosted.org/packages/88/fb/fcdac695beca66800918c18697b48833a9a6701de288452b6715a98cfee1/mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b", size = 12566215, upload-time = "2025-07-31T07:54:04.031Z" }, - { url = "https://files.pythonhosted.org/packages/7f/37/a932da3d3dace99ee8eb2043b6ab03b6768c36eb29a02f98f46c18c0da0e/mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5", size = 12751956, upload-time = "2025-07-31T07:53:36.263Z" }, - { url = "https://files.pythonhosted.org/packages/8c/cf/6438a429e0f2f5cab8bc83e53dbebfa666476f40ee322e13cac5e64b79e7/mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b", size = 9507307, upload-time = "2025-07-31T07:53:59.734Z" }, - { url = "https://files.pythonhosted.org/packages/17/a2/7034d0d61af8098ec47902108553122baa0f438df8a713be860f7407c9e6/mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb", size = 11086295, upload-time = "2025-07-31T07:53:28.124Z" }, - { url = "https://files.pythonhosted.org/packages/14/1f/19e7e44b594d4b12f6ba8064dbe136505cec813549ca3e5191e40b1d3cc2/mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403", size = 10112355, upload-time = "2025-07-31T07:53:21.121Z" }, - { url = "https://files.pythonhosted.org/packages/5b/69/baa33927e29e6b4c55d798a9d44db5d394072eef2bdc18c3e2048c9ed1e9/mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056", size = 11875285, upload-time = "2025-07-31T07:53:55.293Z" }, - { url = "https://files.pythonhosted.org/packages/90/13/f3a89c76b0a41e19490b01e7069713a30949d9a6c147289ee1521bcea245/mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341", size = 12737895, upload-time = "2025-07-31T07:53:43.623Z" }, - { url = "https://files.pythonhosted.org/packages/23/a1/c4ee79ac484241301564072e6476c5a5be2590bc2e7bfd28220033d2ef8f/mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb", size = 12931025, upload-time = "2025-07-31T07:54:17.125Z" }, - { url = "https://files.pythonhosted.org/packages/89/b8/7409477be7919a0608900e6320b155c72caab4fef46427c5cc75f85edadd/mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19", size = 9584664, upload-time = "2025-07-31T07:54:12.842Z" }, - { url = "https://files.pythonhosted.org/packages/5b/82/aec2fc9b9b149f372850291827537a508d6c4d3664b1750a324b91f71355/mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7", size = 11075338, upload-time = "2025-07-31T07:53:38.873Z" }, - { url = "https://files.pythonhosted.org/packages/07/ac/ee93fbde9d2242657128af8c86f5d917cd2887584cf948a8e3663d0cd737/mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81", size = 10113066, upload-time = "2025-07-31T07:54:14.707Z" }, - { url = "https://files.pythonhosted.org/packages/5a/68/946a1e0be93f17f7caa56c45844ec691ca153ee8b62f21eddda336a2d203/mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6", size = 11875473, upload-time = "2025-07-31T07:53:14.504Z" }, - { url = "https://files.pythonhosted.org/packages/9f/0f/478b4dce1cb4f43cf0f0d00fba3030b21ca04a01b74d1cd272a528cf446f/mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849", size = 12744296, upload-time = "2025-07-31T07:53:03.896Z" }, - { url = "https://files.pythonhosted.org/packages/ca/70/afa5850176379d1b303f992a828de95fc14487429a7139a4e0bdd17a8279/mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14", size = 12914657, upload-time = "2025-07-31T07:54:08.576Z" }, - { url = "https://files.pythonhosted.org/packages/53/f9/4a83e1c856a3d9c8f6edaa4749a4864ee98486e9b9dbfbc93842891029c2/mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a", size = 9593320, upload-time = "2025-07-31T07:53:01.341Z" }, - { url = "https://files.pythonhosted.org/packages/38/56/79c2fac86da57c7d8c48622a05873eaab40b905096c33597462713f5af90/mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733", size = 11040037, upload-time = "2025-07-31T07:54:10.942Z" }, - { url = "https://files.pythonhosted.org/packages/4d/c3/adabe6ff53638e3cad19e3547268482408323b1e68bf082c9119000cd049/mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd", size = 10131550, upload-time = "2025-07-31T07:53:41.307Z" }, - { url = "https://files.pythonhosted.org/packages/b8/c5/2e234c22c3bdeb23a7817af57a58865a39753bde52c74e2c661ee0cfc640/mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0", size = 11872963, upload-time = "2025-07-31T07:53:16.878Z" }, - { url = "https://files.pythonhosted.org/packages/ab/26/c13c130f35ca8caa5f2ceab68a247775648fdcd6c9a18f158825f2bc2410/mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a", size = 12710189, upload-time = "2025-07-31T07:54:01.962Z" }, - { url = "https://files.pythonhosted.org/packages/82/df/c7d79d09f6de8383fe800521d066d877e54d30b4fb94281c262be2df84ef/mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91", size = 12900322, upload-time = "2025-07-31T07:53:10.551Z" }, - { url = "https://files.pythonhosted.org/packages/b8/98/3d5a48978b4f708c55ae832619addc66d677f6dc59f3ebad71bae8285ca6/mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed", size = 9751879, upload-time = "2025-07-31T07:52:56.683Z" }, - { url = "https://files.pythonhosted.org/packages/1d/f3/8fcd2af0f5b806f6cf463efaffd3c9548a28f84220493ecd38d127b6b66d/mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9", size = 2283411, upload-time = "2025-07-31T07:53:24.664Z" }, + { url = "https://files.pythonhosted.org/packages/77/a9/3d7aa83955617cdf02f94e50aab5c830d205cfa4320cf124ff64acce3a8e/mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972", size = 11003299 }, + { url = "https://files.pythonhosted.org/packages/83/e8/72e62ff837dd5caaac2b4a5c07ce769c8e808a00a65e5d8f94ea9c6f20ab/mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7", size = 10125451 }, + { url = "https://files.pythonhosted.org/packages/7d/10/f3f3543f6448db11881776f26a0ed079865926b0c841818ee22de2c6bbab/mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df", size = 11916211 }, + { url = "https://files.pythonhosted.org/packages/06/bf/63e83ed551282d67bb3f7fea2cd5561b08d2bb6eb287c096539feb5ddbc5/mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390", size = 12652687 }, + { url = "https://files.pythonhosted.org/packages/69/66/68f2eeef11facf597143e85b694a161868b3b006a5fbad50e09ea117ef24/mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94", size = 12896322 }, + { url = "https://files.pythonhosted.org/packages/a3/87/8e3e9c2c8bd0d7e071a89c71be28ad088aaecbadf0454f46a540bda7bca6/mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b", size = 9507962 }, + { url = "https://files.pythonhosted.org/packages/46/cf/eadc80c4e0a70db1c08921dcc220357ba8ab2faecb4392e3cebeb10edbfa/mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58", size = 10921009 }, + { url = "https://files.pythonhosted.org/packages/5d/c1/c869d8c067829ad30d9bdae051046561552516cfb3a14f7f0347b7d973ee/mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5", size = 10047482 }, + { url = "https://files.pythonhosted.org/packages/98/b9/803672bab3fe03cee2e14786ca056efda4bb511ea02dadcedde6176d06d0/mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd", size = 11832883 }, + { url = "https://files.pythonhosted.org/packages/88/fb/fcdac695beca66800918c18697b48833a9a6701de288452b6715a98cfee1/mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b", size = 12566215 }, + { url = "https://files.pythonhosted.org/packages/7f/37/a932da3d3dace99ee8eb2043b6ab03b6768c36eb29a02f98f46c18c0da0e/mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5", size = 12751956 }, + { url = "https://files.pythonhosted.org/packages/8c/cf/6438a429e0f2f5cab8bc83e53dbebfa666476f40ee322e13cac5e64b79e7/mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b", size = 9507307 }, + { url = "https://files.pythonhosted.org/packages/17/a2/7034d0d61af8098ec47902108553122baa0f438df8a713be860f7407c9e6/mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb", size = 11086295 }, + { url = "https://files.pythonhosted.org/packages/14/1f/19e7e44b594d4b12f6ba8064dbe136505cec813549ca3e5191e40b1d3cc2/mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403", size = 10112355 }, + { url = "https://files.pythonhosted.org/packages/5b/69/baa33927e29e6b4c55d798a9d44db5d394072eef2bdc18c3e2048c9ed1e9/mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056", size = 11875285 }, + { url = "https://files.pythonhosted.org/packages/90/13/f3a89c76b0a41e19490b01e7069713a30949d9a6c147289ee1521bcea245/mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341", size = 12737895 }, + { url = "https://files.pythonhosted.org/packages/23/a1/c4ee79ac484241301564072e6476c5a5be2590bc2e7bfd28220033d2ef8f/mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb", size = 12931025 }, + { url = "https://files.pythonhosted.org/packages/89/b8/7409477be7919a0608900e6320b155c72caab4fef46427c5cc75f85edadd/mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19", size = 9584664 }, + { url = "https://files.pythonhosted.org/packages/5b/82/aec2fc9b9b149f372850291827537a508d6c4d3664b1750a324b91f71355/mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7", size = 11075338 }, + { url = "https://files.pythonhosted.org/packages/07/ac/ee93fbde9d2242657128af8c86f5d917cd2887584cf948a8e3663d0cd737/mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81", size = 10113066 }, + { url = "https://files.pythonhosted.org/packages/5a/68/946a1e0be93f17f7caa56c45844ec691ca153ee8b62f21eddda336a2d203/mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6", size = 11875473 }, + { url = "https://files.pythonhosted.org/packages/9f/0f/478b4dce1cb4f43cf0f0d00fba3030b21ca04a01b74d1cd272a528cf446f/mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849", size = 12744296 }, + { url = "https://files.pythonhosted.org/packages/ca/70/afa5850176379d1b303f992a828de95fc14487429a7139a4e0bdd17a8279/mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14", size = 12914657 }, + { url = "https://files.pythonhosted.org/packages/53/f9/4a83e1c856a3d9c8f6edaa4749a4864ee98486e9b9dbfbc93842891029c2/mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a", size = 9593320 }, + { url = "https://files.pythonhosted.org/packages/38/56/79c2fac86da57c7d8c48622a05873eaab40b905096c33597462713f5af90/mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733", size = 11040037 }, + { url = "https://files.pythonhosted.org/packages/4d/c3/adabe6ff53638e3cad19e3547268482408323b1e68bf082c9119000cd049/mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd", size = 10131550 }, + { url = "https://files.pythonhosted.org/packages/b8/c5/2e234c22c3bdeb23a7817af57a58865a39753bde52c74e2c661ee0cfc640/mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0", size = 11872963 }, + { url = "https://files.pythonhosted.org/packages/ab/26/c13c130f35ca8caa5f2ceab68a247775648fdcd6c9a18f158825f2bc2410/mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a", size = 12710189 }, + { url = "https://files.pythonhosted.org/packages/82/df/c7d79d09f6de8383fe800521d066d877e54d30b4fb94281c262be2df84ef/mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91", size = 12900322 }, + { url = "https://files.pythonhosted.org/packages/b8/98/3d5a48978b4f708c55ae832619addc66d677f6dc59f3ebad71bae8285ca6/mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed", size = 9751879 }, + { url = "https://files.pythonhosted.org/packages/1d/f3/8fcd2af0f5b806f6cf463efaffd3c9548a28f84220493ecd38d127b6b66d/mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9", size = 2283411 }, ] [[package]] name = "mypy-extensions" version = "1.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433, upload-time = "2023-02-04T12:11:27.157Z" } +sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695, upload-time = "2023-02-04T12:11:25.002Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, ] [[package]] name = "networkx" version = "3.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c4/80/a84676339aaae2f1cfdf9f418701dd634aef9cc76f708ef55c36ff39c3ca/networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6", size = 2073928, upload-time = "2023-10-28T08:41:39.364Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/80/a84676339aaae2f1cfdf9f418701dd634aef9cc76f708ef55c36ff39c3ca/networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6", size = 2073928 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/f0/8fbc882ca80cf077f1b246c0e3c3465f7f415439bdea6b899f6b19f61f70/networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2", size = 1647772, upload-time = "2023-10-28T08:41:36.945Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f0/8fbc882ca80cf077f1b246c0e3c3465f7f415439bdea6b899f6b19f61f70/networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2", size = 1647772 }, ] [[package]] name = "numpy" version = "1.26.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129, upload-time = "2024-02-06T00:26:44.495Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/94/ace0fdea5241a27d13543ee117cbc65868e82213fb31a8eb7fe9ff23f313/numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0", size = 20631468, upload-time = "2024-02-05T23:48:01.194Z" }, - { url = "https://files.pythonhosted.org/packages/20/f7/b24208eba89f9d1b58c1668bc6c8c4fd472b20c45573cb767f59d49fb0f6/numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a", size = 13966411, upload-time = "2024-02-05T23:48:29.038Z" }, - { url = "https://files.pythonhosted.org/packages/fc/a5/4beee6488160798683eed5bdb7eead455892c3b4e1f78d79d8d3f3b084ac/numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4", size = 14219016, upload-time = "2024-02-05T23:48:54.098Z" }, - { url = "https://files.pythonhosted.org/packages/4b/d7/ecf66c1cd12dc28b4040b15ab4d17b773b87fa9d29ca16125de01adb36cd/numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f", size = 18240889, upload-time = "2024-02-05T23:49:25.361Z" }, - { url = "https://files.pythonhosted.org/packages/24/03/6f229fe3187546435c4f6f89f6d26c129d4f5bed40552899fcf1f0bf9e50/numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a", size = 13876746, upload-time = "2024-02-05T23:49:51.983Z" }, - { url = "https://files.pythonhosted.org/packages/39/fe/39ada9b094f01f5a35486577c848fe274e374bbf8d8f472e1423a0bbd26d/numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2", size = 18078620, upload-time = "2024-02-05T23:50:22.515Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ef/6ad11d51197aad206a9ad2286dc1aac6a378059e06e8cf22cd08ed4f20dc/numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07", size = 5972659, upload-time = "2024-02-05T23:50:35.834Z" }, - { url = "https://files.pythonhosted.org/packages/19/77/538f202862b9183f54108557bfda67e17603fc560c384559e769321c9d92/numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5", size = 15808905, upload-time = "2024-02-05T23:51:03.701Z" }, - { url = "https://files.pythonhosted.org/packages/11/57/baae43d14fe163fa0e4c47f307b6b2511ab8d7d30177c491960504252053/numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", size = 20630554, upload-time = "2024-02-05T23:51:50.149Z" }, - { url = "https://files.pythonhosted.org/packages/1a/2e/151484f49fd03944c4a3ad9c418ed193cfd02724e138ac8a9505d056c582/numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", size = 13997127, upload-time = "2024-02-05T23:52:15.314Z" }, - { url = "https://files.pythonhosted.org/packages/79/ae/7e5b85136806f9dadf4878bf73cf223fe5c2636818ba3ab1c585d0403164/numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", size = 14222994, upload-time = "2024-02-05T23:52:47.569Z" }, - { url = "https://files.pythonhosted.org/packages/3a/d0/edc009c27b406c4f9cbc79274d6e46d634d139075492ad055e3d68445925/numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5", size = 18252005, upload-time = "2024-02-05T23:53:15.637Z" }, - { url = "https://files.pythonhosted.org/packages/09/bf/2b1aaf8f525f2923ff6cfcf134ae5e750e279ac65ebf386c75a0cf6da06a/numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a", size = 13885297, upload-time = "2024-02-05T23:53:42.16Z" }, - { url = "https://files.pythonhosted.org/packages/df/a0/4e0f14d847cfc2a633a1c8621d00724f3206cfeddeb66d35698c4e2cf3d2/numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a", size = 18093567, upload-time = "2024-02-05T23:54:11.696Z" }, - { url = "https://files.pythonhosted.org/packages/d2/b7/a734c733286e10a7f1a8ad1ae8c90f2d33bf604a96548e0a4a3a6739b468/numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20", size = 5968812, upload-time = "2024-02-05T23:54:26.453Z" }, - { url = "https://files.pythonhosted.org/packages/3f/6b/5610004206cf7f8e7ad91c5a85a8c71b2f2f8051a0c0c4d5916b76d6cbb2/numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2", size = 15811913, upload-time = "2024-02-05T23:54:53.933Z" }, - { url = "https://files.pythonhosted.org/packages/95/12/8f2020a8e8b8383ac0177dc9570aad031a3beb12e38847f7129bacd96228/numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", size = 20335901, upload-time = "2024-02-05T23:55:32.801Z" }, - { url = "https://files.pythonhosted.org/packages/75/5b/ca6c8bd14007e5ca171c7c03102d17b4f4e0ceb53957e8c44343a9546dcc/numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", size = 13685868, upload-time = "2024-02-05T23:55:56.28Z" }, - { url = "https://files.pythonhosted.org/packages/79/f8/97f10e6755e2a7d027ca783f63044d5b1bc1ae7acb12afe6a9b4286eac17/numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", size = 13925109, upload-time = "2024-02-05T23:56:20.368Z" }, - { url = "https://files.pythonhosted.org/packages/0f/50/de23fde84e45f5c4fda2488c759b69990fd4512387a8632860f3ac9cd225/numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", size = 17950613, upload-time = "2024-02-05T23:56:56.054Z" }, - { url = "https://files.pythonhosted.org/packages/4c/0c/9c603826b6465e82591e05ca230dfc13376da512b25ccd0894709b054ed0/numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", size = 13572172, upload-time = "2024-02-05T23:57:21.56Z" }, - { url = "https://files.pythonhosted.org/packages/76/8c/2ba3902e1a0fc1c74962ea9bb33a534bb05984ad7ff9515bf8d07527cadd/numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", size = 17786643, upload-time = "2024-02-05T23:57:56.585Z" }, - { url = "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", size = 5677803, upload-time = "2024-02-05T23:58:08.963Z" }, - { url = "https://files.pythonhosted.org/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754, upload-time = "2024-02-05T23:58:36.364Z" }, + { url = "https://files.pythonhosted.org/packages/a7/94/ace0fdea5241a27d13543ee117cbc65868e82213fb31a8eb7fe9ff23f313/numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0", size = 20631468 }, + { url = "https://files.pythonhosted.org/packages/20/f7/b24208eba89f9d1b58c1668bc6c8c4fd472b20c45573cb767f59d49fb0f6/numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a", size = 13966411 }, + { url = "https://files.pythonhosted.org/packages/fc/a5/4beee6488160798683eed5bdb7eead455892c3b4e1f78d79d8d3f3b084ac/numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4", size = 14219016 }, + { url = "https://files.pythonhosted.org/packages/4b/d7/ecf66c1cd12dc28b4040b15ab4d17b773b87fa9d29ca16125de01adb36cd/numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f", size = 18240889 }, + { url = "https://files.pythonhosted.org/packages/24/03/6f229fe3187546435c4f6f89f6d26c129d4f5bed40552899fcf1f0bf9e50/numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a", size = 13876746 }, + { url = "https://files.pythonhosted.org/packages/39/fe/39ada9b094f01f5a35486577c848fe274e374bbf8d8f472e1423a0bbd26d/numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2", size = 18078620 }, + { url = "https://files.pythonhosted.org/packages/d5/ef/6ad11d51197aad206a9ad2286dc1aac6a378059e06e8cf22cd08ed4f20dc/numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07", size = 5972659 }, + { url = "https://files.pythonhosted.org/packages/19/77/538f202862b9183f54108557bfda67e17603fc560c384559e769321c9d92/numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5", size = 15808905 }, + { url = "https://files.pythonhosted.org/packages/11/57/baae43d14fe163fa0e4c47f307b6b2511ab8d7d30177c491960504252053/numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", size = 20630554 }, + { url = "https://files.pythonhosted.org/packages/1a/2e/151484f49fd03944c4a3ad9c418ed193cfd02724e138ac8a9505d056c582/numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", size = 13997127 }, + { url = "https://files.pythonhosted.org/packages/79/ae/7e5b85136806f9dadf4878bf73cf223fe5c2636818ba3ab1c585d0403164/numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", size = 14222994 }, + { url = "https://files.pythonhosted.org/packages/3a/d0/edc009c27b406c4f9cbc79274d6e46d634d139075492ad055e3d68445925/numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5", size = 18252005 }, + { url = "https://files.pythonhosted.org/packages/09/bf/2b1aaf8f525f2923ff6cfcf134ae5e750e279ac65ebf386c75a0cf6da06a/numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a", size = 13885297 }, + { url = "https://files.pythonhosted.org/packages/df/a0/4e0f14d847cfc2a633a1c8621d00724f3206cfeddeb66d35698c4e2cf3d2/numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a", size = 18093567 }, + { url = "https://files.pythonhosted.org/packages/d2/b7/a734c733286e10a7f1a8ad1ae8c90f2d33bf604a96548e0a4a3a6739b468/numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20", size = 5968812 }, + { url = "https://files.pythonhosted.org/packages/3f/6b/5610004206cf7f8e7ad91c5a85a8c71b2f2f8051a0c0c4d5916b76d6cbb2/numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2", size = 15811913 }, + { url = "https://files.pythonhosted.org/packages/95/12/8f2020a8e8b8383ac0177dc9570aad031a3beb12e38847f7129bacd96228/numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", size = 20335901 }, + { url = "https://files.pythonhosted.org/packages/75/5b/ca6c8bd14007e5ca171c7c03102d17b4f4e0ceb53957e8c44343a9546dcc/numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", size = 13685868 }, + { url = "https://files.pythonhosted.org/packages/79/f8/97f10e6755e2a7d027ca783f63044d5b1bc1ae7acb12afe6a9b4286eac17/numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", size = 13925109 }, + { url = "https://files.pythonhosted.org/packages/0f/50/de23fde84e45f5c4fda2488c759b69990fd4512387a8632860f3ac9cd225/numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", size = 17950613 }, + { url = "https://files.pythonhosted.org/packages/4c/0c/9c603826b6465e82591e05ca230dfc13376da512b25ccd0894709b054ed0/numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", size = 13572172 }, + { url = "https://files.pythonhosted.org/packages/76/8c/2ba3902e1a0fc1c74962ea9bb33a534bb05984ad7ff9515bf8d07527cadd/numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", size = 17786643 }, + { url = "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", size = 5677803 }, + { url = "https://files.pythonhosted.org/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754 }, +] + +[[package]] +name = "omegaconf" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "antlr4-python3-runtime" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/48/6388f1bb9da707110532cb70ec4d2822858ddfb44f1cdf1233c20a80ea4b/omegaconf-2.3.0.tar.gz", hash = "sha256:d5d4b6d29955cc50ad50c46dc269bcd92c6e00f5f90d23ab5fee7bfca4ba4cc7", size = 3298120 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/94/1843518e420fa3ed6919835845df698c7e27e183cb997394e4a670973a65/omegaconf-2.3.0-py3-none-any.whl", hash = "sha256:7b4df175cdb08ba400f45cae3bdcae7ba8365db4d165fc65fd04b050ab63b46b", size = 79500 }, ] [[package]] @@ -1753,26 +1759,26 @@ dependencies = [ { name = "numpy" }, { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b3/fe/0978403c8d710ece2f34006367e78de80410743fe0e7680c8f33f2dab20d/onnx-1.16.0.tar.gz", hash = "sha256:237c6987c6c59d9f44b6136f5819af79574f8d96a760a1fa843bede11f3822f7", size = 12303017, upload-time = "2024-03-25T15:33:46.091Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/fe/0978403c8d710ece2f34006367e78de80410743fe0e7680c8f33f2dab20d/onnx-1.16.0.tar.gz", hash = "sha256:237c6987c6c59d9f44b6136f5819af79574f8d96a760a1fa843bede11f3822f7", size = 12303017 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/0b/f4705e4a3fa6fd0de971302fdae17ad176b024eca8c24360f0e37c00f9df/onnx-1.16.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:9eadbdce25b19d6216f426d6d99b8bc877a65ed92cbef9707751c6669190ba4f", size = 16514483, upload-time = "2024-03-25T15:25:07.947Z" }, - { url = "https://files.pythonhosted.org/packages/b8/1c/50310a559857951fc6e069cf5d89deebe34287997d1c5928bca435456f62/onnx-1.16.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:034ae21a2aaa2e9c14119a840d2926d213c27aad29e5e3edaa30145a745048e1", size = 15012939, upload-time = "2024-03-25T15:25:11.632Z" }, - { url = "https://files.pythonhosted.org/packages/ef/6e/96be6692ebcd8da568084d753f386ce08efa1f99b216f346ee281edd6cc3/onnx-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec22a43d74eb1f2303373e2fbe7fbcaa45fb225f4eb146edfed1356ada7a9aea", size = 15791856, upload-time = "2024-03-25T15:25:15.36Z" }, - { url = "https://files.pythonhosted.org/packages/49/5f/d8e1a24247f506a77cbe22341c72ca91bea3b468c5d6bca2047d885ea3c6/onnx-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:298f28a2b5ac09145fa958513d3d1e6b349ccf86a877dbdcccad57713fe360b3", size = 15922279, upload-time = "2024-03-25T15:25:18.939Z" }, - { url = "https://files.pythonhosted.org/packages/cb/14/562e4ac22cdf41f4465e3b114ef1a9467d513eeff0b9c2285c2da5db6ed1/onnx-1.16.0-cp310-cp310-win32.whl", hash = "sha256:66300197b52beca08bc6262d43c103289c5d45fde43fb51922ed1eb83658cf0c", size = 14335703, upload-time = "2024-03-25T15:25:22.611Z" }, - { url = "https://files.pythonhosted.org/packages/3b/e2/471ff83b3862967791d67f630000afce038756afbdf0665a3d767677c851/onnx-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:ae0029f5e47bf70a1a62e7f88c80bca4ef39b844a89910039184221775df5e43", size = 14435099, upload-time = "2024-03-25T15:25:25.05Z" }, - { url = "https://files.pythonhosted.org/packages/a4/b8/7accf3f93eee498711f0b7f07f6e93906e031622473e85ce9cd3578f6a92/onnx-1.16.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:f51179d4af3372b4f3800c558d204b592c61e4b4a18b8f61e0eea7f46211221a", size = 16514376, upload-time = "2024-03-25T15:25:27.899Z" }, - { url = "https://files.pythonhosted.org/packages/cc/24/a328236b594d5fea23f70a3a8139e730cb43334f0b24693831c47c9064f0/onnx-1.16.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:5202559070afec5144332db216c20f2fff8323cf7f6512b0ca11b215eacc5bf3", size = 15012839, upload-time = "2024-03-25T15:25:31.16Z" }, - { url = "https://files.pythonhosted.org/packages/80/12/57187bab3f830a47fa65eafe4fbaef01dfdf5042cf82a41fa440fab68766/onnx-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77579e7c15b4df39d29465b216639a5f9b74026bdd9e4b6306cd19a32dcfe67c", size = 15791944, upload-time = "2024-03-25T15:25:34.778Z" }, - { url = "https://files.pythonhosted.org/packages/df/48/63f68b65d041aedffab41eea930563ca52aab70dbaa7d4820501618c1a70/onnx-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e60ca76ac24b65c25860d0f2d2cdd96d6320d062a01dd8ce87c5743603789b8", size = 15922450, upload-time = "2024-03-25T15:25:37.983Z" }, - { url = "https://files.pythonhosted.org/packages/08/1b/4bdf4534f5ff08973725ba5409f95bbf64e2789cd20be615880dae689973/onnx-1.16.0-cp311-cp311-win32.whl", hash = "sha256:81b4ee01bc554e8a2b11ac6439882508a5377a1c6b452acd69a1eebb83571117", size = 14335808, upload-time = "2024-03-25T15:25:40.523Z" }, - { url = "https://files.pythonhosted.org/packages/aa/d0/0514d02d2e84e7bb48a105877eae4065e54d7dabb60d0b60214fe2677346/onnx-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:7449241e70b847b9c3eb8dae622df8c1b456d11032a9d7e26e0ee8a698d5bf86", size = 14434905, upload-time = "2024-03-25T15:25:42.905Z" }, - { url = "https://files.pythonhosted.org/packages/42/87/577adadda30ee08041e81ef02a331ca9d1a8df93a2e4c4c53ec56fbbc2ac/onnx-1.16.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:03a627488b1a9975d95d6a55582af3e14c7f3bb87444725b999935ddd271d352", size = 16516304, upload-time = "2024-03-25T15:25:45.875Z" }, - { url = "https://files.pythonhosted.org/packages/e3/1b/6e1ea37e081cc49a28f0e4d3830b4c8525081354cf9f5529c6c92268fc77/onnx-1.16.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:c392faeabd9283ee344ccb4b067d1fea9dfc614fa1f0de7c47589efd79e15e78", size = 15016538, upload-time = "2024-03-25T15:25:49.396Z" }, - { url = "https://files.pythonhosted.org/packages/6d/07/f8fefd5eb0984be42ef677f0b7db7527edc4529224a34a3c31f7b12ec80d/onnx-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0efeb46985de08f0efe758cb54ad3457e821a05c2eaf5ba2ccb8cd1602c08084", size = 15790415, upload-time = "2024-03-25T15:25:51.929Z" }, - { url = "https://files.pythonhosted.org/packages/11/71/c219ce6d4b5205c77405af7f2de2511ad4eeffbfeb77a422151e893de0ea/onnx-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddf14a3d32234f23e44abb73a755cb96a423fac7f004e8f046f36b10214151ee", size = 15922224, upload-time = "2024-03-25T15:25:55.049Z" }, - { url = "https://files.pythonhosted.org/packages/8e/a4/554a6e5741b42406c5b1970d04685d7f2012019d4178408ed4b3ec953033/onnx-1.16.0-cp312-cp312-win32.whl", hash = "sha256:62a2e27ae8ba5fc9b4a2620301446a517b5ffaaf8566611de7a7c2160f5bcf4c", size = 14336234, upload-time = "2024-03-25T15:25:57.998Z" }, - { url = "https://files.pythonhosted.org/packages/e9/a1/8aecec497010ad34e7656408df1868d94483c5c56bc991f4088c06150896/onnx-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:3e0860fea94efde777e81a6f68f65761ed5e5f3adea2e050d7fbe373a9ae05b3", size = 14436591, upload-time = "2024-03-25T15:26:01.252Z" }, + { url = "https://files.pythonhosted.org/packages/c8/0b/f4705e4a3fa6fd0de971302fdae17ad176b024eca8c24360f0e37c00f9df/onnx-1.16.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:9eadbdce25b19d6216f426d6d99b8bc877a65ed92cbef9707751c6669190ba4f", size = 16514483 }, + { url = "https://files.pythonhosted.org/packages/b8/1c/50310a559857951fc6e069cf5d89deebe34287997d1c5928bca435456f62/onnx-1.16.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:034ae21a2aaa2e9c14119a840d2926d213c27aad29e5e3edaa30145a745048e1", size = 15012939 }, + { url = "https://files.pythonhosted.org/packages/ef/6e/96be6692ebcd8da568084d753f386ce08efa1f99b216f346ee281edd6cc3/onnx-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec22a43d74eb1f2303373e2fbe7fbcaa45fb225f4eb146edfed1356ada7a9aea", size = 15791856 }, + { url = "https://files.pythonhosted.org/packages/49/5f/d8e1a24247f506a77cbe22341c72ca91bea3b468c5d6bca2047d885ea3c6/onnx-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:298f28a2b5ac09145fa958513d3d1e6b349ccf86a877dbdcccad57713fe360b3", size = 15922279 }, + { url = "https://files.pythonhosted.org/packages/cb/14/562e4ac22cdf41f4465e3b114ef1a9467d513eeff0b9c2285c2da5db6ed1/onnx-1.16.0-cp310-cp310-win32.whl", hash = "sha256:66300197b52beca08bc6262d43c103289c5d45fde43fb51922ed1eb83658cf0c", size = 14335703 }, + { url = "https://files.pythonhosted.org/packages/3b/e2/471ff83b3862967791d67f630000afce038756afbdf0665a3d767677c851/onnx-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:ae0029f5e47bf70a1a62e7f88c80bca4ef39b844a89910039184221775df5e43", size = 14435099 }, + { url = "https://files.pythonhosted.org/packages/a4/b8/7accf3f93eee498711f0b7f07f6e93906e031622473e85ce9cd3578f6a92/onnx-1.16.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:f51179d4af3372b4f3800c558d204b592c61e4b4a18b8f61e0eea7f46211221a", size = 16514376 }, + { url = "https://files.pythonhosted.org/packages/cc/24/a328236b594d5fea23f70a3a8139e730cb43334f0b24693831c47c9064f0/onnx-1.16.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:5202559070afec5144332db216c20f2fff8323cf7f6512b0ca11b215eacc5bf3", size = 15012839 }, + { url = "https://files.pythonhosted.org/packages/80/12/57187bab3f830a47fa65eafe4fbaef01dfdf5042cf82a41fa440fab68766/onnx-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77579e7c15b4df39d29465b216639a5f9b74026bdd9e4b6306cd19a32dcfe67c", size = 15791944 }, + { url = "https://files.pythonhosted.org/packages/df/48/63f68b65d041aedffab41eea930563ca52aab70dbaa7d4820501618c1a70/onnx-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e60ca76ac24b65c25860d0f2d2cdd96d6320d062a01dd8ce87c5743603789b8", size = 15922450 }, + { url = "https://files.pythonhosted.org/packages/08/1b/4bdf4534f5ff08973725ba5409f95bbf64e2789cd20be615880dae689973/onnx-1.16.0-cp311-cp311-win32.whl", hash = "sha256:81b4ee01bc554e8a2b11ac6439882508a5377a1c6b452acd69a1eebb83571117", size = 14335808 }, + { url = "https://files.pythonhosted.org/packages/aa/d0/0514d02d2e84e7bb48a105877eae4065e54d7dabb60d0b60214fe2677346/onnx-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:7449241e70b847b9c3eb8dae622df8c1b456d11032a9d7e26e0ee8a698d5bf86", size = 14434905 }, + { url = "https://files.pythonhosted.org/packages/42/87/577adadda30ee08041e81ef02a331ca9d1a8df93a2e4c4c53ec56fbbc2ac/onnx-1.16.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:03a627488b1a9975d95d6a55582af3e14c7f3bb87444725b999935ddd271d352", size = 16516304 }, + { url = "https://files.pythonhosted.org/packages/e3/1b/6e1ea37e081cc49a28f0e4d3830b4c8525081354cf9f5529c6c92268fc77/onnx-1.16.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:c392faeabd9283ee344ccb4b067d1fea9dfc614fa1f0de7c47589efd79e15e78", size = 15016538 }, + { url = "https://files.pythonhosted.org/packages/6d/07/f8fefd5eb0984be42ef677f0b7db7527edc4529224a34a3c31f7b12ec80d/onnx-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0efeb46985de08f0efe758cb54ad3457e821a05c2eaf5ba2ccb8cd1602c08084", size = 15790415 }, + { url = "https://files.pythonhosted.org/packages/11/71/c219ce6d4b5205c77405af7f2de2511ad4eeffbfeb77a422151e893de0ea/onnx-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddf14a3d32234f23e44abb73a755cb96a423fac7f004e8f046f36b10214151ee", size = 15922224 }, + { url = "https://files.pythonhosted.org/packages/8e/a4/554a6e5741b42406c5b1970d04685d7f2012019d4178408ed4b3ec953033/onnx-1.16.0-cp312-cp312-win32.whl", hash = "sha256:62a2e27ae8ba5fc9b4a2620301446a517b5ffaaf8566611de7a7c2160f5bcf4c", size = 14336234 }, + { url = "https://files.pythonhosted.org/packages/e9/a1/8aecec497010ad34e7656408df1868d94483c5c56bc991f4088c06150896/onnx-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:3e0860fea94efde777e81a6f68f65761ed5e5f3adea2e050d7fbe373a9ae05b3", size = 14436591 }, ] [[package]] @@ -1788,24 +1794,24 @@ dependencies = [ { name = "sympy" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/76/b9/664a1ffee62fa51529fac27b37409d5d28cadee8d97db806fcba68339b7e/onnxruntime-1.22.1-cp310-cp310-macosx_13_0_universal2.whl", hash = "sha256:80e7f51da1f5201c1379b8d6ef6170505cd800e40da216290f5e06be01aadf95", size = 34319864, upload-time = "2025-07-10T19:15:15.371Z" }, - { url = "https://files.pythonhosted.org/packages/b9/64/bc7221e92c994931024e22b22401b962c299e991558c3d57f7e34538b4b9/onnxruntime-1.22.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89ddfdbbdaf7e3a59515dee657f6515601d55cb21a0f0f48c81aefc54ff1b73", size = 14472246, upload-time = "2025-07-10T19:15:19.403Z" }, - { url = "https://files.pythonhosted.org/packages/84/57/901eddbfb59ac4d008822b236450d5765cafcd450c787019416f8d3baf11/onnxruntime-1.22.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bddc75868bcf6f9ed76858a632f65f7b1846bdcefc6d637b1e359c2c68609964", size = 16459905, upload-time = "2025-07-10T19:15:21.749Z" }, - { url = "https://files.pythonhosted.org/packages/de/90/d6a1eb9b47e66a18afe7d1cf7cf0b2ef966ffa6f44d9f32d94c2be2860fb/onnxruntime-1.22.1-cp310-cp310-win_amd64.whl", hash = "sha256:01e2f21b2793eb0c8642d2be3cee34cc7d96b85f45f6615e4e220424158877ce", size = 12689001, upload-time = "2025-07-10T19:15:23.848Z" }, - { url = "https://files.pythonhosted.org/packages/82/ff/4a1a6747e039ef29a8d4ee4510060e9a805982b6da906a3da2306b7a3be6/onnxruntime-1.22.1-cp311-cp311-macosx_13_0_universal2.whl", hash = "sha256:f4581bccb786da68725d8eac7c63a8f31a89116b8761ff8b4989dc58b61d49a0", size = 34324148, upload-time = "2025-07-10T19:15:26.584Z" }, - { url = "https://files.pythonhosted.org/packages/0b/05/9f1929723f1cca8c9fb1b2b97ac54ce61362c7201434d38053ea36ee4225/onnxruntime-1.22.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7ae7526cf10f93454beb0f751e78e5cb7619e3b92f9fc3bd51aa6f3b7a8977e5", size = 14473779, upload-time = "2025-07-10T19:15:30.183Z" }, - { url = "https://files.pythonhosted.org/packages/59/f3/c93eb4167d4f36ea947930f82850231f7ce0900cb00e1a53dc4995b60479/onnxruntime-1.22.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f6effa1299ac549a05c784d50292e3378dbbf010346ded67400193b09ddc2f04", size = 16460799, upload-time = "2025-07-10T19:15:33.005Z" }, - { url = "https://files.pythonhosted.org/packages/a8/01/e536397b03e4462d3260aee5387e6f606c8fa9d2b20b1728f988c3c72891/onnxruntime-1.22.1-cp311-cp311-win_amd64.whl", hash = "sha256:f28a42bb322b4ca6d255531bb334a2b3e21f172e37c1741bd5e66bc4b7b61f03", size = 12689881, upload-time = "2025-07-10T19:15:35.501Z" }, - { url = "https://files.pythonhosted.org/packages/48/70/ca2a4d38a5deccd98caa145581becb20c53684f451e89eb3a39915620066/onnxruntime-1.22.1-cp312-cp312-macosx_13_0_universal2.whl", hash = "sha256:a938d11c0dc811badf78e435daa3899d9af38abee950d87f3ab7430eb5b3cf5a", size = 34342883, upload-time = "2025-07-10T19:15:38.223Z" }, - { url = "https://files.pythonhosted.org/packages/29/e5/00b099b4d4f6223b610421080d0eed9327ef9986785c9141819bbba0d396/onnxruntime-1.22.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:984cea2a02fcc5dfea44ade9aca9fe0f7a8a2cd6f77c258fc4388238618f3928", size = 14473861, upload-time = "2025-07-10T19:15:42.911Z" }, - { url = "https://files.pythonhosted.org/packages/0a/50/519828a5292a6ccd8d5cd6d2f72c6b36ea528a2ef68eca69647732539ffa/onnxruntime-1.22.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2d39a530aff1ec8d02e365f35e503193991417788641b184f5b1e8c9a6d5ce8d", size = 16475713, upload-time = "2025-07-10T19:15:45.452Z" }, - { url = "https://files.pythonhosted.org/packages/5d/54/7139d463bb0a312890c9a5db87d7815d4a8cce9e6f5f28d04f0b55fcb160/onnxruntime-1.22.1-cp312-cp312-win_amd64.whl", hash = "sha256:6a64291d57ea966a245f749eb970f4fa05a64d26672e05a83fdb5db6b7d62f87", size = 12690910, upload-time = "2025-07-10T19:15:47.478Z" }, - { url = "https://files.pythonhosted.org/packages/e0/39/77cefa829740bd830915095d8408dce6d731b244e24b1f64fe3df9f18e86/onnxruntime-1.22.1-cp313-cp313-macosx_13_0_universal2.whl", hash = "sha256:d29c7d87b6cbed8fecfd09dca471832384d12a69e1ab873e5effbb94adc3e966", size = 34342026, upload-time = "2025-07-10T19:15:50.266Z" }, - { url = "https://files.pythonhosted.org/packages/d2/a6/444291524cb52875b5de980a6e918072514df63a57a7120bf9dfae3aeed1/onnxruntime-1.22.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:460487d83b7056ba98f1f7bac80287224c31d8149b15712b0d6f5078fcc33d0f", size = 14474014, upload-time = "2025-07-10T19:15:53.991Z" }, - { url = "https://files.pythonhosted.org/packages/87/9d/45a995437879c18beff26eacc2322f4227224d04c6ac3254dce2e8950190/onnxruntime-1.22.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b0c37070268ba4e02a1a9d28560cd00cd1e94f0d4f275cbef283854f861a65fa", size = 16475427, upload-time = "2025-07-10T19:15:56.067Z" }, - { url = "https://files.pythonhosted.org/packages/4c/06/9c765e66ad32a7e709ce4cb6b95d7eaa9cb4d92a6e11ea97c20ffecaf765/onnxruntime-1.22.1-cp313-cp313-win_amd64.whl", hash = "sha256:70980d729145a36a05f74b573435531f55ef9503bcda81fc6c3d6b9306199982", size = 12690841, upload-time = "2025-07-10T19:15:58.337Z" }, - { url = "https://files.pythonhosted.org/packages/52/8c/02af24ee1c8dce4e6c14a1642a7a56cebe323d2fa01d9a360a638f7e4b75/onnxruntime-1.22.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33a7980bbc4b7f446bac26c3785652fe8730ed02617d765399e89ac7d44e0f7d", size = 14479333, upload-time = "2025-07-10T19:16:00.544Z" }, - { url = "https://files.pythonhosted.org/packages/5d/15/d75fd66aba116ce3732bb1050401394c5ec52074c4f7ee18db8838dd4667/onnxruntime-1.22.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7e823624b015ea879d976cbef8bfaed2f7e2cc233d7506860a76dd37f8f381", size = 16477261, upload-time = "2025-07-10T19:16:03.226Z" }, + { url = "https://files.pythonhosted.org/packages/76/b9/664a1ffee62fa51529fac27b37409d5d28cadee8d97db806fcba68339b7e/onnxruntime-1.22.1-cp310-cp310-macosx_13_0_universal2.whl", hash = "sha256:80e7f51da1f5201c1379b8d6ef6170505cd800e40da216290f5e06be01aadf95", size = 34319864 }, + { url = "https://files.pythonhosted.org/packages/b9/64/bc7221e92c994931024e22b22401b962c299e991558c3d57f7e34538b4b9/onnxruntime-1.22.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89ddfdbbdaf7e3a59515dee657f6515601d55cb21a0f0f48c81aefc54ff1b73", size = 14472246 }, + { url = "https://files.pythonhosted.org/packages/84/57/901eddbfb59ac4d008822b236450d5765cafcd450c787019416f8d3baf11/onnxruntime-1.22.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bddc75868bcf6f9ed76858a632f65f7b1846bdcefc6d637b1e359c2c68609964", size = 16459905 }, + { url = "https://files.pythonhosted.org/packages/de/90/d6a1eb9b47e66a18afe7d1cf7cf0b2ef966ffa6f44d9f32d94c2be2860fb/onnxruntime-1.22.1-cp310-cp310-win_amd64.whl", hash = "sha256:01e2f21b2793eb0c8642d2be3cee34cc7d96b85f45f6615e4e220424158877ce", size = 12689001 }, + { url = "https://files.pythonhosted.org/packages/82/ff/4a1a6747e039ef29a8d4ee4510060e9a805982b6da906a3da2306b7a3be6/onnxruntime-1.22.1-cp311-cp311-macosx_13_0_universal2.whl", hash = "sha256:f4581bccb786da68725d8eac7c63a8f31a89116b8761ff8b4989dc58b61d49a0", size = 34324148 }, + { url = "https://files.pythonhosted.org/packages/0b/05/9f1929723f1cca8c9fb1b2b97ac54ce61362c7201434d38053ea36ee4225/onnxruntime-1.22.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7ae7526cf10f93454beb0f751e78e5cb7619e3b92f9fc3bd51aa6f3b7a8977e5", size = 14473779 }, + { url = "https://files.pythonhosted.org/packages/59/f3/c93eb4167d4f36ea947930f82850231f7ce0900cb00e1a53dc4995b60479/onnxruntime-1.22.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f6effa1299ac549a05c784d50292e3378dbbf010346ded67400193b09ddc2f04", size = 16460799 }, + { url = "https://files.pythonhosted.org/packages/a8/01/e536397b03e4462d3260aee5387e6f606c8fa9d2b20b1728f988c3c72891/onnxruntime-1.22.1-cp311-cp311-win_amd64.whl", hash = "sha256:f28a42bb322b4ca6d255531bb334a2b3e21f172e37c1741bd5e66bc4b7b61f03", size = 12689881 }, + { url = "https://files.pythonhosted.org/packages/48/70/ca2a4d38a5deccd98caa145581becb20c53684f451e89eb3a39915620066/onnxruntime-1.22.1-cp312-cp312-macosx_13_0_universal2.whl", hash = "sha256:a938d11c0dc811badf78e435daa3899d9af38abee950d87f3ab7430eb5b3cf5a", size = 34342883 }, + { url = "https://files.pythonhosted.org/packages/29/e5/00b099b4d4f6223b610421080d0eed9327ef9986785c9141819bbba0d396/onnxruntime-1.22.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:984cea2a02fcc5dfea44ade9aca9fe0f7a8a2cd6f77c258fc4388238618f3928", size = 14473861 }, + { url = "https://files.pythonhosted.org/packages/0a/50/519828a5292a6ccd8d5cd6d2f72c6b36ea528a2ef68eca69647732539ffa/onnxruntime-1.22.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2d39a530aff1ec8d02e365f35e503193991417788641b184f5b1e8c9a6d5ce8d", size = 16475713 }, + { url = "https://files.pythonhosted.org/packages/5d/54/7139d463bb0a312890c9a5db87d7815d4a8cce9e6f5f28d04f0b55fcb160/onnxruntime-1.22.1-cp312-cp312-win_amd64.whl", hash = "sha256:6a64291d57ea966a245f749eb970f4fa05a64d26672e05a83fdb5db6b7d62f87", size = 12690910 }, + { url = "https://files.pythonhosted.org/packages/e0/39/77cefa829740bd830915095d8408dce6d731b244e24b1f64fe3df9f18e86/onnxruntime-1.22.1-cp313-cp313-macosx_13_0_universal2.whl", hash = "sha256:d29c7d87b6cbed8fecfd09dca471832384d12a69e1ab873e5effbb94adc3e966", size = 34342026 }, + { url = "https://files.pythonhosted.org/packages/d2/a6/444291524cb52875b5de980a6e918072514df63a57a7120bf9dfae3aeed1/onnxruntime-1.22.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:460487d83b7056ba98f1f7bac80287224c31d8149b15712b0d6f5078fcc33d0f", size = 14474014 }, + { url = "https://files.pythonhosted.org/packages/87/9d/45a995437879c18beff26eacc2322f4227224d04c6ac3254dce2e8950190/onnxruntime-1.22.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b0c37070268ba4e02a1a9d28560cd00cd1e94f0d4f275cbef283854f861a65fa", size = 16475427 }, + { url = "https://files.pythonhosted.org/packages/4c/06/9c765e66ad32a7e709ce4cb6b95d7eaa9cb4d92a6e11ea97c20ffecaf765/onnxruntime-1.22.1-cp313-cp313-win_amd64.whl", hash = "sha256:70980d729145a36a05f74b573435531f55ef9503bcda81fc6c3d6b9306199982", size = 12690841 }, + { url = "https://files.pythonhosted.org/packages/52/8c/02af24ee1c8dce4e6c14a1642a7a56cebe323d2fa01d9a360a638f7e4b75/onnxruntime-1.22.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33a7980bbc4b7f446bac26c3785652fe8730ed02617d765399e89ac7d44e0f7d", size = 14479333 }, + { url = "https://files.pythonhosted.org/packages/5d/15/d75fd66aba116ce3732bb1050401394c5ec52074c4f7ee18db8838dd4667/onnxruntime-1.22.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7e823624b015ea879d976cbef8bfaed2f7e2cc233d7506860a76dd37f8f381", size = 16477261 }, ] [[package]] @@ -1842,10 +1848,27 @@ dependencies = [ { name = "sympy" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/57/e9a080f2477b2a4c16925f766e4615fc545098b0f4e20cf8ad803e7a9672/onnxruntime_openvino-1.18.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:565b874d21bcd48126da7d62f57db019f5ec0e1f82ae9b0740afa2ad91f8d331", size = 41971800, upload-time = "2024-06-25T06:30:37.042Z" }, - { url = "https://files.pythonhosted.org/packages/34/7d/b75913bce58f4ee9bf6a02d1b513b9fc82303a496ec698e6fb1f9d597cb4/onnxruntime_openvino-1.18.0-cp310-cp310-win_amd64.whl", hash = "sha256:7f1931060f710a6c8e32121bb73044c4772ef5925802fc8776d3fe1e87ab3f75", size = 5963263, upload-time = "2024-06-24T13:38:15.906Z" }, - { url = "https://files.pythonhosted.org/packages/7e/d3/8299b7285dc8fa7bd986b6f0d7c50b7f0fd13db50dd3b88b93ec269b1e08/onnxruntime_openvino-1.18.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eb1723d386f70a8e26398d983ebe35d2c25ba56e9cdb382670ebbf1f5139f8ba", size = 41971927, upload-time = "2024-06-25T06:30:43.765Z" }, - { url = "https://files.pythonhosted.org/packages/88/d9/ca0bfd7ed37153d9664ccdcfb4d0e5b1963563553b05cb4338b46968feb2/onnxruntime_openvino-1.18.0-cp311-cp311-win_amd64.whl", hash = "sha256:874a1e263dd86674593e5a879257650b06a8609c4d5768c3d8ed8dc4ae874b9c", size = 5963464, upload-time = "2024-06-24T13:38:18.437Z" }, + { url = "https://files.pythonhosted.org/packages/b3/57/e9a080f2477b2a4c16925f766e4615fc545098b0f4e20cf8ad803e7a9672/onnxruntime_openvino-1.18.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:565b874d21bcd48126da7d62f57db019f5ec0e1f82ae9b0740afa2ad91f8d331", size = 41971800 }, + { url = "https://files.pythonhosted.org/packages/34/7d/b75913bce58f4ee9bf6a02d1b513b9fc82303a496ec698e6fb1f9d597cb4/onnxruntime_openvino-1.18.0-cp310-cp310-win_amd64.whl", hash = "sha256:7f1931060f710a6c8e32121bb73044c4772ef5925802fc8776d3fe1e87ab3f75", size = 5963263 }, + { url = "https://files.pythonhosted.org/packages/7e/d3/8299b7285dc8fa7bd986b6f0d7c50b7f0fd13db50dd3b88b93ec269b1e08/onnxruntime_openvino-1.18.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eb1723d386f70a8e26398d983ebe35d2c25ba56e9cdb382670ebbf1f5139f8ba", size = 41971927 }, + { url = "https://files.pythonhosted.org/packages/88/d9/ca0bfd7ed37153d9664ccdcfb4d0e5b1963563553b05cb4338b46968feb2/onnxruntime_openvino-1.18.0-cp311-cp311-win_amd64.whl", hash = "sha256:874a1e263dd86674593e5a879257650b06a8609c4d5768c3d8ed8dc4ae874b9c", size = 5963464 }, +] + +[[package]] +name = "opencv-python" +version = "4.11.0.86" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/17/06/68c27a523103dad5837dc5b87e71285280c4f098c60e4fe8a8db6486ab09/opencv-python-4.11.0.86.tar.gz", hash = "sha256:03d60ccae62304860d232272e4a4fda93c39d595780cb40b161b310244b736a4", size = 95171956 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/4d/53b30a2a3ac1f75f65a59eb29cf2ee7207ce64867db47036ad61743d5a23/opencv_python-4.11.0.86-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:432f67c223f1dc2824f5e73cdfcd9db0efc8710647d4e813012195dc9122a52a", size = 37326322 }, + { url = "https://files.pythonhosted.org/packages/3b/84/0a67490741867eacdfa37bc18df96e08a9d579583b419010d7f3da8ff503/opencv_python-4.11.0.86-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:9d05ef13d23fe97f575153558653e2d6e87103995d54e6a35db3f282fe1f9c66", size = 56723197 }, + { url = "https://files.pythonhosted.org/packages/f3/bd/29c126788da65c1fb2b5fb621b7fed0ed5f9122aa22a0868c5e2c15c6d23/opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b92ae2c8852208817e6776ba1ea0d6b1e0a1b5431e971a2a0ddd2a8cc398202", size = 42230439 }, + { url = "https://files.pythonhosted.org/packages/2c/8b/90eb44a40476fa0e71e05a0283947cfd74a5d36121a11d926ad6f3193cc4/opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b02611523803495003bd87362db3e1d2a0454a6a63025dc6658a9830570aa0d", size = 62986597 }, + { url = "https://files.pythonhosted.org/packages/fb/d7/1d5941a9dde095468b288d989ff6539dd69cd429dbf1b9e839013d21b6f0/opencv_python-4.11.0.86-cp37-abi3-win32.whl", hash = "sha256:810549cb2a4aedaa84ad9a1c92fbfdfc14090e2749cedf2c1589ad8359aa169b", size = 29384337 }, + { url = "https://files.pythonhosted.org/packages/a4/7d/f1c30a92854540bf789e9cd5dde7ef49bbe63f855b85a2e6b3db8135c591/opencv_python-4.11.0.86-cp37-abi3-win_amd64.whl", hash = "sha256:085ad9b77c18853ea66283e98affefe2de8cc4c1f43eda4c100cf9b2721142ec", size = 39488044 }, ] [[package]] @@ -1855,186 +1878,186 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/36/2f/5b2b3ba52c864848885ba988f24b7f105052f68da9ab0e693cc7c25b0b30/opencv-python-headless-4.11.0.86.tar.gz", hash = "sha256:996eb282ca4b43ec6a3972414de0e2331f5d9cda2b41091a49739c19fb843798", size = 95177929, upload-time = "2025-01-16T13:53:40.22Z" } +sdist = { url = "https://files.pythonhosted.org/packages/36/2f/5b2b3ba52c864848885ba988f24b7f105052f68da9ab0e693cc7c25b0b30/opencv-python-headless-4.11.0.86.tar.gz", hash = "sha256:996eb282ca4b43ec6a3972414de0e2331f5d9cda2b41091a49739c19fb843798", size = 95177929 } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/53/2c50afa0b1e05ecdb4603818e85f7d174e683d874ef63a6abe3ac92220c8/opencv_python_headless-4.11.0.86-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:48128188ade4a7e517237c8e1e11a9cdf5c282761473383e77beb875bb1e61ca", size = 37326460, upload-time = "2025-01-16T13:52:57.015Z" }, - { url = "https://files.pythonhosted.org/packages/3b/43/68555327df94bb9b59a1fd645f63fafb0762515344d2046698762fc19d58/opencv_python_headless-4.11.0.86-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:a66c1b286a9de872c343ee7c3553b084244299714ebb50fbdcd76f07ebbe6c81", size = 56723330, upload-time = "2025-01-16T13:55:45.731Z" }, - { url = "https://files.pythonhosted.org/packages/45/be/1438ce43ebe65317344a87e4b150865c5585f4c0db880a34cdae5ac46881/opencv_python_headless-4.11.0.86-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6efabcaa9df731f29e5ea9051776715b1bdd1845d7c9530065c7951d2a2899eb", size = 29487060, upload-time = "2025-01-16T13:51:59.625Z" }, - { url = "https://files.pythonhosted.org/packages/dd/5c/c139a7876099916879609372bfa513b7f1257f7f1a908b0bdc1c2328241b/opencv_python_headless-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e0a27c19dd1f40ddff94976cfe43066fbbe9dfbb2ec1907d66c19caef42a57b", size = 49969856, upload-time = "2025-01-16T13:53:29.654Z" }, - { url = "https://files.pythonhosted.org/packages/95/dd/ed1191c9dc91abcc9f752b499b7928aacabf10567bb2c2535944d848af18/opencv_python_headless-4.11.0.86-cp37-abi3-win32.whl", hash = "sha256:f447d8acbb0b6f2808da71fddd29c1cdd448d2bc98f72d9bb78a7a898fc9621b", size = 29324425, upload-time = "2025-01-16T13:52:49.048Z" }, - { url = "https://files.pythonhosted.org/packages/86/8a/69176a64335aed183529207ba8bc3d329c2999d852b4f3818027203f50e6/opencv_python_headless-4.11.0.86-cp37-abi3-win_amd64.whl", hash = "sha256:6c304df9caa7a6a5710b91709dd4786bf20a74d57672b3c31f7033cc638174ca", size = 39402386, upload-time = "2025-01-16T13:52:56.418Z" }, + { url = "https://files.pythonhosted.org/packages/dc/53/2c50afa0b1e05ecdb4603818e85f7d174e683d874ef63a6abe3ac92220c8/opencv_python_headless-4.11.0.86-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:48128188ade4a7e517237c8e1e11a9cdf5c282761473383e77beb875bb1e61ca", size = 37326460 }, + { url = "https://files.pythonhosted.org/packages/3b/43/68555327df94bb9b59a1fd645f63fafb0762515344d2046698762fc19d58/opencv_python_headless-4.11.0.86-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:a66c1b286a9de872c343ee7c3553b084244299714ebb50fbdcd76f07ebbe6c81", size = 56723330 }, + { url = "https://files.pythonhosted.org/packages/45/be/1438ce43ebe65317344a87e4b150865c5585f4c0db880a34cdae5ac46881/opencv_python_headless-4.11.0.86-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6efabcaa9df731f29e5ea9051776715b1bdd1845d7c9530065c7951d2a2899eb", size = 29487060 }, + { url = "https://files.pythonhosted.org/packages/dd/5c/c139a7876099916879609372bfa513b7f1257f7f1a908b0bdc1c2328241b/opencv_python_headless-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e0a27c19dd1f40ddff94976cfe43066fbbe9dfbb2ec1907d66c19caef42a57b", size = 49969856 }, + { url = "https://files.pythonhosted.org/packages/95/dd/ed1191c9dc91abcc9f752b499b7928aacabf10567bb2c2535944d848af18/opencv_python_headless-4.11.0.86-cp37-abi3-win32.whl", hash = "sha256:f447d8acbb0b6f2808da71fddd29c1cdd448d2bc98f72d9bb78a7a898fc9621b", size = 29324425 }, + { url = "https://files.pythonhosted.org/packages/86/8a/69176a64335aed183529207ba8bc3d329c2999d852b4f3818027203f50e6/opencv_python_headless-4.11.0.86-cp37-abi3-win_amd64.whl", hash = "sha256:6c304df9caa7a6a5710b91709dd4786bf20a74d57672b3c31f7033cc638174ca", size = 39402386 }, ] [[package]] name = "orjson" version = "3.11.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/be/4d/8df5f83256a809c22c4d6792ce8d43bb503be0fb7a8e4da9025754b09658/orjson-3.11.3.tar.gz", hash = "sha256:1c0603b1d2ffcd43a411d64797a19556ef76958aef1c182f22dc30860152a98a", size = 5482394, upload-time = "2025-08-26T17:46:43.171Z" } +sdist = { url = "https://files.pythonhosted.org/packages/be/4d/8df5f83256a809c22c4d6792ce8d43bb503be0fb7a8e4da9025754b09658/orjson-3.11.3.tar.gz", hash = "sha256:1c0603b1d2ffcd43a411d64797a19556ef76958aef1c182f22dc30860152a98a", size = 5482394 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/64/4a3cef001c6cd9c64256348d4c13a7b09b857e3e1cbb5185917df67d8ced/orjson-3.11.3-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:29cb1f1b008d936803e2da3d7cba726fc47232c45df531b29edf0b232dd737e7", size = 238600, upload-time = "2025-08-26T17:44:36.875Z" }, - { url = "https://files.pythonhosted.org/packages/10/ce/0c8c87f54f79d051485903dc46226c4d3220b691a151769156054df4562b/orjson-3.11.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97dceed87ed9139884a55db8722428e27bd8452817fbf1869c58b49fecab1120", size = 123526, upload-time = "2025-08-26T17:44:39.574Z" }, - { url = "https://files.pythonhosted.org/packages/ef/d0/249497e861f2d438f45b3ab7b7b361484237414945169aa285608f9f7019/orjson-3.11.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:58533f9e8266cb0ac298e259ed7b4d42ed3fa0b78ce76860626164de49e0d467", size = 128075, upload-time = "2025-08-26T17:44:40.672Z" }, - { url = "https://files.pythonhosted.org/packages/e5/64/00485702f640a0fd56144042a1ea196469f4a3ae93681871564bf74fa996/orjson-3.11.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c212cfdd90512fe722fa9bd620de4d46cda691415be86b2e02243242ae81873", size = 130483, upload-time = "2025-08-26T17:44:41.788Z" }, - { url = "https://files.pythonhosted.org/packages/64/81/110d68dba3909171bf3f05619ad0cf187b430e64045ae4e0aa7ccfe25b15/orjson-3.11.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff835b5d3e67d9207343effb03760c00335f8b5285bfceefd4dc967b0e48f6a", size = 132539, upload-time = "2025-08-26T17:44:43.12Z" }, - { url = "https://files.pythonhosted.org/packages/79/92/dba25c22b0ddfafa1e6516a780a00abac28d49f49e7202eb433a53c3e94e/orjson-3.11.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5aa4682912a450c2db89cbd92d356fef47e115dffba07992555542f344d301b", size = 135390, upload-time = "2025-08-26T17:44:44.199Z" }, - { url = "https://files.pythonhosted.org/packages/44/1d/ca2230fd55edbd87b58a43a19032d63a4b180389a97520cc62c535b726f9/orjson-3.11.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7d18dd34ea2e860553a579df02041845dee0af8985dff7f8661306f95504ddf", size = 132966, upload-time = "2025-08-26T17:44:45.719Z" }, - { url = "https://files.pythonhosted.org/packages/6e/b9/96bbc8ed3e47e52b487d504bd6861798977445fbc410da6e87e302dc632d/orjson-3.11.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d8b11701bc43be92ea42bd454910437b355dfb63696c06fe953ffb40b5f763b4", size = 131349, upload-time = "2025-08-26T17:44:46.862Z" }, - { url = "https://files.pythonhosted.org/packages/c4/3c/418fbd93d94b0df71cddf96b7fe5894d64a5d890b453ac365120daec30f7/orjson-3.11.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:90368277087d4af32d38bd55f9da2ff466d25325bf6167c8f382d8ee40cb2bbc", size = 404087, upload-time = "2025-08-26T17:44:48.079Z" }, - { url = "https://files.pythonhosted.org/packages/5b/a9/2bfd58817d736c2f63608dec0c34857339d423eeed30099b126562822191/orjson-3.11.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fd7ff459fb393358d3a155d25b275c60b07a2c83dcd7ea962b1923f5a1134569", size = 146067, upload-time = "2025-08-26T17:44:49.302Z" }, - { url = "https://files.pythonhosted.org/packages/33/ba/29023771f334096f564e48d82ed855a0ed3320389d6748a9c949e25be734/orjson-3.11.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f8d902867b699bcd09c176a280b1acdab57f924489033e53d0afe79817da37e6", size = 135506, upload-time = "2025-08-26T17:44:50.558Z" }, - { url = "https://files.pythonhosted.org/packages/39/62/b5a1eca83f54cb3aa11a9645b8a22f08d97dbd13f27f83aae7c6666a0a05/orjson-3.11.3-cp310-cp310-win32.whl", hash = "sha256:bb93562146120bb51e6b154962d3dadc678ed0fce96513fa6bc06599bb6f6edc", size = 136352, upload-time = "2025-08-26T17:44:51.698Z" }, - { url = "https://files.pythonhosted.org/packages/e3/c0/7ebfaa327d9a9ed982adc0d9420dbce9a3fec45b60ab32c6308f731333fa/orjson-3.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:976c6f1975032cc327161c65d4194c549f2589d88b105a5e3499429a54479770", size = 131539, upload-time = "2025-08-26T17:44:52.974Z" }, - { url = "https://files.pythonhosted.org/packages/cd/8b/360674cd817faef32e49276187922a946468579fcaf37afdfb6c07046e92/orjson-3.11.3-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9d2ae0cc6aeb669633e0124531f342a17d8e97ea999e42f12a5ad4adaa304c5f", size = 238238, upload-time = "2025-08-26T17:44:54.214Z" }, - { url = "https://files.pythonhosted.org/packages/05/3d/5fa9ea4b34c1a13be7d9046ba98d06e6feb1d8853718992954ab59d16625/orjson-3.11.3-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:ba21dbb2493e9c653eaffdc38819b004b7b1b246fb77bfc93dc016fe664eac91", size = 127713, upload-time = "2025-08-26T17:44:55.596Z" }, - { url = "https://files.pythonhosted.org/packages/e5/5f/e18367823925e00b1feec867ff5f040055892fc474bf5f7875649ecfa586/orjson-3.11.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00f1a271e56d511d1569937c0447d7dce5a99a33ea0dec76673706360a051904", size = 123241, upload-time = "2025-08-26T17:44:57.185Z" }, - { url = "https://files.pythonhosted.org/packages/0f/bd/3c66b91c4564759cf9f473251ac1650e446c7ba92a7c0f9f56ed54f9f0e6/orjson-3.11.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b67e71e47caa6680d1b6f075a396d04fa6ca8ca09aafb428731da9b3ea32a5a6", size = 127895, upload-time = "2025-08-26T17:44:58.349Z" }, - { url = "https://files.pythonhosted.org/packages/82/b5/dc8dcd609db4766e2967a85f63296c59d4722b39503e5b0bf7fd340d387f/orjson-3.11.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7d012ebddffcce8c85734a6d9e5f08180cd3857c5f5a3ac70185b43775d043d", size = 130303, upload-time = "2025-08-26T17:44:59.491Z" }, - { url = "https://files.pythonhosted.org/packages/48/c2/d58ec5fd1270b2aa44c862171891adc2e1241bd7dab26c8f46eb97c6c6f1/orjson-3.11.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd759f75d6b8d1b62012b7f5ef9461d03c804f94d539a5515b454ba3a6588038", size = 132366, upload-time = "2025-08-26T17:45:00.654Z" }, - { url = "https://files.pythonhosted.org/packages/73/87/0ef7e22eb8dd1ef940bfe3b9e441db519e692d62ed1aae365406a16d23d0/orjson-3.11.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6890ace0809627b0dff19cfad92d69d0fa3f089d3e359a2a532507bb6ba34efb", size = 135180, upload-time = "2025-08-26T17:45:02.424Z" }, - { url = "https://files.pythonhosted.org/packages/bb/6a/e5bf7b70883f374710ad74faf99bacfc4b5b5a7797c1d5e130350e0e28a3/orjson-3.11.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9d4a5e041ae435b815e568537755773d05dac031fee6a57b4ba70897a44d9d2", size = 132741, upload-time = "2025-08-26T17:45:03.663Z" }, - { url = "https://files.pythonhosted.org/packages/bd/0c/4577fd860b6386ffaa56440e792af01c7882b56d2766f55384b5b0e9d39b/orjson-3.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d68bf97a771836687107abfca089743885fb664b90138d8761cce61d5625d55", size = 131104, upload-time = "2025-08-26T17:45:04.939Z" }, - { url = "https://files.pythonhosted.org/packages/66/4b/83e92b2d67e86d1c33f2ea9411742a714a26de63641b082bdbf3d8e481af/orjson-3.11.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bfc27516ec46f4520b18ef645864cee168d2a027dbf32c5537cb1f3e3c22dac1", size = 403887, upload-time = "2025-08-26T17:45:06.228Z" }, - { url = "https://files.pythonhosted.org/packages/6d/e5/9eea6a14e9b5ceb4a271a1fd2e1dec5f2f686755c0fab6673dc6ff3433f4/orjson-3.11.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f66b001332a017d7945e177e282a40b6997056394e3ed7ddb41fb1813b83e824", size = 145855, upload-time = "2025-08-26T17:45:08.338Z" }, - { url = "https://files.pythonhosted.org/packages/45/78/8d4f5ad0c80ba9bf8ac4d0fc71f93a7d0dc0844989e645e2074af376c307/orjson-3.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:212e67806525d2561efbfe9e799633b17eb668b8964abed6b5319b2f1cfbae1f", size = 135361, upload-time = "2025-08-26T17:45:09.625Z" }, - { url = "https://files.pythonhosted.org/packages/0b/5f/16386970370178d7a9b438517ea3d704efcf163d286422bae3b37b88dbb5/orjson-3.11.3-cp311-cp311-win32.whl", hash = "sha256:6e8e0c3b85575a32f2ffa59de455f85ce002b8bdc0662d6b9c2ed6d80ab5d204", size = 136190, upload-time = "2025-08-26T17:45:10.962Z" }, - { url = "https://files.pythonhosted.org/packages/09/60/db16c6f7a41dd8ac9fb651f66701ff2aeb499ad9ebc15853a26c7c152448/orjson-3.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:6be2f1b5d3dc99a5ce5ce162fc741c22ba9f3443d3dd586e6a1211b7bc87bc7b", size = 131389, upload-time = "2025-08-26T17:45:12.285Z" }, - { url = "https://files.pythonhosted.org/packages/3e/2a/bb811ad336667041dea9b8565c7c9faf2f59b47eb5ab680315eea612ef2e/orjson-3.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:fafb1a99d740523d964b15c8db4eabbfc86ff29f84898262bf6e3e4c9e97e43e", size = 126120, upload-time = "2025-08-26T17:45:13.515Z" }, - { url = "https://files.pythonhosted.org/packages/3d/b0/a7edab2a00cdcb2688e1c943401cb3236323e7bfd2839815c6131a3742f4/orjson-3.11.3-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:8c752089db84333e36d754c4baf19c0e1437012242048439c7e80eb0e6426e3b", size = 238259, upload-time = "2025-08-26T17:45:15.093Z" }, - { url = "https://files.pythonhosted.org/packages/e1/c6/ff4865a9cc398a07a83342713b5932e4dc3cb4bf4bc04e8f83dedfc0d736/orjson-3.11.3-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:9b8761b6cf04a856eb544acdd82fc594b978f12ac3602d6374a7edb9d86fd2c2", size = 127633, upload-time = "2025-08-26T17:45:16.417Z" }, - { url = "https://files.pythonhosted.org/packages/6e/e6/e00bea2d9472f44fe8794f523e548ce0ad51eb9693cf538a753a27b8bda4/orjson-3.11.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b13974dc8ac6ba22feaa867fc19135a3e01a134b4f7c9c28162fed4d615008a", size = 123061, upload-time = "2025-08-26T17:45:17.673Z" }, - { url = "https://files.pythonhosted.org/packages/54/31/9fbb78b8e1eb3ac605467cb846e1c08d0588506028b37f4ee21f978a51d4/orjson-3.11.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f83abab5bacb76d9c821fd5c07728ff224ed0e52d7a71b7b3de822f3df04e15c", size = 127956, upload-time = "2025-08-26T17:45:19.172Z" }, - { url = "https://files.pythonhosted.org/packages/36/88/b0604c22af1eed9f98d709a96302006915cfd724a7ebd27d6dd11c22d80b/orjson-3.11.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6fbaf48a744b94091a56c62897b27c31ee2da93d826aa5b207131a1e13d4064", size = 130790, upload-time = "2025-08-26T17:45:20.586Z" }, - { url = "https://files.pythonhosted.org/packages/0e/9d/1c1238ae9fffbfed51ba1e507731b3faaf6b846126a47e9649222b0fd06f/orjson-3.11.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc779b4f4bba2847d0d2940081a7b6f7b5877e05408ffbb74fa1faf4a136c424", size = 132385, upload-time = "2025-08-26T17:45:22.036Z" }, - { url = "https://files.pythonhosted.org/packages/a3/b5/c06f1b090a1c875f337e21dd71943bc9d84087f7cdf8c6e9086902c34e42/orjson-3.11.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd4b909ce4c50faa2192da6bb684d9848d4510b736b0611b6ab4020ea6fd2d23", size = 135305, upload-time = "2025-08-26T17:45:23.4Z" }, - { url = "https://files.pythonhosted.org/packages/a0/26/5f028c7d81ad2ebbf84414ba6d6c9cac03f22f5cd0d01eb40fb2d6a06b07/orjson-3.11.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:524b765ad888dc5518bbce12c77c2e83dee1ed6b0992c1790cc5fb49bb4b6667", size = 132875, upload-time = "2025-08-26T17:45:25.182Z" }, - { url = "https://files.pythonhosted.org/packages/fe/d4/b8df70d9cfb56e385bf39b4e915298f9ae6c61454c8154a0f5fd7efcd42e/orjson-3.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:84fd82870b97ae3cdcea9d8746e592b6d40e1e4d4527835fc520c588d2ded04f", size = 130940, upload-time = "2025-08-26T17:45:27.209Z" }, - { url = "https://files.pythonhosted.org/packages/da/5e/afe6a052ebc1a4741c792dd96e9f65bf3939d2094e8b356503b68d48f9f5/orjson-3.11.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fbecb9709111be913ae6879b07bafd4b0785b44c1eb5cac8ac76da048b3885a1", size = 403852, upload-time = "2025-08-26T17:45:28.478Z" }, - { url = "https://files.pythonhosted.org/packages/f8/90/7bbabafeb2ce65915e9247f14a56b29c9334003536009ef5b122783fe67e/orjson-3.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9dba358d55aee552bd868de348f4736ca5a4086d9a62e2bfbbeeb5629fe8b0cc", size = 146293, upload-time = "2025-08-26T17:45:29.86Z" }, - { url = "https://files.pythonhosted.org/packages/27/b3/2d703946447da8b093350570644a663df69448c9d9330e5f1d9cce997f20/orjson-3.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eabcf2e84f1d7105f84580e03012270c7e97ecb1fb1618bda395061b2a84a049", size = 135470, upload-time = "2025-08-26T17:45:31.243Z" }, - { url = "https://files.pythonhosted.org/packages/38/70/b14dcfae7aff0e379b0119c8a812f8396678919c431efccc8e8a0263e4d9/orjson-3.11.3-cp312-cp312-win32.whl", hash = "sha256:3782d2c60b8116772aea8d9b7905221437fdf53e7277282e8d8b07c220f96cca", size = 136248, upload-time = "2025-08-26T17:45:32.567Z" }, - { url = "https://files.pythonhosted.org/packages/35/b8/9e3127d65de7fff243f7f3e53f59a531bf6bb295ebe5db024c2503cc0726/orjson-3.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:79b44319268af2eaa3e315b92298de9a0067ade6e6003ddaef72f8e0bedb94f1", size = 131437, upload-time = "2025-08-26T17:45:34.949Z" }, - { url = "https://files.pythonhosted.org/packages/51/92/a946e737d4d8a7fd84a606aba96220043dcc7d6988b9e7551f7f6d5ba5ad/orjson-3.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:0e92a4e83341ef79d835ca21b8bd13e27c859e4e9e4d7b63defc6e58462a3710", size = 125978, upload-time = "2025-08-26T17:45:36.422Z" }, - { url = "https://files.pythonhosted.org/packages/fc/79/8932b27293ad35919571f77cb3693b5906cf14f206ef17546052a241fdf6/orjson-3.11.3-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:af40c6612fd2a4b00de648aa26d18186cd1322330bd3a3cc52f87c699e995810", size = 238127, upload-time = "2025-08-26T17:45:38.146Z" }, - { url = "https://files.pythonhosted.org/packages/1c/82/cb93cd8cf132cd7643b30b6c5a56a26c4e780c7a145db6f83de977b540ce/orjson-3.11.3-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:9f1587f26c235894c09e8b5b7636a38091a9e6e7fe4531937534749c04face43", size = 127494, upload-time = "2025-08-26T17:45:39.57Z" }, - { url = "https://files.pythonhosted.org/packages/a4/b8/2d9eb181a9b6bb71463a78882bcac1027fd29cf62c38a40cc02fc11d3495/orjson-3.11.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61dcdad16da5bb486d7227a37a2e789c429397793a6955227cedbd7252eb5a27", size = 123017, upload-time = "2025-08-26T17:45:40.876Z" }, - { url = "https://files.pythonhosted.org/packages/b4/14/a0e971e72d03b509190232356d54c0f34507a05050bd026b8db2bf2c192c/orjson-3.11.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11c6d71478e2cbea0a709e8a06365fa63da81da6498a53e4c4f065881d21ae8f", size = 127898, upload-time = "2025-08-26T17:45:42.188Z" }, - { url = "https://files.pythonhosted.org/packages/8e/af/dc74536722b03d65e17042cc30ae586161093e5b1f29bccda24765a6ae47/orjson-3.11.3-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff94112e0098470b665cb0ed06efb187154b63649403b8d5e9aedeb482b4548c", size = 130742, upload-time = "2025-08-26T17:45:43.511Z" }, - { url = "https://files.pythonhosted.org/packages/62/e6/7a3b63b6677bce089fe939353cda24a7679825c43a24e49f757805fc0d8a/orjson-3.11.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae8b756575aaa2a855a75192f356bbda11a89169830e1439cfb1a3e1a6dde7be", size = 132377, upload-time = "2025-08-26T17:45:45.525Z" }, - { url = "https://files.pythonhosted.org/packages/fc/cd/ce2ab93e2e7eaf518f0fd15e3068b8c43216c8a44ed82ac2b79ce5cef72d/orjson-3.11.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9416cc19a349c167ef76135b2fe40d03cea93680428efee8771f3e9fb66079d", size = 135313, upload-time = "2025-08-26T17:45:46.821Z" }, - { url = "https://files.pythonhosted.org/packages/d0/b4/f98355eff0bd1a38454209bbc73372ce351ba29933cb3e2eba16c04b9448/orjson-3.11.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b822caf5b9752bc6f246eb08124c3d12bf2175b66ab74bac2ef3bbf9221ce1b2", size = 132908, upload-time = "2025-08-26T17:45:48.126Z" }, - { url = "https://files.pythonhosted.org/packages/eb/92/8f5182d7bc2a1bed46ed960b61a39af8389f0ad476120cd99e67182bfb6d/orjson-3.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:414f71e3bdd5573893bf5ecdf35c32b213ed20aa15536fe2f588f946c318824f", size = 130905, upload-time = "2025-08-26T17:45:49.414Z" }, - { url = "https://files.pythonhosted.org/packages/1a/60/c41ca753ce9ffe3d0f67b9b4c093bdd6e5fdb1bc53064f992f66bb99954d/orjson-3.11.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:828e3149ad8815dc14468f36ab2a4b819237c155ee1370341b91ea4c8672d2ee", size = 403812, upload-time = "2025-08-26T17:45:51.085Z" }, - { url = "https://files.pythonhosted.org/packages/dd/13/e4a4f16d71ce1868860db59092e78782c67082a8f1dc06a3788aef2b41bc/orjson-3.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac9e05f25627ffc714c21f8dfe3a579445a5c392a9c8ae7ba1d0e9fb5333f56e", size = 146277, upload-time = "2025-08-26T17:45:52.851Z" }, - { url = "https://files.pythonhosted.org/packages/8d/8b/bafb7f0afef9344754a3a0597a12442f1b85a048b82108ef2c956f53babd/orjson-3.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e44fbe4000bd321d9f3b648ae46e0196d21577cf66ae684a96ff90b1f7c93633", size = 135418, upload-time = "2025-08-26T17:45:54.806Z" }, - { url = "https://files.pythonhosted.org/packages/60/d4/bae8e4f26afb2c23bea69d2f6d566132584d1c3a5fe89ee8c17b718cab67/orjson-3.11.3-cp313-cp313-win32.whl", hash = "sha256:2039b7847ba3eec1f5886e75e6763a16e18c68a63efc4b029ddf994821e2e66b", size = 136216, upload-time = "2025-08-26T17:45:57.182Z" }, - { url = "https://files.pythonhosted.org/packages/88/76/224985d9f127e121c8cad882cea55f0ebe39f97925de040b75ccd4b33999/orjson-3.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:29be5ac4164aa8bdcba5fa0700a3c9c316b411d8ed9d39ef8a882541bd452fae", size = 131362, upload-time = "2025-08-26T17:45:58.56Z" }, - { url = "https://files.pythonhosted.org/packages/e2/cf/0dce7a0be94bd36d1346be5067ed65ded6adb795fdbe3abd234c8d576d01/orjson-3.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:18bd1435cb1f2857ceb59cfb7de6f92593ef7b831ccd1b9bfb28ca530e539dce", size = 125989, upload-time = "2025-08-26T17:45:59.95Z" }, - { url = "https://files.pythonhosted.org/packages/ef/77/d3b1fef1fc6aaeed4cbf3be2b480114035f4df8fa1a99d2dac1d40d6e924/orjson-3.11.3-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:cf4b81227ec86935568c7edd78352a92e97af8da7bd70bdfdaa0d2e0011a1ab4", size = 238115, upload-time = "2025-08-26T17:46:01.669Z" }, - { url = "https://files.pythonhosted.org/packages/e4/6d/468d21d49bb12f900052edcfbf52c292022d0a323d7828dc6376e6319703/orjson-3.11.3-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:bc8bc85b81b6ac9fc4dae393a8c159b817f4c2c9dee5d12b773bddb3b95fc07e", size = 127493, upload-time = "2025-08-26T17:46:03.466Z" }, - { url = "https://files.pythonhosted.org/packages/67/46/1e2588700d354aacdf9e12cc2d98131fb8ac6f31ca65997bef3863edb8ff/orjson-3.11.3-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:88dcfc514cfd1b0de038443c7b3e6a9797ffb1b3674ef1fd14f701a13397f82d", size = 122998, upload-time = "2025-08-26T17:46:04.803Z" }, - { url = "https://files.pythonhosted.org/packages/3b/94/11137c9b6adb3779f1b34fd98be51608a14b430dbc02c6d41134fbba484c/orjson-3.11.3-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:d61cd543d69715d5fc0a690c7c6f8dcc307bc23abef9738957981885f5f38229", size = 132915, upload-time = "2025-08-26T17:46:06.237Z" }, - { url = "https://files.pythonhosted.org/packages/10/61/dccedcf9e9bcaac09fdabe9eaee0311ca92115699500efbd31950d878833/orjson-3.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2b7b153ed90ababadbef5c3eb39549f9476890d339cf47af563aea7e07db2451", size = 130907, upload-time = "2025-08-26T17:46:07.581Z" }, - { url = "https://files.pythonhosted.org/packages/0e/fd/0e935539aa7b08b3ca0f817d73034f7eb506792aae5ecc3b7c6e679cdf5f/orjson-3.11.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7909ae2460f5f494fecbcd10613beafe40381fd0316e35d6acb5f3a05bfda167", size = 403852, upload-time = "2025-08-26T17:46:08.982Z" }, - { url = "https://files.pythonhosted.org/packages/4a/2b/50ae1a5505cd1043379132fdb2adb8a05f37b3e1ebffe94a5073321966fd/orjson-3.11.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:2030c01cbf77bc67bee7eef1e7e31ecf28649353987775e3583062c752da0077", size = 146309, upload-time = "2025-08-26T17:46:10.576Z" }, - { url = "https://files.pythonhosted.org/packages/cd/1d/a473c158e380ef6f32753b5f39a69028b25ec5be331c2049a2201bde2e19/orjson-3.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a0169ebd1cbd94b26c7a7ad282cf5c2744fce054133f959e02eb5265deae1872", size = 135424, upload-time = "2025-08-26T17:46:12.386Z" }, - { url = "https://files.pythonhosted.org/packages/da/09/17d9d2b60592890ff7382e591aa1d9afb202a266b180c3d4049b1ec70e4a/orjson-3.11.3-cp314-cp314-win32.whl", hash = "sha256:0c6d7328c200c349e3a4c6d8c83e0a5ad029bdc2d417f234152bf34842d0fc8d", size = 136266, upload-time = "2025-08-26T17:46:13.853Z" }, - { url = "https://files.pythonhosted.org/packages/15/58/358f6846410a6b4958b74734727e582ed971e13d335d6c7ce3e47730493e/orjson-3.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:317bbe2c069bbc757b1a2e4105b64aacd3bc78279b66a6b9e51e846e4809f804", size = 131351, upload-time = "2025-08-26T17:46:15.27Z" }, - { url = "https://files.pythonhosted.org/packages/28/01/d6b274a0635be0468d4dbd9cafe80c47105937a0d42434e805e67cd2ed8b/orjson-3.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:e8f6a7a27d7b7bec81bd5924163e9af03d49bbb63013f107b48eb5d16db711bc", size = 125985, upload-time = "2025-08-26T17:46:16.67Z" }, + { url = "https://files.pythonhosted.org/packages/9b/64/4a3cef001c6cd9c64256348d4c13a7b09b857e3e1cbb5185917df67d8ced/orjson-3.11.3-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:29cb1f1b008d936803e2da3d7cba726fc47232c45df531b29edf0b232dd737e7", size = 238600 }, + { url = "https://files.pythonhosted.org/packages/10/ce/0c8c87f54f79d051485903dc46226c4d3220b691a151769156054df4562b/orjson-3.11.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97dceed87ed9139884a55db8722428e27bd8452817fbf1869c58b49fecab1120", size = 123526 }, + { url = "https://files.pythonhosted.org/packages/ef/d0/249497e861f2d438f45b3ab7b7b361484237414945169aa285608f9f7019/orjson-3.11.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:58533f9e8266cb0ac298e259ed7b4d42ed3fa0b78ce76860626164de49e0d467", size = 128075 }, + { url = "https://files.pythonhosted.org/packages/e5/64/00485702f640a0fd56144042a1ea196469f4a3ae93681871564bf74fa996/orjson-3.11.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c212cfdd90512fe722fa9bd620de4d46cda691415be86b2e02243242ae81873", size = 130483 }, + { url = "https://files.pythonhosted.org/packages/64/81/110d68dba3909171bf3f05619ad0cf187b430e64045ae4e0aa7ccfe25b15/orjson-3.11.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff835b5d3e67d9207343effb03760c00335f8b5285bfceefd4dc967b0e48f6a", size = 132539 }, + { url = "https://files.pythonhosted.org/packages/79/92/dba25c22b0ddfafa1e6516a780a00abac28d49f49e7202eb433a53c3e94e/orjson-3.11.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5aa4682912a450c2db89cbd92d356fef47e115dffba07992555542f344d301b", size = 135390 }, + { url = "https://files.pythonhosted.org/packages/44/1d/ca2230fd55edbd87b58a43a19032d63a4b180389a97520cc62c535b726f9/orjson-3.11.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7d18dd34ea2e860553a579df02041845dee0af8985dff7f8661306f95504ddf", size = 132966 }, + { url = "https://files.pythonhosted.org/packages/6e/b9/96bbc8ed3e47e52b487d504bd6861798977445fbc410da6e87e302dc632d/orjson-3.11.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d8b11701bc43be92ea42bd454910437b355dfb63696c06fe953ffb40b5f763b4", size = 131349 }, + { url = "https://files.pythonhosted.org/packages/c4/3c/418fbd93d94b0df71cddf96b7fe5894d64a5d890b453ac365120daec30f7/orjson-3.11.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:90368277087d4af32d38bd55f9da2ff466d25325bf6167c8f382d8ee40cb2bbc", size = 404087 }, + { url = "https://files.pythonhosted.org/packages/5b/a9/2bfd58817d736c2f63608dec0c34857339d423eeed30099b126562822191/orjson-3.11.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fd7ff459fb393358d3a155d25b275c60b07a2c83dcd7ea962b1923f5a1134569", size = 146067 }, + { url = "https://files.pythonhosted.org/packages/33/ba/29023771f334096f564e48d82ed855a0ed3320389d6748a9c949e25be734/orjson-3.11.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f8d902867b699bcd09c176a280b1acdab57f924489033e53d0afe79817da37e6", size = 135506 }, + { url = "https://files.pythonhosted.org/packages/39/62/b5a1eca83f54cb3aa11a9645b8a22f08d97dbd13f27f83aae7c6666a0a05/orjson-3.11.3-cp310-cp310-win32.whl", hash = "sha256:bb93562146120bb51e6b154962d3dadc678ed0fce96513fa6bc06599bb6f6edc", size = 136352 }, + { url = "https://files.pythonhosted.org/packages/e3/c0/7ebfaa327d9a9ed982adc0d9420dbce9a3fec45b60ab32c6308f731333fa/orjson-3.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:976c6f1975032cc327161c65d4194c549f2589d88b105a5e3499429a54479770", size = 131539 }, + { url = "https://files.pythonhosted.org/packages/cd/8b/360674cd817faef32e49276187922a946468579fcaf37afdfb6c07046e92/orjson-3.11.3-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9d2ae0cc6aeb669633e0124531f342a17d8e97ea999e42f12a5ad4adaa304c5f", size = 238238 }, + { url = "https://files.pythonhosted.org/packages/05/3d/5fa9ea4b34c1a13be7d9046ba98d06e6feb1d8853718992954ab59d16625/orjson-3.11.3-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:ba21dbb2493e9c653eaffdc38819b004b7b1b246fb77bfc93dc016fe664eac91", size = 127713 }, + { url = "https://files.pythonhosted.org/packages/e5/5f/e18367823925e00b1feec867ff5f040055892fc474bf5f7875649ecfa586/orjson-3.11.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00f1a271e56d511d1569937c0447d7dce5a99a33ea0dec76673706360a051904", size = 123241 }, + { url = "https://files.pythonhosted.org/packages/0f/bd/3c66b91c4564759cf9f473251ac1650e446c7ba92a7c0f9f56ed54f9f0e6/orjson-3.11.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b67e71e47caa6680d1b6f075a396d04fa6ca8ca09aafb428731da9b3ea32a5a6", size = 127895 }, + { url = "https://files.pythonhosted.org/packages/82/b5/dc8dcd609db4766e2967a85f63296c59d4722b39503e5b0bf7fd340d387f/orjson-3.11.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7d012ebddffcce8c85734a6d9e5f08180cd3857c5f5a3ac70185b43775d043d", size = 130303 }, + { url = "https://files.pythonhosted.org/packages/48/c2/d58ec5fd1270b2aa44c862171891adc2e1241bd7dab26c8f46eb97c6c6f1/orjson-3.11.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd759f75d6b8d1b62012b7f5ef9461d03c804f94d539a5515b454ba3a6588038", size = 132366 }, + { url = "https://files.pythonhosted.org/packages/73/87/0ef7e22eb8dd1ef940bfe3b9e441db519e692d62ed1aae365406a16d23d0/orjson-3.11.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6890ace0809627b0dff19cfad92d69d0fa3f089d3e359a2a532507bb6ba34efb", size = 135180 }, + { url = "https://files.pythonhosted.org/packages/bb/6a/e5bf7b70883f374710ad74faf99bacfc4b5b5a7797c1d5e130350e0e28a3/orjson-3.11.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9d4a5e041ae435b815e568537755773d05dac031fee6a57b4ba70897a44d9d2", size = 132741 }, + { url = "https://files.pythonhosted.org/packages/bd/0c/4577fd860b6386ffaa56440e792af01c7882b56d2766f55384b5b0e9d39b/orjson-3.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d68bf97a771836687107abfca089743885fb664b90138d8761cce61d5625d55", size = 131104 }, + { url = "https://files.pythonhosted.org/packages/66/4b/83e92b2d67e86d1c33f2ea9411742a714a26de63641b082bdbf3d8e481af/orjson-3.11.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bfc27516ec46f4520b18ef645864cee168d2a027dbf32c5537cb1f3e3c22dac1", size = 403887 }, + { url = "https://files.pythonhosted.org/packages/6d/e5/9eea6a14e9b5ceb4a271a1fd2e1dec5f2f686755c0fab6673dc6ff3433f4/orjson-3.11.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f66b001332a017d7945e177e282a40b6997056394e3ed7ddb41fb1813b83e824", size = 145855 }, + { url = "https://files.pythonhosted.org/packages/45/78/8d4f5ad0c80ba9bf8ac4d0fc71f93a7d0dc0844989e645e2074af376c307/orjson-3.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:212e67806525d2561efbfe9e799633b17eb668b8964abed6b5319b2f1cfbae1f", size = 135361 }, + { url = "https://files.pythonhosted.org/packages/0b/5f/16386970370178d7a9b438517ea3d704efcf163d286422bae3b37b88dbb5/orjson-3.11.3-cp311-cp311-win32.whl", hash = "sha256:6e8e0c3b85575a32f2ffa59de455f85ce002b8bdc0662d6b9c2ed6d80ab5d204", size = 136190 }, + { url = "https://files.pythonhosted.org/packages/09/60/db16c6f7a41dd8ac9fb651f66701ff2aeb499ad9ebc15853a26c7c152448/orjson-3.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:6be2f1b5d3dc99a5ce5ce162fc741c22ba9f3443d3dd586e6a1211b7bc87bc7b", size = 131389 }, + { url = "https://files.pythonhosted.org/packages/3e/2a/bb811ad336667041dea9b8565c7c9faf2f59b47eb5ab680315eea612ef2e/orjson-3.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:fafb1a99d740523d964b15c8db4eabbfc86ff29f84898262bf6e3e4c9e97e43e", size = 126120 }, + { url = "https://files.pythonhosted.org/packages/3d/b0/a7edab2a00cdcb2688e1c943401cb3236323e7bfd2839815c6131a3742f4/orjson-3.11.3-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:8c752089db84333e36d754c4baf19c0e1437012242048439c7e80eb0e6426e3b", size = 238259 }, + { url = "https://files.pythonhosted.org/packages/e1/c6/ff4865a9cc398a07a83342713b5932e4dc3cb4bf4bc04e8f83dedfc0d736/orjson-3.11.3-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:9b8761b6cf04a856eb544acdd82fc594b978f12ac3602d6374a7edb9d86fd2c2", size = 127633 }, + { url = "https://files.pythonhosted.org/packages/6e/e6/e00bea2d9472f44fe8794f523e548ce0ad51eb9693cf538a753a27b8bda4/orjson-3.11.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b13974dc8ac6ba22feaa867fc19135a3e01a134b4f7c9c28162fed4d615008a", size = 123061 }, + { url = "https://files.pythonhosted.org/packages/54/31/9fbb78b8e1eb3ac605467cb846e1c08d0588506028b37f4ee21f978a51d4/orjson-3.11.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f83abab5bacb76d9c821fd5c07728ff224ed0e52d7a71b7b3de822f3df04e15c", size = 127956 }, + { url = "https://files.pythonhosted.org/packages/36/88/b0604c22af1eed9f98d709a96302006915cfd724a7ebd27d6dd11c22d80b/orjson-3.11.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6fbaf48a744b94091a56c62897b27c31ee2da93d826aa5b207131a1e13d4064", size = 130790 }, + { url = "https://files.pythonhosted.org/packages/0e/9d/1c1238ae9fffbfed51ba1e507731b3faaf6b846126a47e9649222b0fd06f/orjson-3.11.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc779b4f4bba2847d0d2940081a7b6f7b5877e05408ffbb74fa1faf4a136c424", size = 132385 }, + { url = "https://files.pythonhosted.org/packages/a3/b5/c06f1b090a1c875f337e21dd71943bc9d84087f7cdf8c6e9086902c34e42/orjson-3.11.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd4b909ce4c50faa2192da6bb684d9848d4510b736b0611b6ab4020ea6fd2d23", size = 135305 }, + { url = "https://files.pythonhosted.org/packages/a0/26/5f028c7d81ad2ebbf84414ba6d6c9cac03f22f5cd0d01eb40fb2d6a06b07/orjson-3.11.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:524b765ad888dc5518bbce12c77c2e83dee1ed6b0992c1790cc5fb49bb4b6667", size = 132875 }, + { url = "https://files.pythonhosted.org/packages/fe/d4/b8df70d9cfb56e385bf39b4e915298f9ae6c61454c8154a0f5fd7efcd42e/orjson-3.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:84fd82870b97ae3cdcea9d8746e592b6d40e1e4d4527835fc520c588d2ded04f", size = 130940 }, + { url = "https://files.pythonhosted.org/packages/da/5e/afe6a052ebc1a4741c792dd96e9f65bf3939d2094e8b356503b68d48f9f5/orjson-3.11.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fbecb9709111be913ae6879b07bafd4b0785b44c1eb5cac8ac76da048b3885a1", size = 403852 }, + { url = "https://files.pythonhosted.org/packages/f8/90/7bbabafeb2ce65915e9247f14a56b29c9334003536009ef5b122783fe67e/orjson-3.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9dba358d55aee552bd868de348f4736ca5a4086d9a62e2bfbbeeb5629fe8b0cc", size = 146293 }, + { url = "https://files.pythonhosted.org/packages/27/b3/2d703946447da8b093350570644a663df69448c9d9330e5f1d9cce997f20/orjson-3.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eabcf2e84f1d7105f84580e03012270c7e97ecb1fb1618bda395061b2a84a049", size = 135470 }, + { url = "https://files.pythonhosted.org/packages/38/70/b14dcfae7aff0e379b0119c8a812f8396678919c431efccc8e8a0263e4d9/orjson-3.11.3-cp312-cp312-win32.whl", hash = "sha256:3782d2c60b8116772aea8d9b7905221437fdf53e7277282e8d8b07c220f96cca", size = 136248 }, + { url = "https://files.pythonhosted.org/packages/35/b8/9e3127d65de7fff243f7f3e53f59a531bf6bb295ebe5db024c2503cc0726/orjson-3.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:79b44319268af2eaa3e315b92298de9a0067ade6e6003ddaef72f8e0bedb94f1", size = 131437 }, + { url = "https://files.pythonhosted.org/packages/51/92/a946e737d4d8a7fd84a606aba96220043dcc7d6988b9e7551f7f6d5ba5ad/orjson-3.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:0e92a4e83341ef79d835ca21b8bd13e27c859e4e9e4d7b63defc6e58462a3710", size = 125978 }, + { url = "https://files.pythonhosted.org/packages/fc/79/8932b27293ad35919571f77cb3693b5906cf14f206ef17546052a241fdf6/orjson-3.11.3-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:af40c6612fd2a4b00de648aa26d18186cd1322330bd3a3cc52f87c699e995810", size = 238127 }, + { url = "https://files.pythonhosted.org/packages/1c/82/cb93cd8cf132cd7643b30b6c5a56a26c4e780c7a145db6f83de977b540ce/orjson-3.11.3-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:9f1587f26c235894c09e8b5b7636a38091a9e6e7fe4531937534749c04face43", size = 127494 }, + { url = "https://files.pythonhosted.org/packages/a4/b8/2d9eb181a9b6bb71463a78882bcac1027fd29cf62c38a40cc02fc11d3495/orjson-3.11.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61dcdad16da5bb486d7227a37a2e789c429397793a6955227cedbd7252eb5a27", size = 123017 }, + { url = "https://files.pythonhosted.org/packages/b4/14/a0e971e72d03b509190232356d54c0f34507a05050bd026b8db2bf2c192c/orjson-3.11.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11c6d71478e2cbea0a709e8a06365fa63da81da6498a53e4c4f065881d21ae8f", size = 127898 }, + { url = "https://files.pythonhosted.org/packages/8e/af/dc74536722b03d65e17042cc30ae586161093e5b1f29bccda24765a6ae47/orjson-3.11.3-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff94112e0098470b665cb0ed06efb187154b63649403b8d5e9aedeb482b4548c", size = 130742 }, + { url = "https://files.pythonhosted.org/packages/62/e6/7a3b63b6677bce089fe939353cda24a7679825c43a24e49f757805fc0d8a/orjson-3.11.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae8b756575aaa2a855a75192f356bbda11a89169830e1439cfb1a3e1a6dde7be", size = 132377 }, + { url = "https://files.pythonhosted.org/packages/fc/cd/ce2ab93e2e7eaf518f0fd15e3068b8c43216c8a44ed82ac2b79ce5cef72d/orjson-3.11.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9416cc19a349c167ef76135b2fe40d03cea93680428efee8771f3e9fb66079d", size = 135313 }, + { url = "https://files.pythonhosted.org/packages/d0/b4/f98355eff0bd1a38454209bbc73372ce351ba29933cb3e2eba16c04b9448/orjson-3.11.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b822caf5b9752bc6f246eb08124c3d12bf2175b66ab74bac2ef3bbf9221ce1b2", size = 132908 }, + { url = "https://files.pythonhosted.org/packages/eb/92/8f5182d7bc2a1bed46ed960b61a39af8389f0ad476120cd99e67182bfb6d/orjson-3.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:414f71e3bdd5573893bf5ecdf35c32b213ed20aa15536fe2f588f946c318824f", size = 130905 }, + { url = "https://files.pythonhosted.org/packages/1a/60/c41ca753ce9ffe3d0f67b9b4c093bdd6e5fdb1bc53064f992f66bb99954d/orjson-3.11.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:828e3149ad8815dc14468f36ab2a4b819237c155ee1370341b91ea4c8672d2ee", size = 403812 }, + { url = "https://files.pythonhosted.org/packages/dd/13/e4a4f16d71ce1868860db59092e78782c67082a8f1dc06a3788aef2b41bc/orjson-3.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac9e05f25627ffc714c21f8dfe3a579445a5c392a9c8ae7ba1d0e9fb5333f56e", size = 146277 }, + { url = "https://files.pythonhosted.org/packages/8d/8b/bafb7f0afef9344754a3a0597a12442f1b85a048b82108ef2c956f53babd/orjson-3.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e44fbe4000bd321d9f3b648ae46e0196d21577cf66ae684a96ff90b1f7c93633", size = 135418 }, + { url = "https://files.pythonhosted.org/packages/60/d4/bae8e4f26afb2c23bea69d2f6d566132584d1c3a5fe89ee8c17b718cab67/orjson-3.11.3-cp313-cp313-win32.whl", hash = "sha256:2039b7847ba3eec1f5886e75e6763a16e18c68a63efc4b029ddf994821e2e66b", size = 136216 }, + { url = "https://files.pythonhosted.org/packages/88/76/224985d9f127e121c8cad882cea55f0ebe39f97925de040b75ccd4b33999/orjson-3.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:29be5ac4164aa8bdcba5fa0700a3c9c316b411d8ed9d39ef8a882541bd452fae", size = 131362 }, + { url = "https://files.pythonhosted.org/packages/e2/cf/0dce7a0be94bd36d1346be5067ed65ded6adb795fdbe3abd234c8d576d01/orjson-3.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:18bd1435cb1f2857ceb59cfb7de6f92593ef7b831ccd1b9bfb28ca530e539dce", size = 125989 }, + { url = "https://files.pythonhosted.org/packages/ef/77/d3b1fef1fc6aaeed4cbf3be2b480114035f4df8fa1a99d2dac1d40d6e924/orjson-3.11.3-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:cf4b81227ec86935568c7edd78352a92e97af8da7bd70bdfdaa0d2e0011a1ab4", size = 238115 }, + { url = "https://files.pythonhosted.org/packages/e4/6d/468d21d49bb12f900052edcfbf52c292022d0a323d7828dc6376e6319703/orjson-3.11.3-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:bc8bc85b81b6ac9fc4dae393a8c159b817f4c2c9dee5d12b773bddb3b95fc07e", size = 127493 }, + { url = "https://files.pythonhosted.org/packages/67/46/1e2588700d354aacdf9e12cc2d98131fb8ac6f31ca65997bef3863edb8ff/orjson-3.11.3-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:88dcfc514cfd1b0de038443c7b3e6a9797ffb1b3674ef1fd14f701a13397f82d", size = 122998 }, + { url = "https://files.pythonhosted.org/packages/3b/94/11137c9b6adb3779f1b34fd98be51608a14b430dbc02c6d41134fbba484c/orjson-3.11.3-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:d61cd543d69715d5fc0a690c7c6f8dcc307bc23abef9738957981885f5f38229", size = 132915 }, + { url = "https://files.pythonhosted.org/packages/10/61/dccedcf9e9bcaac09fdabe9eaee0311ca92115699500efbd31950d878833/orjson-3.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2b7b153ed90ababadbef5c3eb39549f9476890d339cf47af563aea7e07db2451", size = 130907 }, + { url = "https://files.pythonhosted.org/packages/0e/fd/0e935539aa7b08b3ca0f817d73034f7eb506792aae5ecc3b7c6e679cdf5f/orjson-3.11.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7909ae2460f5f494fecbcd10613beafe40381fd0316e35d6acb5f3a05bfda167", size = 403852 }, + { url = "https://files.pythonhosted.org/packages/4a/2b/50ae1a5505cd1043379132fdb2adb8a05f37b3e1ebffe94a5073321966fd/orjson-3.11.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:2030c01cbf77bc67bee7eef1e7e31ecf28649353987775e3583062c752da0077", size = 146309 }, + { url = "https://files.pythonhosted.org/packages/cd/1d/a473c158e380ef6f32753b5f39a69028b25ec5be331c2049a2201bde2e19/orjson-3.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a0169ebd1cbd94b26c7a7ad282cf5c2744fce054133f959e02eb5265deae1872", size = 135424 }, + { url = "https://files.pythonhosted.org/packages/da/09/17d9d2b60592890ff7382e591aa1d9afb202a266b180c3d4049b1ec70e4a/orjson-3.11.3-cp314-cp314-win32.whl", hash = "sha256:0c6d7328c200c349e3a4c6d8c83e0a5ad029bdc2d417f234152bf34842d0fc8d", size = 136266 }, + { url = "https://files.pythonhosted.org/packages/15/58/358f6846410a6b4958b74734727e582ed971e13d335d6c7ce3e47730493e/orjson-3.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:317bbe2c069bbc757b1a2e4105b64aacd3bc78279b66a6b9e51e846e4809f804", size = 131351 }, + { url = "https://files.pythonhosted.org/packages/28/01/d6b274a0635be0468d4dbd9cafe80c47105937a0d42434e805e67cd2ed8b/orjson-3.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:e8f6a7a27d7b7bec81bd5924163e9af03d49bbb63013f107b48eb5d16db711bc", size = 125985 }, ] [[package]] name = "packaging" version = "23.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fb/2b/9b9c33ffed44ee921d0967086d653047286054117d584f1b1a7c22ceaf7b/packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", size = 146714, upload-time = "2023-10-01T13:50:05.279Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/2b/9b9c33ffed44ee921d0967086d653047286054117d584f1b1a7c22ceaf7b/packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", size = 146714 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/1a/610693ac4ee14fcdf2d9bf3c493370e4f2ef7ae2e19217d7a237ff42367d/packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7", size = 53011, upload-time = "2023-10-01T13:50:03.745Z" }, + { url = "https://files.pythonhosted.org/packages/ec/1a/610693ac4ee14fcdf2d9bf3c493370e4f2ef7ae2e19217d7a237ff42367d/packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7", size = 53011 }, ] [[package]] name = "pathspec" version = "0.12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, ] [[package]] name = "pillow" version = "10.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cd/74/ad3d526f3bf7b6d3f408b73fde271ec69dfac8b81341a318ce825f2b3812/pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06", size = 46555059, upload-time = "2024-07-01T09:48:43.583Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/74/ad3d526f3bf7b6d3f408b73fde271ec69dfac8b81341a318ce825f2b3812/pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06", size = 46555059 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/69/a31cccd538ca0b5272be2a38347f8839b97a14be104ea08b0db92f749c74/pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e", size = 3509271, upload-time = "2024-07-01T09:45:22.07Z" }, - { url = "https://files.pythonhosted.org/packages/9a/9e/4143b907be8ea0bce215f2ae4f7480027473f8b61fcedfda9d851082a5d2/pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d", size = 3375658, upload-time = "2024-07-01T09:45:25.292Z" }, - { url = "https://files.pythonhosted.org/packages/8a/25/1fc45761955f9359b1169aa75e241551e74ac01a09f487adaaf4c3472d11/pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856", size = 4332075, upload-time = "2024-07-01T09:45:27.94Z" }, - { url = "https://files.pythonhosted.org/packages/5e/dd/425b95d0151e1d6c951f45051112394f130df3da67363b6bc75dc4c27aba/pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f", size = 4444808, upload-time = "2024-07-01T09:45:30.305Z" }, - { url = "https://files.pythonhosted.org/packages/b1/84/9a15cc5726cbbfe7f9f90bfb11f5d028586595907cd093815ca6644932e3/pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b", size = 4356290, upload-time = "2024-07-01T09:45:32.868Z" }, - { url = "https://files.pythonhosted.org/packages/b5/5b/6651c288b08df3b8c1e2f8c1152201e0b25d240e22ddade0f1e242fc9fa0/pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc", size = 4525163, upload-time = "2024-07-01T09:45:35.279Z" }, - { url = "https://files.pythonhosted.org/packages/07/8b/34854bf11a83c248505c8cb0fcf8d3d0b459a2246c8809b967963b6b12ae/pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e", size = 4463100, upload-time = "2024-07-01T09:45:37.74Z" }, - { url = "https://files.pythonhosted.org/packages/78/63/0632aee4e82476d9cbe5200c0cdf9ba41ee04ed77887432845264d81116d/pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46", size = 4592880, upload-time = "2024-07-01T09:45:39.89Z" }, - { url = "https://files.pythonhosted.org/packages/df/56/b8663d7520671b4398b9d97e1ed9f583d4afcbefbda3c6188325e8c297bd/pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984", size = 2235218, upload-time = "2024-07-01T09:45:42.771Z" }, - { url = "https://files.pythonhosted.org/packages/f4/72/0203e94a91ddb4a9d5238434ae6c1ca10e610e8487036132ea9bf806ca2a/pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141", size = 2554487, upload-time = "2024-07-01T09:45:45.176Z" }, - { url = "https://files.pythonhosted.org/packages/bd/52/7e7e93d7a6e4290543f17dc6f7d3af4bd0b3dd9926e2e8a35ac2282bc5f4/pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1", size = 2243219, upload-time = "2024-07-01T09:45:47.274Z" }, - { url = "https://files.pythonhosted.org/packages/a7/62/c9449f9c3043c37f73e7487ec4ef0c03eb9c9afc91a92b977a67b3c0bbc5/pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c", size = 3509265, upload-time = "2024-07-01T09:45:49.812Z" }, - { url = "https://files.pythonhosted.org/packages/f4/5f/491dafc7bbf5a3cc1845dc0430872e8096eb9e2b6f8161509d124594ec2d/pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be", size = 3375655, upload-time = "2024-07-01T09:45:52.462Z" }, - { url = "https://files.pythonhosted.org/packages/73/d5/c4011a76f4207a3c151134cd22a1415741e42fa5ddecec7c0182887deb3d/pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3", size = 4340304, upload-time = "2024-07-01T09:45:55.006Z" }, - { url = "https://files.pythonhosted.org/packages/ac/10/c67e20445a707f7a610699bba4fe050583b688d8cd2d202572b257f46600/pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6", size = 4452804, upload-time = "2024-07-01T09:45:58.437Z" }, - { url = "https://files.pythonhosted.org/packages/a9/83/6523837906d1da2b269dee787e31df3b0acb12e3d08f024965a3e7f64665/pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe", size = 4365126, upload-time = "2024-07-01T09:46:00.713Z" }, - { url = "https://files.pythonhosted.org/packages/ba/e5/8c68ff608a4203085158cff5cc2a3c534ec384536d9438c405ed6370d080/pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319", size = 4533541, upload-time = "2024-07-01T09:46:03.235Z" }, - { url = "https://files.pythonhosted.org/packages/f4/7c/01b8dbdca5bc6785573f4cee96e2358b0918b7b2c7b60d8b6f3abf87a070/pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d", size = 4471616, upload-time = "2024-07-01T09:46:05.356Z" }, - { url = "https://files.pythonhosted.org/packages/c8/57/2899b82394a35a0fbfd352e290945440e3b3785655a03365c0ca8279f351/pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696", size = 4600802, upload-time = "2024-07-01T09:46:08.145Z" }, - { url = "https://files.pythonhosted.org/packages/4d/d7/a44f193d4c26e58ee5d2d9db3d4854b2cfb5b5e08d360a5e03fe987c0086/pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496", size = 2235213, upload-time = "2024-07-01T09:46:10.211Z" }, - { url = "https://files.pythonhosted.org/packages/c1/d0/5866318eec2b801cdb8c82abf190c8343d8a1cd8bf5a0c17444a6f268291/pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91", size = 2554498, upload-time = "2024-07-01T09:46:12.685Z" }, - { url = "https://files.pythonhosted.org/packages/d4/c8/310ac16ac2b97e902d9eb438688de0d961660a87703ad1561fd3dfbd2aa0/pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22", size = 2243219, upload-time = "2024-07-01T09:46:14.83Z" }, - { url = "https://files.pythonhosted.org/packages/05/cb/0353013dc30c02a8be34eb91d25e4e4cf594b59e5a55ea1128fde1e5f8ea/pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94", size = 3509350, upload-time = "2024-07-01T09:46:17.177Z" }, - { url = "https://files.pythonhosted.org/packages/e7/cf/5c558a0f247e0bf9cec92bff9b46ae6474dd736f6d906315e60e4075f737/pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597", size = 3374980, upload-time = "2024-07-01T09:46:19.169Z" }, - { url = "https://files.pythonhosted.org/packages/84/48/6e394b86369a4eb68b8a1382c78dc092245af517385c086c5094e3b34428/pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80", size = 4343799, upload-time = "2024-07-01T09:46:21.883Z" }, - { url = "https://files.pythonhosted.org/packages/3b/f3/a8c6c11fa84b59b9df0cd5694492da8c039a24cd159f0f6918690105c3be/pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca", size = 4459973, upload-time = "2024-07-01T09:46:24.321Z" }, - { url = "https://files.pythonhosted.org/packages/7d/1b/c14b4197b80150fb64453585247e6fb2e1d93761fa0fa9cf63b102fde822/pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef", size = 4370054, upload-time = "2024-07-01T09:46:26.825Z" }, - { url = "https://files.pythonhosted.org/packages/55/77/40daddf677897a923d5d33329acd52a2144d54a9644f2a5422c028c6bf2d/pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a", size = 4539484, upload-time = "2024-07-01T09:46:29.355Z" }, - { url = "https://files.pythonhosted.org/packages/40/54/90de3e4256b1207300fb2b1d7168dd912a2fb4b2401e439ba23c2b2cabde/pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b", size = 4477375, upload-time = "2024-07-01T09:46:31.756Z" }, - { url = "https://files.pythonhosted.org/packages/13/24/1bfba52f44193860918ff7c93d03d95e3f8748ca1de3ceaf11157a14cf16/pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9", size = 4608773, upload-time = "2024-07-01T09:46:33.73Z" }, - { url = "https://files.pythonhosted.org/packages/55/04/5e6de6e6120451ec0c24516c41dbaf80cce1b6451f96561235ef2429da2e/pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42", size = 2235690, upload-time = "2024-07-01T09:46:36.587Z" }, - { url = "https://files.pythonhosted.org/packages/74/0a/d4ce3c44bca8635bd29a2eab5aa181b654a734a29b263ca8efe013beea98/pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a", size = 2554951, upload-time = "2024-07-01T09:46:38.777Z" }, - { url = "https://files.pythonhosted.org/packages/b5/ca/184349ee40f2e92439be9b3502ae6cfc43ac4b50bc4fc6b3de7957563894/pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9", size = 2243427, upload-time = "2024-07-01T09:46:43.15Z" }, - { url = "https://files.pythonhosted.org/packages/c3/00/706cebe7c2c12a6318aabe5d354836f54adff7156fd9e1bd6c89f4ba0e98/pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3", size = 3525685, upload-time = "2024-07-01T09:46:45.194Z" }, - { url = "https://files.pythonhosted.org/packages/cf/76/f658cbfa49405e5ecbfb9ba42d07074ad9792031267e782d409fd8fe7c69/pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb", size = 3374883, upload-time = "2024-07-01T09:46:47.331Z" }, - { url = "https://files.pythonhosted.org/packages/46/2b/99c28c4379a85e65378211971c0b430d9c7234b1ec4d59b2668f6299e011/pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70", size = 4339837, upload-time = "2024-07-01T09:46:49.647Z" }, - { url = "https://files.pythonhosted.org/packages/f1/74/b1ec314f624c0c43711fdf0d8076f82d9d802afd58f1d62c2a86878e8615/pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be", size = 4455562, upload-time = "2024-07-01T09:46:51.811Z" }, - { url = "https://files.pythonhosted.org/packages/4a/2a/4b04157cb7b9c74372fa867096a1607e6fedad93a44deeff553ccd307868/pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0", size = 4366761, upload-time = "2024-07-01T09:46:53.961Z" }, - { url = "https://files.pythonhosted.org/packages/ac/7b/8f1d815c1a6a268fe90481232c98dd0e5fa8c75e341a75f060037bd5ceae/pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc", size = 4536767, upload-time = "2024-07-01T09:46:56.664Z" }, - { url = "https://files.pythonhosted.org/packages/e5/77/05fa64d1f45d12c22c314e7b97398ffb28ef2813a485465017b7978b3ce7/pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a", size = 4477989, upload-time = "2024-07-01T09:46:58.977Z" }, - { url = "https://files.pythonhosted.org/packages/12/63/b0397cfc2caae05c3fb2f4ed1b4fc4fc878f0243510a7a6034ca59726494/pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309", size = 4610255, upload-time = "2024-07-01T09:47:01.189Z" }, - { url = "https://files.pythonhosted.org/packages/7b/f9/cfaa5082ca9bc4a6de66ffe1c12c2d90bf09c309a5f52b27759a596900e7/pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060", size = 2235603, upload-time = "2024-07-01T09:47:03.918Z" }, - { url = "https://files.pythonhosted.org/packages/01/6a/30ff0eef6e0c0e71e55ded56a38d4859bf9d3634a94a88743897b5f96936/pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea", size = 2554972, upload-time = "2024-07-01T09:47:06.152Z" }, - { url = "https://files.pythonhosted.org/packages/48/2c/2e0a52890f269435eee38b21c8218e102c621fe8d8df8b9dd06fabf879ba/pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d", size = 2243375, upload-time = "2024-07-01T09:47:09.065Z" }, - { url = "https://files.pythonhosted.org/packages/38/30/095d4f55f3a053392f75e2eae45eba3228452783bab3d9a920b951ac495c/pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4", size = 3493889, upload-time = "2024-07-01T09:48:04.815Z" }, - { url = "https://files.pythonhosted.org/packages/f3/e8/4ff79788803a5fcd5dc35efdc9386af153569853767bff74540725b45863/pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da", size = 3346160, upload-time = "2024-07-01T09:48:07.206Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ac/4184edd511b14f760c73f5bb8a5d6fd85c591c8aff7c2229677a355c4179/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026", size = 3435020, upload-time = "2024-07-01T09:48:09.66Z" }, - { url = "https://files.pythonhosted.org/packages/da/21/1749cd09160149c0a246a81d646e05f35041619ce76f6493d6a96e8d1103/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e", size = 3490539, upload-time = "2024-07-01T09:48:12.529Z" }, - { url = "https://files.pythonhosted.org/packages/b6/f5/f71fe1888b96083b3f6dfa0709101f61fc9e972c0c8d04e9d93ccef2a045/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5", size = 3476125, upload-time = "2024-07-01T09:48:14.891Z" }, - { url = "https://files.pythonhosted.org/packages/96/b9/c0362c54290a31866c3526848583a2f45a535aa9d725fd31e25d318c805f/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885", size = 3579373, upload-time = "2024-07-01T09:48:17.601Z" }, - { url = "https://files.pythonhosted.org/packages/52/3b/ce7a01026a7cf46e5452afa86f97a5e88ca97f562cafa76570178ab56d8d/pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5", size = 2554661, upload-time = "2024-07-01T09:48:20.293Z" }, + { url = "https://files.pythonhosted.org/packages/0e/69/a31cccd538ca0b5272be2a38347f8839b97a14be104ea08b0db92f749c74/pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e", size = 3509271 }, + { url = "https://files.pythonhosted.org/packages/9a/9e/4143b907be8ea0bce215f2ae4f7480027473f8b61fcedfda9d851082a5d2/pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d", size = 3375658 }, + { url = "https://files.pythonhosted.org/packages/8a/25/1fc45761955f9359b1169aa75e241551e74ac01a09f487adaaf4c3472d11/pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856", size = 4332075 }, + { url = "https://files.pythonhosted.org/packages/5e/dd/425b95d0151e1d6c951f45051112394f130df3da67363b6bc75dc4c27aba/pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f", size = 4444808 }, + { url = "https://files.pythonhosted.org/packages/b1/84/9a15cc5726cbbfe7f9f90bfb11f5d028586595907cd093815ca6644932e3/pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b", size = 4356290 }, + { url = "https://files.pythonhosted.org/packages/b5/5b/6651c288b08df3b8c1e2f8c1152201e0b25d240e22ddade0f1e242fc9fa0/pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc", size = 4525163 }, + { url = "https://files.pythonhosted.org/packages/07/8b/34854bf11a83c248505c8cb0fcf8d3d0b459a2246c8809b967963b6b12ae/pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e", size = 4463100 }, + { url = "https://files.pythonhosted.org/packages/78/63/0632aee4e82476d9cbe5200c0cdf9ba41ee04ed77887432845264d81116d/pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46", size = 4592880 }, + { url = "https://files.pythonhosted.org/packages/df/56/b8663d7520671b4398b9d97e1ed9f583d4afcbefbda3c6188325e8c297bd/pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984", size = 2235218 }, + { url = "https://files.pythonhosted.org/packages/f4/72/0203e94a91ddb4a9d5238434ae6c1ca10e610e8487036132ea9bf806ca2a/pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141", size = 2554487 }, + { url = "https://files.pythonhosted.org/packages/bd/52/7e7e93d7a6e4290543f17dc6f7d3af4bd0b3dd9926e2e8a35ac2282bc5f4/pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1", size = 2243219 }, + { url = "https://files.pythonhosted.org/packages/a7/62/c9449f9c3043c37f73e7487ec4ef0c03eb9c9afc91a92b977a67b3c0bbc5/pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c", size = 3509265 }, + { url = "https://files.pythonhosted.org/packages/f4/5f/491dafc7bbf5a3cc1845dc0430872e8096eb9e2b6f8161509d124594ec2d/pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be", size = 3375655 }, + { url = "https://files.pythonhosted.org/packages/73/d5/c4011a76f4207a3c151134cd22a1415741e42fa5ddecec7c0182887deb3d/pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3", size = 4340304 }, + { url = "https://files.pythonhosted.org/packages/ac/10/c67e20445a707f7a610699bba4fe050583b688d8cd2d202572b257f46600/pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6", size = 4452804 }, + { url = "https://files.pythonhosted.org/packages/a9/83/6523837906d1da2b269dee787e31df3b0acb12e3d08f024965a3e7f64665/pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe", size = 4365126 }, + { url = "https://files.pythonhosted.org/packages/ba/e5/8c68ff608a4203085158cff5cc2a3c534ec384536d9438c405ed6370d080/pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319", size = 4533541 }, + { url = "https://files.pythonhosted.org/packages/f4/7c/01b8dbdca5bc6785573f4cee96e2358b0918b7b2c7b60d8b6f3abf87a070/pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d", size = 4471616 }, + { url = "https://files.pythonhosted.org/packages/c8/57/2899b82394a35a0fbfd352e290945440e3b3785655a03365c0ca8279f351/pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696", size = 4600802 }, + { url = "https://files.pythonhosted.org/packages/4d/d7/a44f193d4c26e58ee5d2d9db3d4854b2cfb5b5e08d360a5e03fe987c0086/pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496", size = 2235213 }, + { url = "https://files.pythonhosted.org/packages/c1/d0/5866318eec2b801cdb8c82abf190c8343d8a1cd8bf5a0c17444a6f268291/pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91", size = 2554498 }, + { url = "https://files.pythonhosted.org/packages/d4/c8/310ac16ac2b97e902d9eb438688de0d961660a87703ad1561fd3dfbd2aa0/pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22", size = 2243219 }, + { url = "https://files.pythonhosted.org/packages/05/cb/0353013dc30c02a8be34eb91d25e4e4cf594b59e5a55ea1128fde1e5f8ea/pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94", size = 3509350 }, + { url = "https://files.pythonhosted.org/packages/e7/cf/5c558a0f247e0bf9cec92bff9b46ae6474dd736f6d906315e60e4075f737/pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597", size = 3374980 }, + { url = "https://files.pythonhosted.org/packages/84/48/6e394b86369a4eb68b8a1382c78dc092245af517385c086c5094e3b34428/pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80", size = 4343799 }, + { url = "https://files.pythonhosted.org/packages/3b/f3/a8c6c11fa84b59b9df0cd5694492da8c039a24cd159f0f6918690105c3be/pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca", size = 4459973 }, + { url = "https://files.pythonhosted.org/packages/7d/1b/c14b4197b80150fb64453585247e6fb2e1d93761fa0fa9cf63b102fde822/pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef", size = 4370054 }, + { url = "https://files.pythonhosted.org/packages/55/77/40daddf677897a923d5d33329acd52a2144d54a9644f2a5422c028c6bf2d/pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a", size = 4539484 }, + { url = "https://files.pythonhosted.org/packages/40/54/90de3e4256b1207300fb2b1d7168dd912a2fb4b2401e439ba23c2b2cabde/pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b", size = 4477375 }, + { url = "https://files.pythonhosted.org/packages/13/24/1bfba52f44193860918ff7c93d03d95e3f8748ca1de3ceaf11157a14cf16/pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9", size = 4608773 }, + { url = "https://files.pythonhosted.org/packages/55/04/5e6de6e6120451ec0c24516c41dbaf80cce1b6451f96561235ef2429da2e/pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42", size = 2235690 }, + { url = "https://files.pythonhosted.org/packages/74/0a/d4ce3c44bca8635bd29a2eab5aa181b654a734a29b263ca8efe013beea98/pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a", size = 2554951 }, + { url = "https://files.pythonhosted.org/packages/b5/ca/184349ee40f2e92439be9b3502ae6cfc43ac4b50bc4fc6b3de7957563894/pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9", size = 2243427 }, + { url = "https://files.pythonhosted.org/packages/c3/00/706cebe7c2c12a6318aabe5d354836f54adff7156fd9e1bd6c89f4ba0e98/pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3", size = 3525685 }, + { url = "https://files.pythonhosted.org/packages/cf/76/f658cbfa49405e5ecbfb9ba42d07074ad9792031267e782d409fd8fe7c69/pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb", size = 3374883 }, + { url = "https://files.pythonhosted.org/packages/46/2b/99c28c4379a85e65378211971c0b430d9c7234b1ec4d59b2668f6299e011/pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70", size = 4339837 }, + { url = "https://files.pythonhosted.org/packages/f1/74/b1ec314f624c0c43711fdf0d8076f82d9d802afd58f1d62c2a86878e8615/pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be", size = 4455562 }, + { url = "https://files.pythonhosted.org/packages/4a/2a/4b04157cb7b9c74372fa867096a1607e6fedad93a44deeff553ccd307868/pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0", size = 4366761 }, + { url = "https://files.pythonhosted.org/packages/ac/7b/8f1d815c1a6a268fe90481232c98dd0e5fa8c75e341a75f060037bd5ceae/pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc", size = 4536767 }, + { url = "https://files.pythonhosted.org/packages/e5/77/05fa64d1f45d12c22c314e7b97398ffb28ef2813a485465017b7978b3ce7/pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a", size = 4477989 }, + { url = "https://files.pythonhosted.org/packages/12/63/b0397cfc2caae05c3fb2f4ed1b4fc4fc878f0243510a7a6034ca59726494/pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309", size = 4610255 }, + { url = "https://files.pythonhosted.org/packages/7b/f9/cfaa5082ca9bc4a6de66ffe1c12c2d90bf09c309a5f52b27759a596900e7/pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060", size = 2235603 }, + { url = "https://files.pythonhosted.org/packages/01/6a/30ff0eef6e0c0e71e55ded56a38d4859bf9d3634a94a88743897b5f96936/pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea", size = 2554972 }, + { url = "https://files.pythonhosted.org/packages/48/2c/2e0a52890f269435eee38b21c8218e102c621fe8d8df8b9dd06fabf879ba/pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d", size = 2243375 }, + { url = "https://files.pythonhosted.org/packages/38/30/095d4f55f3a053392f75e2eae45eba3228452783bab3d9a920b951ac495c/pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4", size = 3493889 }, + { url = "https://files.pythonhosted.org/packages/f3/e8/4ff79788803a5fcd5dc35efdc9386af153569853767bff74540725b45863/pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da", size = 3346160 }, + { url = "https://files.pythonhosted.org/packages/d7/ac/4184edd511b14f760c73f5bb8a5d6fd85c591c8aff7c2229677a355c4179/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026", size = 3435020 }, + { url = "https://files.pythonhosted.org/packages/da/21/1749cd09160149c0a246a81d646e05f35041619ce76f6493d6a96e8d1103/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e", size = 3490539 }, + { url = "https://files.pythonhosted.org/packages/b6/f5/f71fe1888b96083b3f6dfa0709101f61fc9e972c0c8d04e9d93ccef2a045/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5", size = 3476125 }, + { url = "https://files.pythonhosted.org/packages/96/b9/c0362c54290a31866c3526848583a2f45a535aa9d725fd31e25d318c805f/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885", size = 3579373 }, + { url = "https://files.pythonhosted.org/packages/52/3b/ce7a01026a7cf46e5452afa86f97a5e88ca97f562cafa76570178ab56d8d/pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5", size = 2554661 }, ] [[package]] name = "platformdirs" version = "4.3.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291, upload-time = "2025-03-19T20:36:10.989Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499, upload-time = "2025-03-19T20:36:09.038Z" }, + { url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499 }, ] [[package]] name = "pluggy" version = "1.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, ] [[package]] @@ -2044,46 +2067,78 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "wcwidth" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e1/c0/5e9c4d2a643a00a6f67578ef35485173de273a4567279e4f0c200c01386b/prettytable-3.9.0.tar.gz", hash = "sha256:f4ed94803c23073a90620b201965e5dc0bccf1760b7a7eaf3158cab8aaffdf34", size = 47874, upload-time = "2023-09-11T14:04:14.548Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/c0/5e9c4d2a643a00a6f67578ef35485173de273a4567279e4f0c200c01386b/prettytable-3.9.0.tar.gz", hash = "sha256:f4ed94803c23073a90620b201965e5dc0bccf1760b7a7eaf3158cab8aaffdf34", size = 47874 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/81/316b6a55a0d1f327d04cc7b0ba9d04058cb62de6c3a4d4b0df280cbe3b0b/prettytable-3.9.0-py3-none-any.whl", hash = "sha256:a71292ab7769a5de274b146b276ce938786f56c31cf7cea88b6f3775d82fe8c8", size = 27772, upload-time = "2023-09-11T14:03:45.582Z" }, + { url = "https://files.pythonhosted.org/packages/4d/81/316b6a55a0d1f327d04cc7b0ba9d04058cb62de6c3a4d4b0df280cbe3b0b/prettytable-3.9.0-py3-none-any.whl", hash = "sha256:a71292ab7769a5de274b146b276ce938786f56c31cf7cea88b6f3775d82fe8c8", size = 27772 }, ] [[package]] name = "protobuf" version = "4.25.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/db/a5/05ea470f4e793c9408bc975ce1c6957447e3134ce7f7a58c13be8b2c216f/protobuf-4.25.2.tar.gz", hash = "sha256:fe599e175cb347efc8ee524bcd4b902d11f7262c0e569ececcb89995c15f0a5e", size = 380282, upload-time = "2024-01-10T19:37:42.958Z" } +sdist = { url = "https://files.pythonhosted.org/packages/db/a5/05ea470f4e793c9408bc975ce1c6957447e3134ce7f7a58c13be8b2c216f/protobuf-4.25.2.tar.gz", hash = "sha256:fe599e175cb347efc8ee524bcd4b902d11f7262c0e569ececcb89995c15f0a5e", size = 380282 } wheels = [ - { url = "https://files.pythonhosted.org/packages/36/2f/01f63896ddf22cbb0173ab51f54fde70b0208ca6c2f5e8416950977930e1/protobuf-4.25.2-cp310-abi3-win32.whl", hash = "sha256:b50c949608682b12efb0b2717f53256f03636af5f60ac0c1d900df6213910fd6", size = 392408, upload-time = "2024-01-10T19:37:23.466Z" }, - { url = "https://files.pythonhosted.org/packages/c1/00/c3ae19cabb36cfabc94ff0b102aac21b471c9f91a1357f8aafffb9efe8e0/protobuf-4.25.2-cp310-abi3-win_amd64.whl", hash = "sha256:8f62574857ee1de9f770baf04dde4165e30b15ad97ba03ceac65f760ff018ac9", size = 413397, upload-time = "2024-01-10T19:37:26.321Z" }, - { url = "https://files.pythonhosted.org/packages/b3/81/0017aefacf23273d4efd1154ef958a27eed9c177c4cc09d2d4ba398fb47f/protobuf-4.25.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:2db9f8fa64fbdcdc93767d3cf81e0f2aef176284071507e3ede160811502fd3d", size = 394159, upload-time = "2024-01-10T19:37:28.932Z" }, - { url = "https://files.pythonhosted.org/packages/23/17/405ba44f60a693dfe96c7a18e843707cffa0fcfad80bd8fc4f227f499ea5/protobuf-4.25.2-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:10894a2885b7175d3984f2be8d9850712c57d5e7587a2410720af8be56cdaf62", size = 293698, upload-time = "2024-01-10T19:37:30.666Z" }, - { url = "https://files.pythonhosted.org/packages/81/9e/63501b8d5b4e40c7260049836bd15ec3270c936e83bc57b85e4603cc212c/protobuf-4.25.2-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:fc381d1dd0516343f1440019cedf08a7405f791cd49eef4ae1ea06520bc1c020", size = 294609, upload-time = "2024-01-10T19:37:32.777Z" }, - { url = "https://files.pythonhosted.org/packages/ff/52/5d23df1fe3b368133ec3e2436fb3dd4ccedf44c8d5ac7f4a88087c75180b/protobuf-4.25.2-py3-none-any.whl", hash = "sha256:a8b7a98d4ce823303145bf3c1a8bdb0f2f4642a414b196f04ad9853ed0c8f830", size = 156463, upload-time = "2024-01-10T19:37:41.24Z" }, + { url = "https://files.pythonhosted.org/packages/36/2f/01f63896ddf22cbb0173ab51f54fde70b0208ca6c2f5e8416950977930e1/protobuf-4.25.2-cp310-abi3-win32.whl", hash = "sha256:b50c949608682b12efb0b2717f53256f03636af5f60ac0c1d900df6213910fd6", size = 392408 }, + { url = "https://files.pythonhosted.org/packages/c1/00/c3ae19cabb36cfabc94ff0b102aac21b471c9f91a1357f8aafffb9efe8e0/protobuf-4.25.2-cp310-abi3-win_amd64.whl", hash = "sha256:8f62574857ee1de9f770baf04dde4165e30b15ad97ba03ceac65f760ff018ac9", size = 413397 }, + { url = "https://files.pythonhosted.org/packages/b3/81/0017aefacf23273d4efd1154ef958a27eed9c177c4cc09d2d4ba398fb47f/protobuf-4.25.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:2db9f8fa64fbdcdc93767d3cf81e0f2aef176284071507e3ede160811502fd3d", size = 394159 }, + { url = "https://files.pythonhosted.org/packages/23/17/405ba44f60a693dfe96c7a18e843707cffa0fcfad80bd8fc4f227f499ea5/protobuf-4.25.2-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:10894a2885b7175d3984f2be8d9850712c57d5e7587a2410720af8be56cdaf62", size = 293698 }, + { url = "https://files.pythonhosted.org/packages/81/9e/63501b8d5b4e40c7260049836bd15ec3270c936e83bc57b85e4603cc212c/protobuf-4.25.2-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:fc381d1dd0516343f1440019cedf08a7405f791cd49eef4ae1ea06520bc1c020", size = 294609 }, + { url = "https://files.pythonhosted.org/packages/ff/52/5d23df1fe3b368133ec3e2436fb3dd4ccedf44c8d5ac7f4a88087c75180b/protobuf-4.25.2-py3-none-any.whl", hash = "sha256:a8b7a98d4ce823303145bf3c1a8bdb0f2f4642a414b196f04ad9853ed0c8f830", size = 156463 }, ] [[package]] name = "psutil" version = "5.9.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a0/d0/c9ae661a302931735237791f04cb7086ac244377f78692ba3b3eae3a9619/psutil-5.9.7.tar.gz", hash = "sha256:3f02134e82cfb5d089fddf20bb2e03fd5cd52395321d1c8458a9e58500ff417c", size = 498429, upload-time = "2023-12-17T11:25:21.22Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a0/d0/c9ae661a302931735237791f04cb7086ac244377f78692ba3b3eae3a9619/psutil-5.9.7.tar.gz", hash = "sha256:3f02134e82cfb5d089fddf20bb2e03fd5cd52395321d1c8458a9e58500ff417c", size = 498429 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/63/86a4ccc640b4ee1193800f57bbd20b766853c0cdbdbb248a27cdfafe6cbf/psutil-5.9.7-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ea36cc62e69a13ec52b2f625c27527f6e4479bca2b340b7a452af55b34fcbe2e", size = 245972, upload-time = "2023-12-17T11:25:48.202Z" }, - { url = "https://files.pythonhosted.org/packages/58/80/cc6666b3968646f2d94de66bbc63d701d501f4aa04de43dd7d1f5dc477dd/psutil-5.9.7-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1132704b876e58d277168cd729d64750633d5ff0183acf5b3c986b8466cd0284", size = 282514, upload-time = "2023-12-17T11:25:51.371Z" }, - { url = "https://files.pythonhosted.org/packages/be/fa/f1f626620e3b47e6237dcc64cb8cc1472f139e99422e5b9fa5bbcf457f48/psutil-5.9.7-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe8b7f07948f1304497ce4f4684881250cd859b16d06a1dc4d7941eeb6233bfe", size = 285469, upload-time = "2023-12-17T11:25:54.25Z" }, - { url = "https://files.pythonhosted.org/packages/7c/b8/dc6ebfc030b47cccc5f5229eeb15e64142b4782796c3ce169ccd60b4d511/psutil-5.9.7-cp37-abi3-win32.whl", hash = "sha256:c727ca5a9b2dd5193b8644b9f0c883d54f1248310023b5ad3e92036c5e2ada68", size = 248406, upload-time = "2023-12-17T12:38:50.326Z" }, - { url = "https://files.pythonhosted.org/packages/50/28/92b74d95dd991c837813ffac0c79a581a3d129eb0fa7c1dd616d9901e0f3/psutil-5.9.7-cp37-abi3-win_amd64.whl", hash = "sha256:f37f87e4d73b79e6c5e749440c3113b81d1ee7d26f21c19c47371ddea834f414", size = 252245, upload-time = "2023-12-17T12:39:00.686Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8a/000d0e80156f0b96c55bda6c60f5ed6543d7b5e893ccab83117e50de1400/psutil-5.9.7-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:032f4f2c909818c86cea4fe2cc407f1c0f0cde8e6c6d702b28b8ce0c0d143340", size = 246739, upload-time = "2023-12-17T11:25:57.305Z" }, + { url = "https://files.pythonhosted.org/packages/6c/63/86a4ccc640b4ee1193800f57bbd20b766853c0cdbdbb248a27cdfafe6cbf/psutil-5.9.7-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ea36cc62e69a13ec52b2f625c27527f6e4479bca2b340b7a452af55b34fcbe2e", size = 245972 }, + { url = "https://files.pythonhosted.org/packages/58/80/cc6666b3968646f2d94de66bbc63d701d501f4aa04de43dd7d1f5dc477dd/psutil-5.9.7-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1132704b876e58d277168cd729d64750633d5ff0183acf5b3c986b8466cd0284", size = 282514 }, + { url = "https://files.pythonhosted.org/packages/be/fa/f1f626620e3b47e6237dcc64cb8cc1472f139e99422e5b9fa5bbcf457f48/psutil-5.9.7-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe8b7f07948f1304497ce4f4684881250cd859b16d06a1dc4d7941eeb6233bfe", size = 285469 }, + { url = "https://files.pythonhosted.org/packages/7c/b8/dc6ebfc030b47cccc5f5229eeb15e64142b4782796c3ce169ccd60b4d511/psutil-5.9.7-cp37-abi3-win32.whl", hash = "sha256:c727ca5a9b2dd5193b8644b9f0c883d54f1248310023b5ad3e92036c5e2ada68", size = 248406 }, + { url = "https://files.pythonhosted.org/packages/50/28/92b74d95dd991c837813ffac0c79a581a3d129eb0fa7c1dd616d9901e0f3/psutil-5.9.7-cp37-abi3-win_amd64.whl", hash = "sha256:f37f87e4d73b79e6c5e749440c3113b81d1ee7d26f21c19c47371ddea834f414", size = 252245 }, + { url = "https://files.pythonhosted.org/packages/ba/8a/000d0e80156f0b96c55bda6c60f5ed6543d7b5e893ccab83117e50de1400/psutil-5.9.7-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:032f4f2c909818c86cea4fe2cc407f1c0f0cde8e6c6d702b28b8ce0c0d143340", size = 246739 }, +] + +[[package]] +name = "pyclipper" +version = "1.3.0.post6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/b2/550fe500e49c464d73fabcb8cb04d47e4885d6ca4cfc1f5b0a125a95b19a/pyclipper-1.3.0.post6.tar.gz", hash = "sha256:42bff0102fa7a7f2abdd795a2594654d62b786d0c6cd67b72d469114fdeb608c", size = 165909 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/34/0dca299fe41e9a92e78735502fed5238a4ac734755e624488df9b2eeec46/pyclipper-1.3.0.post6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fa0f5e78cfa8262277bb3d0225537b3c2a90ef68fd90a229d5d24cf49955dcf4", size = 269504 }, + { url = "https://files.pythonhosted.org/packages/8a/5b/81528b08134b3c2abdfae821e1eff975c0703802d41974b02dfb2e101c55/pyclipper-1.3.0.post6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a01f182d8938c1dc515e8508ed2442f7eebd2c25c7d5cb29281f583c1a8008a4", size = 142599 }, + { url = "https://files.pythonhosted.org/packages/84/a4/3e304f6c0d000382cd54d4a1e5f0d8fc28e1ae97413a2ec1016a7b840319/pyclipper-1.3.0.post6-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:640f20975727994d4abacd07396f564e9e5665ba5cb66ceb36b300c281f84fa4", size = 912209 }, + { url = "https://files.pythonhosted.org/packages/f5/6a/28ec55cc3f972368b211fca017e081cf5a71009d1b8ec3559767cda5b289/pyclipper-1.3.0.post6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a63002f6bb0f1efa87c0b81634cbb571066f237067e23707dabf746306c92ba5", size = 929511 }, + { url = "https://files.pythonhosted.org/packages/c4/56/c326f3454c5f30a31f58a5c3154d891fce58ad73ccbf1d3f4aacfcbd344d/pyclipper-1.3.0.post6-cp310-cp310-win32.whl", hash = "sha256:106b8622cd9fb07d80cbf9b1d752334c55839203bae962376a8c59087788af26", size = 100126 }, + { url = "https://files.pythonhosted.org/packages/f8/e6/f8239af6346848b20a3448c554782fe59298ab06c1d040490242dc7e3c26/pyclipper-1.3.0.post6-cp310-cp310-win_amd64.whl", hash = "sha256:9699e98862dadefd0bea2360c31fa61ca553c660cbf6fb44993acde1b959f58f", size = 110470 }, + { url = "https://files.pythonhosted.org/packages/50/a9/66ca5f252dcac93ca076698591b838ba17f9729591edf4b74fef7fbe1414/pyclipper-1.3.0.post6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4247e7c44b34c87acbf38f99d48fb1acaf5da4a2cf4dcd601a9b24d431be4ef", size = 270930 }, + { url = "https://files.pythonhosted.org/packages/59/fe/2ab5818b3504e179086e54a37ecc245525d069267b8c31b18ec3d0830cbf/pyclipper-1.3.0.post6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:851b3e58106c62a5534a1201295fe20c21714dee2eda68081b37ddb0367e6caa", size = 143411 }, + { url = "https://files.pythonhosted.org/packages/09/f7/b58794f643e033a6d14da7c70f517315c3072f3c5fccdf4232fa8c8090c1/pyclipper-1.3.0.post6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16cc1705a915896d2aff52131c427df02265631279eac849ebda766432714cc0", size = 951754 }, + { url = "https://files.pythonhosted.org/packages/c1/77/846a21957cd4ed266c36705ee340beaa923eb57d2bba013cfd7a5c417cfd/pyclipper-1.3.0.post6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace1f0753cf71c5c5f6488b8feef5dd0fa8b976ad86b24bb51f708f513df4aac", size = 969608 }, + { url = "https://files.pythonhosted.org/packages/c9/2b/580703daa6606d160caf596522d4cfdf62ae619b062a7ce6f905821a57e8/pyclipper-1.3.0.post6-cp311-cp311-win32.whl", hash = "sha256:dbc828641667142751b1127fd5c4291663490cf05689c85be4c5bcc89aaa236a", size = 100227 }, + { url = "https://files.pythonhosted.org/packages/17/4b/a4cda18e8556d913ff75052585eb0d658500596b5f97fe8401d05123d47b/pyclipper-1.3.0.post6-cp311-cp311-win_amd64.whl", hash = "sha256:1c03f1ae43b18ee07730c3c774cc3cf88a10c12a4b097239b33365ec24a0a14a", size = 110442 }, + { url = "https://files.pythonhosted.org/packages/fc/c8/197d9a1d8354922d24d11d22fb2e0cc1ebc182f8a30496b7ddbe89467ce1/pyclipper-1.3.0.post6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6363b9d79ba1b5d8f32d1623e797c1e9f994600943402e68d5266067bdde173e", size = 270487 }, + { url = "https://files.pythonhosted.org/packages/8e/8e/eb14eadf054494ad81446e21c4ea163b941747610b0eb9051644395f567e/pyclipper-1.3.0.post6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:32cd7fb9c1c893eb87f82a072dbb5e26224ea7cebbad9dc306d67e1ac62dd229", size = 143469 }, + { url = "https://files.pythonhosted.org/packages/cf/e5/6c4a8df6e904c133bb4c5309d211d31c751db60cbd36a7250c02b05494a1/pyclipper-1.3.0.post6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3aab10e3c10ed8fa60c608fb87c040089b83325c937f98f06450cf9fcfdaf1d", size = 944206 }, + { url = "https://files.pythonhosted.org/packages/76/65/cb014acc41cd5bf6bbfa4671c7faffffb9cee01706642c2dec70c5209ac8/pyclipper-1.3.0.post6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58eae2ff92a8cae1331568df076c4c5775bf946afab0068b217f0cf8e188eb3c", size = 963797 }, + { url = "https://files.pythonhosted.org/packages/80/ec/b40cd81ab7598984167508a5369a2fa31a09fe3b3e3d0b73aa50e06d4b3f/pyclipper-1.3.0.post6-cp312-cp312-win32.whl", hash = "sha256:793b0aa54b914257aa7dc76b793dd4dcfb3c84011d48df7e41ba02b571616eaf", size = 99456 }, + { url = "https://files.pythonhosted.org/packages/24/3a/7d6292e3c94fb6b872d8d7e80d909dc527ee6b0af73b753c63fdde65a7da/pyclipper-1.3.0.post6-cp312-cp312-win_amd64.whl", hash = "sha256:d3f9da96f83b8892504923beb21a481cd4516c19be1d39eb57a92ef1c9a29548", size = 110278 }, + { url = "https://files.pythonhosted.org/packages/8c/b3/75232906bd13f869600d23bdb8fe6903cc899fa7e96981ae4c9b7d9c409e/pyclipper-1.3.0.post6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f129284d2c7bcd213d11c0f35e1ae506a1144ce4954e9d1734d63b120b0a1b58", size = 268254 }, + { url = "https://files.pythonhosted.org/packages/0b/db/35843050a3dd7586781497a21ca6c8d48111afb66061cb40c3d3c288596d/pyclipper-1.3.0.post6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:188fbfd1d30d02247f92c25ce856f5f3c75d841251f43367dbcf10935bc48f38", size = 142204 }, + { url = "https://files.pythonhosted.org/packages/7c/d7/1faa0ff35caa02cb32cb0583688cded3f38788f33e02bfe6461fbcc1bee1/pyclipper-1.3.0.post6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6d129d0c2587f2f5904d201a4021f859afbb45fada4261c9fdedb2205b09d23", size = 943835 }, + { url = "https://files.pythonhosted.org/packages/31/10/c0bf140bee2844e2c0617fdcc8a4e8daf98e71710046b06034e6f1963404/pyclipper-1.3.0.post6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c9c80b5c46eef38ba3f12dd818dc87f5f2a0853ba914b6f91b133232315f526", size = 962510 }, + { url = "https://files.pythonhosted.org/packages/85/6f/8c6afc49b51b1bf16d5903ecd5aee657cf88f52c83cb5fabf771deeba728/pyclipper-1.3.0.post6-cp313-cp313-win32.whl", hash = "sha256:b15113ec4fc423b58e9ae80aa95cf5a0802f02d8f02a98a46af3d7d66ff0cc0e", size = 98836 }, + { url = "https://files.pythonhosted.org/packages/d5/19/9ff4551b42f2068686c50c0d199072fa67aee57fc5cf86770cacf71efda3/pyclipper-1.3.0.post6-cp313-cp313-win_amd64.whl", hash = "sha256:e5ff68fa770ac654c7974fc78792978796f068bd274e95930c0691c31e192889", size = 109672 }, ] [[package]] name = "pycparser" version = "2.21" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5e/0b/95d387f5f4433cb0f53ff7ad859bd2c6051051cebbb564f139a999ab46de/pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206", size = 170877, upload-time = "2021-11-06T12:48:46.095Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/0b/95d387f5f4433cb0f53ff7ad859bd2c6051051cebbb564f139a999ab46de/pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206", size = 170877 } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/d5/5f610ebe421e85889f2e55e33b7f9a6795bd982198517d912eb1c76e1a53/pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", size = 118697, upload-time = "2021-11-06T12:50:13.61Z" }, + { url = "https://files.pythonhosted.org/packages/62/d5/5f610ebe421e85889f2e55e33b7f9a6795bd982198517d912eb1c76e1a53/pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", size = 118697 }, ] [[package]] @@ -2096,9 +2151,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } +sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782 }, ] [[package]] @@ -2108,84 +2163,84 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/92/b31726561b5dae176c2d2c2dc43a9c5bfba5d32f96f8b4c0a600dd492447/pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8", size = 2028817, upload-time = "2025-04-23T18:30:43.919Z" }, - { url = "https://files.pythonhosted.org/packages/a3/44/3f0b95fafdaca04a483c4e685fe437c6891001bf3ce8b2fded82b9ea3aa1/pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d", size = 1861357, upload-time = "2025-04-23T18:30:46.372Z" }, - { url = "https://files.pythonhosted.org/packages/30/97/e8f13b55766234caae05372826e8e4b3b96e7b248be3157f53237682e43c/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d", size = 1898011, upload-time = "2025-04-23T18:30:47.591Z" }, - { url = "https://files.pythonhosted.org/packages/9b/a3/99c48cf7bafc991cc3ee66fd544c0aae8dc907b752f1dad2d79b1b5a471f/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572", size = 1982730, upload-time = "2025-04-23T18:30:49.328Z" }, - { url = "https://files.pythonhosted.org/packages/de/8e/a5b882ec4307010a840fb8b58bd9bf65d1840c92eae7534c7441709bf54b/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02", size = 2136178, upload-time = "2025-04-23T18:30:50.907Z" }, - { url = "https://files.pythonhosted.org/packages/e4/bb/71e35fc3ed05af6834e890edb75968e2802fe98778971ab5cba20a162315/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b", size = 2736462, upload-time = "2025-04-23T18:30:52.083Z" }, - { url = "https://files.pythonhosted.org/packages/31/0d/c8f7593e6bc7066289bbc366f2235701dcbebcd1ff0ef8e64f6f239fb47d/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2", size = 2005652, upload-time = "2025-04-23T18:30:53.389Z" }, - { url = "https://files.pythonhosted.org/packages/d2/7a/996d8bd75f3eda405e3dd219ff5ff0a283cd8e34add39d8ef9157e722867/pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a", size = 2113306, upload-time = "2025-04-23T18:30:54.661Z" }, - { url = "https://files.pythonhosted.org/packages/ff/84/daf2a6fb2db40ffda6578a7e8c5a6e9c8affb251a05c233ae37098118788/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac", size = 2073720, upload-time = "2025-04-23T18:30:56.11Z" }, - { url = "https://files.pythonhosted.org/packages/77/fb/2258da019f4825128445ae79456a5499c032b55849dbd5bed78c95ccf163/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a", size = 2244915, upload-time = "2025-04-23T18:30:57.501Z" }, - { url = "https://files.pythonhosted.org/packages/d8/7a/925ff73756031289468326e355b6fa8316960d0d65f8b5d6b3a3e7866de7/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b", size = 2241884, upload-time = "2025-04-23T18:30:58.867Z" }, - { url = "https://files.pythonhosted.org/packages/0b/b0/249ee6d2646f1cdadcb813805fe76265745c4010cf20a8eba7b0e639d9b2/pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22", size = 1910496, upload-time = "2025-04-23T18:31:00.078Z" }, - { url = "https://files.pythonhosted.org/packages/66/ff/172ba8f12a42d4b552917aa65d1f2328990d3ccfc01d5b7c943ec084299f/pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640", size = 1955019, upload-time = "2025-04-23T18:31:01.335Z" }, - { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584, upload-time = "2025-04-23T18:31:03.106Z" }, - { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071, upload-time = "2025-04-23T18:31:04.621Z" }, - { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823, upload-time = "2025-04-23T18:31:06.377Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792, upload-time = "2025-04-23T18:31:07.93Z" }, - { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338, upload-time = "2025-04-23T18:31:09.283Z" }, - { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998, upload-time = "2025-04-23T18:31:11.7Z" }, - { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200, upload-time = "2025-04-23T18:31:13.536Z" }, - { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890, upload-time = "2025-04-23T18:31:15.011Z" }, - { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359, upload-time = "2025-04-23T18:31:16.393Z" }, - { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883, upload-time = "2025-04-23T18:31:17.892Z" }, - { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074, upload-time = "2025-04-23T18:31:19.205Z" }, - { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538, upload-time = "2025-04-23T18:31:20.541Z" }, - { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909, upload-time = "2025-04-23T18:31:22.371Z" }, - { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786, upload-time = "2025-04-23T18:31:24.161Z" }, - { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, - { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, - { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, - { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, - { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, - { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, - { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, - { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, - { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, - { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, - { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, - { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, - { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, - { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, - { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, - { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, - { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, - { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, - { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, - { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, - { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, - { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, - { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, - { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, - { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, - { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, - { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, - { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, - { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, - { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, - { url = "https://files.pythonhosted.org/packages/30/68/373d55e58b7e83ce371691f6eaa7175e3a24b956c44628eb25d7da007917/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa", size = 2023982, upload-time = "2025-04-23T18:32:53.14Z" }, - { url = "https://files.pythonhosted.org/packages/a4/16/145f54ac08c96a63d8ed6442f9dec17b2773d19920b627b18d4f10a061ea/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29", size = 1858412, upload-time = "2025-04-23T18:32:55.52Z" }, - { url = "https://files.pythonhosted.org/packages/41/b1/c6dc6c3e2de4516c0bb2c46f6a373b91b5660312342a0cf5826e38ad82fa/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d", size = 1892749, upload-time = "2025-04-23T18:32:57.546Z" }, - { url = "https://files.pythonhosted.org/packages/12/73/8cd57e20afba760b21b742106f9dbdfa6697f1570b189c7457a1af4cd8a0/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e", size = 2067527, upload-time = "2025-04-23T18:32:59.771Z" }, - { url = "https://files.pythonhosted.org/packages/e3/d5/0bb5d988cc019b3cba4a78f2d4b3854427fc47ee8ec8e9eaabf787da239c/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c", size = 2108225, upload-time = "2025-04-23T18:33:04.51Z" }, - { url = "https://files.pythonhosted.org/packages/f1/c5/00c02d1571913d496aabf146106ad8239dc132485ee22efe08085084ff7c/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec", size = 2069490, upload-time = "2025-04-23T18:33:06.391Z" }, - { url = "https://files.pythonhosted.org/packages/22/a8/dccc38768274d3ed3a59b5d06f59ccb845778687652daa71df0cab4040d7/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052", size = 2237525, upload-time = "2025-04-23T18:33:08.44Z" }, - { url = "https://files.pythonhosted.org/packages/d4/e7/4f98c0b125dda7cf7ccd14ba936218397b44f50a56dd8c16a3091df116c3/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c", size = 2238446, upload-time = "2025-04-23T18:33:10.313Z" }, - { url = "https://files.pythonhosted.org/packages/ce/91/2ec36480fdb0b783cd9ef6795753c1dea13882f2e68e73bce76ae8c21e6a/pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808", size = 2066678, upload-time = "2025-04-23T18:33:12.224Z" }, - { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200, upload-time = "2025-04-23T18:33:14.199Z" }, - { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123, upload-time = "2025-04-23T18:33:16.555Z" }, - { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852, upload-time = "2025-04-23T18:33:18.513Z" }, - { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484, upload-time = "2025-04-23T18:33:20.475Z" }, - { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896, upload-time = "2025-04-23T18:33:22.501Z" }, - { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475, upload-time = "2025-04-23T18:33:24.528Z" }, - { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013, upload-time = "2025-04-23T18:33:26.621Z" }, - { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715, upload-time = "2025-04-23T18:33:28.656Z" }, - { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" }, + { url = "https://files.pythonhosted.org/packages/e5/92/b31726561b5dae176c2d2c2dc43a9c5bfba5d32f96f8b4c0a600dd492447/pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8", size = 2028817 }, + { url = "https://files.pythonhosted.org/packages/a3/44/3f0b95fafdaca04a483c4e685fe437c6891001bf3ce8b2fded82b9ea3aa1/pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d", size = 1861357 }, + { url = "https://files.pythonhosted.org/packages/30/97/e8f13b55766234caae05372826e8e4b3b96e7b248be3157f53237682e43c/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d", size = 1898011 }, + { url = "https://files.pythonhosted.org/packages/9b/a3/99c48cf7bafc991cc3ee66fd544c0aae8dc907b752f1dad2d79b1b5a471f/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572", size = 1982730 }, + { url = "https://files.pythonhosted.org/packages/de/8e/a5b882ec4307010a840fb8b58bd9bf65d1840c92eae7534c7441709bf54b/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02", size = 2136178 }, + { url = "https://files.pythonhosted.org/packages/e4/bb/71e35fc3ed05af6834e890edb75968e2802fe98778971ab5cba20a162315/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b", size = 2736462 }, + { url = "https://files.pythonhosted.org/packages/31/0d/c8f7593e6bc7066289bbc366f2235701dcbebcd1ff0ef8e64f6f239fb47d/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2", size = 2005652 }, + { url = "https://files.pythonhosted.org/packages/d2/7a/996d8bd75f3eda405e3dd219ff5ff0a283cd8e34add39d8ef9157e722867/pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a", size = 2113306 }, + { url = "https://files.pythonhosted.org/packages/ff/84/daf2a6fb2db40ffda6578a7e8c5a6e9c8affb251a05c233ae37098118788/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac", size = 2073720 }, + { url = "https://files.pythonhosted.org/packages/77/fb/2258da019f4825128445ae79456a5499c032b55849dbd5bed78c95ccf163/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a", size = 2244915 }, + { url = "https://files.pythonhosted.org/packages/d8/7a/925ff73756031289468326e355b6fa8316960d0d65f8b5d6b3a3e7866de7/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b", size = 2241884 }, + { url = "https://files.pythonhosted.org/packages/0b/b0/249ee6d2646f1cdadcb813805fe76265745c4010cf20a8eba7b0e639d9b2/pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22", size = 1910496 }, + { url = "https://files.pythonhosted.org/packages/66/ff/172ba8f12a42d4b552917aa65d1f2328990d3ccfc01d5b7c943ec084299f/pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640", size = 1955019 }, + { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584 }, + { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071 }, + { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823 }, + { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792 }, + { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338 }, + { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998 }, + { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200 }, + { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890 }, + { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359 }, + { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883 }, + { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074 }, + { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538 }, + { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909 }, + { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786 }, + { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000 }, + { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996 }, + { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957 }, + { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199 }, + { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296 }, + { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109 }, + { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028 }, + { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044 }, + { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881 }, + { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034 }, + { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187 }, + { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628 }, + { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866 }, + { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894 }, + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688 }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808 }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580 }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859 }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810 }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498 }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611 }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924 }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196 }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389 }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223 }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473 }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269 }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921 }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162 }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560 }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777 }, + { url = "https://files.pythonhosted.org/packages/30/68/373d55e58b7e83ce371691f6eaa7175e3a24b956c44628eb25d7da007917/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa", size = 2023982 }, + { url = "https://files.pythonhosted.org/packages/a4/16/145f54ac08c96a63d8ed6442f9dec17b2773d19920b627b18d4f10a061ea/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29", size = 1858412 }, + { url = "https://files.pythonhosted.org/packages/41/b1/c6dc6c3e2de4516c0bb2c46f6a373b91b5660312342a0cf5826e38ad82fa/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d", size = 1892749 }, + { url = "https://files.pythonhosted.org/packages/12/73/8cd57e20afba760b21b742106f9dbdfa6697f1570b189c7457a1af4cd8a0/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e", size = 2067527 }, + { url = "https://files.pythonhosted.org/packages/e3/d5/0bb5d988cc019b3cba4a78f2d4b3854427fc47ee8ec8e9eaabf787da239c/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c", size = 2108225 }, + { url = "https://files.pythonhosted.org/packages/f1/c5/00c02d1571913d496aabf146106ad8239dc132485ee22efe08085084ff7c/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec", size = 2069490 }, + { url = "https://files.pythonhosted.org/packages/22/a8/dccc38768274d3ed3a59b5d06f59ccb845778687652daa71df0cab4040d7/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052", size = 2237525 }, + { url = "https://files.pythonhosted.org/packages/d4/e7/4f98c0b125dda7cf7ccd14ba936218397b44f50a56dd8c16a3091df116c3/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c", size = 2238446 }, + { url = "https://files.pythonhosted.org/packages/ce/91/2ec36480fdb0b783cd9ef6795753c1dea13882f2e68e73bce76ae8c21e6a/pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808", size = 2066678 }, + { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200 }, + { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123 }, + { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852 }, + { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484 }, + { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896 }, + { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475 }, + { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013 }, + { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715 }, + { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757 }, ] [[package]] @@ -2197,41 +2252,41 @@ dependencies = [ { name = "python-dotenv" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/68/85/1ea668bbab3c50071ca613c6ab30047fb36ab0da1b92fa8f17bbc38fd36c/pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee", size = 172583, upload-time = "2025-06-24T13:26:46.841Z" } +sdist = { url = "https://files.pythonhosted.org/packages/68/85/1ea668bbab3c50071ca613c6ab30047fb36ab0da1b92fa8f17bbc38fd36c/pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee", size = 172583 } wheels = [ - { url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235, upload-time = "2025-06-24T13:26:45.485Z" }, + { url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235 }, ] [[package]] name = "pygments" version = "2.17.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/55/59/8bccf4157baf25e4aa5a0bb7fa3ba8600907de105ebc22b0c78cfbf6f565/pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367", size = 4827772, upload-time = "2023-11-21T20:43:53.875Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/59/8bccf4157baf25e4aa5a0bb7fa3ba8600907de105ebc22b0c78cfbf6f565/pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367", size = 4827772 } wheels = [ - { url = "https://files.pythonhosted.org/packages/97/9c/372fef8377a6e340b1704768d20daaded98bf13282b5327beb2e2fe2c7ef/pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c", size = 1179756, upload-time = "2023-11-21T20:43:49.423Z" }, + { url = "https://files.pythonhosted.org/packages/97/9c/372fef8377a6e340b1704768d20daaded98bf13282b5327beb2e2fe2c7ef/pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c", size = 1179756 }, ] [[package]] name = "pyparsing" version = "3.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/37/fe/65c989f70bd630b589adfbbcd6ed238af22319e90f059946c26b4835e44b/pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db", size = 884814, upload-time = "2023-07-30T15:07:02.617Z" } +sdist = { url = "https://files.pythonhosted.org/packages/37/fe/65c989f70bd630b589adfbbcd6ed238af22319e90f059946c26b4835e44b/pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db", size = 884814 } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/92/8486ede85fcc088f1b3dba4ce92dd29d126fd96b0008ea213167940a2475/pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb", size = 103139, upload-time = "2023-07-30T15:06:59.829Z" }, + { url = "https://files.pythonhosted.org/packages/39/92/8486ede85fcc088f1b3dba4ce92dd29d126fd96b0008ea213167940a2475/pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb", size = 103139 }, ] [[package]] name = "pyreadline3" version = "3.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/86/3d61a61f36a0067874a00cb4dceb9028d34b6060e47828f7fc86fb9f7ee9/pyreadline3-3.4.1.tar.gz", hash = "sha256:6f3d1f7b8a31ba32b73917cefc1f28cc660562f39aea8646d30bd6eff21f7bae", size = 86465, upload-time = "2022-01-24T20:05:11.66Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/86/3d61a61f36a0067874a00cb4dceb9028d34b6060e47828f7fc86fb9f7ee9/pyreadline3-3.4.1.tar.gz", hash = "sha256:6f3d1f7b8a31ba32b73917cefc1f28cc660562f39aea8646d30bd6eff21f7bae", size = 86465 } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/fc/a3c13ded7b3057680c8ae95a9b6cc83e63657c38e0005c400a5d018a33a7/pyreadline3-3.4.1-py3-none-any.whl", hash = "sha256:b0efb6516fd4fb07b45949053826a62fa4cb353db5be2bbb4a7aa1fdd1e345fb", size = 95203, upload-time = "2022-01-24T20:05:10.442Z" }, + { url = "https://files.pythonhosted.org/packages/56/fc/a3c13ded7b3057680c8ae95a9b6cc83e63657c38e0005c400a5d018a33a7/pyreadline3-3.4.1-py3-none-any.whl", hash = "sha256:b0efb6516fd4fb07b45949053826a62fa4cb353db5be2bbb4a7aa1fdd1e345fb", size = 95203 }, ] [[package]] name = "pytest" -version = "8.4.2" +version = "8.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -2242,9 +2297,9 @@ dependencies = [ { name = "pygments" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474 }, ] [[package]] @@ -2255,35 +2310,35 @@ dependencies = [ { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4e/51/f8794af39eeb870e87a8c8068642fc07bce0c854d6865d7dd0f2a9d338c2/pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea", size = 46652, upload-time = "2025-07-16T04:29:26.393Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/51/f8794af39eeb870e87a8c8068642fc07bce0c854d6865d7dd0f2a9d338c2/pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea", size = 46652 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/9d/bf86eddabf8c6c9cb1ea9a869d6873b46f105a5d292d3a6f7071f5b07935/pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf", size = 15157, upload-time = "2025-07-16T04:29:24.929Z" }, + { url = "https://files.pythonhosted.org/packages/c7/9d/bf86eddabf8c6c9cb1ea9a869d6873b46f105a5d292d3a6f7071f5b07935/pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf", size = 15157 }, ] [[package]] name = "pytest-cov" -version = "7.0.0" +version = "6.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage", extra = ["toml"] }, { name = "pluggy" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, + { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644 }, ] [[package]] name = "pytest-mock" -version = "3.15.0" +version = "3.14.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/61/99/3323ee5c16b3637b4d941c362182d3e749c11e400bea31018c42219f3a98/pytest_mock-3.15.0.tar.gz", hash = "sha256:ab896bd190316b9d5d87b277569dfcdf718b2d049a2ccff5f7aca279c002a1cf", size = 33838, upload-time = "2025-09-04T20:57:48.679Z" } +sdist = { url = "https://files.pythonhosted.org/packages/71/28/67172c96ba684058a4d24ffe144d64783d2a270d0af0d9e792737bddc75c/pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e", size = 33241 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/b3/7fefc43fb706380144bcd293cc6e446e6f637ddfa8b83f48d1734156b529/pytest_mock-3.15.0-py3-none-any.whl", hash = "sha256:ef2219485fb1bd256b00e7ad7466ce26729b30eadfc7cbcdb4fa9a92ca68db6f", size = 10050, upload-time = "2025-09-04T20:57:47.274Z" }, + { url = "https://files.pythonhosted.org/packages/b2/05/77b60e520511c53d1c1ca75f1930c7dd8e971d0c4379b7f4b3f9644685ba/pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0", size = 9923 }, ] [[package]] @@ -2293,18 +2348,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/c4/13b4776ea2d76c115c1d1b84579f3764ee6d57204f6be27119f13a61d0a9/python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", size = 357324, upload-time = "2021-07-14T08:19:19.783Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/c4/13b4776ea2d76c115c1d1b84579f3764ee6d57204f6be27119f13a61d0a9/python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", size = 357324 } wheels = [ - { url = "https://files.pythonhosted.org/packages/36/7a/87837f39d0296e723bb9b62bbb257d0355c7f6128853c78955f57342a56d/python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9", size = 247702, upload-time = "2021-07-14T08:19:18.161Z" }, + { url = "https://files.pythonhosted.org/packages/36/7a/87837f39d0296e723bb9b62bbb257d0355c7f6128853c78955f57342a56d/python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9", size = 247702 }, ] [[package]] name = "python-dotenv" version = "1.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/31/06/1ef763af20d0572c032fa22882cfbfb005fba6e7300715a37840858c919e/python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba", size = 37399, upload-time = "2023-02-24T06:46:37.282Z" } +sdist = { url = "https://files.pythonhosted.org/packages/31/06/1ef763af20d0572c032fa22882cfbfb005fba6e7300715a37840858c919e/python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba", size = 37399 } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/2f/62ea1c8b593f4e093cc1a7768f0d46112107e790c3e478532329e434f00b/python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a", size = 19482, upload-time = "2023-02-24T06:46:36.009Z" }, + { url = "https://files.pythonhosted.org/packages/44/2f/62ea1c8b593f4e093cc1a7768f0d46112107e790c3e478532329e434f00b/python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a", size = 19482 }, ] [[package]] @@ -2314,18 +2369,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "simple-websocket" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ba/0b/67295279b66835f9fa7a491650efcd78b20321c127036eef62c11a31e028/python_engineio-4.12.2.tar.gz", hash = "sha256:e7e712ffe1be1f6a05ee5f951e72d434854a32fcfc7f6e4d9d3cae24ec70defa", size = 91677, upload-time = "2025-06-04T19:22:18.789Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/0b/67295279b66835f9fa7a491650efcd78b20321c127036eef62c11a31e028/python_engineio-4.12.2.tar.gz", hash = "sha256:e7e712ffe1be1f6a05ee5f951e72d434854a32fcfc7f6e4d9d3cae24ec70defa", size = 91677 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl", hash = "sha256:8218ab66950e179dfec4b4bbb30aecf3f5d86f5e58e6fc1aa7fde2c698b2804f", size = 59536, upload-time = "2025-06-04T19:22:16.916Z" }, + { url = "https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl", hash = "sha256:8218ab66950e179dfec4b4bbb30aecf3f5d86f5e58e6fc1aa7fde2c698b2804f", size = 59536 }, ] [[package]] name = "python-multipart" version = "0.0.20" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158 } wheels = [ - { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546 }, ] [[package]] @@ -2336,9 +2391,9 @@ dependencies = [ { name = "bidict" }, { name = "python-engineio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/21/1a/396d50ccf06ee539fa758ce5623b59a9cb27637fc4b2dc07ed08bf495e77/python_socketio-5.13.0.tar.gz", hash = "sha256:ac4e19a0302ae812e23b712ec8b6427ca0521f7c582d6abb096e36e24a263029", size = 121125, upload-time = "2025-04-12T15:46:59.933Z" } +sdist = { url = "https://files.pythonhosted.org/packages/21/1a/396d50ccf06ee539fa758ce5623b59a9cb27637fc4b2dc07ed08bf495e77/python_socketio-5.13.0.tar.gz", hash = "sha256:ac4e19a0302ae812e23b712ec8b6427ca0521f7c582d6abb096e36e24a263029", size = 121125 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl", hash = "sha256:51f68d6499f2df8524668c24bcec13ba1414117cfb3a90115c559b601ab10caf", size = 77800, upload-time = "2025-04-12T15:46:58.412Z" }, + { url = "https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl", hash = "sha256:51f68d6499f2df8524668c24bcec13ba1414117cfb3a90115c559b601ab10caf", size = 77800 }, ] [package.optional-dependencies] @@ -2352,45 +2407,58 @@ name = "pywin32" version = "306" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/dc/28c668097edfaf4eac4617ef7adf081b9cf50d254672fcf399a70f5efc41/pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d", size = 8506422, upload-time = "2023-03-26T03:27:46.303Z" }, - { url = "https://files.pythonhosted.org/packages/d3/d6/891894edec688e72c2e308b3243fad98b4066e1839fd2fe78f04129a9d31/pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8", size = 9226392, upload-time = "2023-03-26T03:27:53.591Z" }, - { url = "https://files.pythonhosted.org/packages/8b/1e/fc18ad83ca553e01b97aa8393ff10e33c1fb57801db05488b83282ee9913/pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407", size = 8507689, upload-time = "2023-03-25T23:50:08.499Z" }, - { url = "https://files.pythonhosted.org/packages/7e/9e/ad6b1ae2a5ad1066dc509350e0fbf74d8d50251a51e420a2a8feaa0cecbd/pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e", size = 9227547, upload-time = "2023-03-25T23:50:20.331Z" }, - { url = "https://files.pythonhosted.org/packages/91/20/f744bff1da8f43388498503634378dbbefbe493e65675f2cc52f7185c2c2/pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a", size = 10388324, upload-time = "2023-03-25T23:50:30.904Z" }, - { url = "https://files.pythonhosted.org/packages/14/91/17e016d5923e178346aabda3dfec6629d1a26efe587d19667542105cf0a6/pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b", size = 8507705, upload-time = "2023-03-25T23:50:40.279Z" }, - { url = "https://files.pythonhosted.org/packages/83/1c/25b79fc3ec99b19b0a0730cc47356f7e2959863bf9f3cd314332bddb4f68/pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e", size = 9227429, upload-time = "2023-03-25T23:50:50.222Z" }, - { url = "https://files.pythonhosted.org/packages/1c/43/e3444dc9a12f8365d9603c2145d16bf0a2f8180f343cf87be47f5579e547/pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040", size = 10388145, upload-time = "2023-03-25T23:51:01.401Z" }, + { url = "https://files.pythonhosted.org/packages/08/dc/28c668097edfaf4eac4617ef7adf081b9cf50d254672fcf399a70f5efc41/pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d", size = 8506422 }, + { url = "https://files.pythonhosted.org/packages/d3/d6/891894edec688e72c2e308b3243fad98b4066e1839fd2fe78f04129a9d31/pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8", size = 9226392 }, + { url = "https://files.pythonhosted.org/packages/8b/1e/fc18ad83ca553e01b97aa8393ff10e33c1fb57801db05488b83282ee9913/pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407", size = 8507689 }, + { url = "https://files.pythonhosted.org/packages/7e/9e/ad6b1ae2a5ad1066dc509350e0fbf74d8d50251a51e420a2a8feaa0cecbd/pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e", size = 9227547 }, + { url = "https://files.pythonhosted.org/packages/91/20/f744bff1da8f43388498503634378dbbefbe493e65675f2cc52f7185c2c2/pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a", size = 10388324 }, + { url = "https://files.pythonhosted.org/packages/14/91/17e016d5923e178346aabda3dfec6629d1a26efe587d19667542105cf0a6/pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b", size = 8507705 }, + { url = "https://files.pythonhosted.org/packages/83/1c/25b79fc3ec99b19b0a0730cc47356f7e2959863bf9f3cd314332bddb4f68/pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e", size = 9227429 }, + { url = "https://files.pythonhosted.org/packages/1c/43/e3444dc9a12f8365d9603c2145d16bf0a2f8180f343cf87be47f5579e547/pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040", size = 10388145 }, ] [[package]] name = "pyyaml" -version = "6.0.1" +version = "6.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cd/e5/af35f7ea75cf72f2cd079c95ee16797de7cd71f29ea7c68ae5ce7be1eda0/PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", size = 125201, upload-time = "2023-07-18T00:00:23.308Z" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } wheels = [ - { url = "https://files.pythonhosted.org/packages/96/06/4beb652c0fe16834032e54f0956443d4cc797fe645527acee59e7deaa0a2/PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", size = 189447, upload-time = "2023-07-17T23:57:04.325Z" }, - { url = "https://files.pythonhosted.org/packages/5b/07/10033a403b23405a8fc48975444463d3d10a5c2736b7eb2550b07b367429/PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f", size = 169264, upload-time = "2023-07-17T23:57:07.787Z" }, - { url = "https://files.pythonhosted.org/packages/f1/26/55e4f21db1f72eaef092015d9017c11510e7e6301c62a6cfee91295d13c6/PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", size = 677003, upload-time = "2023-07-17T23:57:13.144Z" }, - { url = "https://files.pythonhosted.org/packages/ba/91/090818dfa62e85181f3ae23dd1e8b7ea7f09684864a900cab72d29c57346/PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", size = 699070, upload-time = "2023-07-17T23:57:19.402Z" }, - { url = "https://files.pythonhosted.org/packages/29/61/bf33c6c85c55bc45a29eee3195848ff2d518d84735eb0e2d8cb42e0d285e/PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", size = 705525, upload-time = "2023-07-17T23:57:25.272Z" }, - { url = "https://files.pythonhosted.org/packages/07/91/45dfd0ef821a7f41d9d0136ea3608bb5b1653e42fd56a7970532cb5c003f/PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", size = 707514, upload-time = "2023-08-28T18:43:20.945Z" }, - { url = "https://files.pythonhosted.org/packages/b6/a0/b6700da5d49e9fed49dc3243d3771b598dad07abb37cc32e524607f96adc/PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", size = 130488, upload-time = "2023-07-17T23:57:28.144Z" }, - { url = "https://files.pythonhosted.org/packages/24/97/9b59b43431f98d01806b288532da38099cc6f2fea0f3d712e21e269c0279/PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", size = 145338, upload-time = "2023-07-17T23:57:31.118Z" }, - { url = "https://files.pythonhosted.org/packages/ec/0d/26fb23e8863e0aeaac0c64e03fd27367ad2ae3f3cccf3798ee98ce160368/PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", size = 187867, upload-time = "2023-07-17T23:57:34.35Z" }, - { url = "https://files.pythonhosted.org/packages/28/09/55f715ddbf95a054b764b547f617e22f1d5e45d83905660e9a088078fe67/PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", size = 167530, upload-time = "2023-07-17T23:57:36.975Z" }, - { url = "https://files.pythonhosted.org/packages/5e/94/7d5ee059dfb92ca9e62f4057dcdec9ac08a9e42679644854dc01177f8145/PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", size = 732244, upload-time = "2023-07-17T23:57:43.774Z" }, - { url = "https://files.pythonhosted.org/packages/06/92/e0224aa6ebf9dc54a06a4609da37da40bb08d126f5535d81bff6b417b2ae/PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", size = 752871, upload-time = "2023-07-17T23:57:51.921Z" }, - { url = "https://files.pythonhosted.org/packages/7b/5e/efd033ab7199a0b2044dab3b9f7a4f6670e6a52c089de572e928d2873b06/PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", size = 757729, upload-time = "2023-07-17T23:57:59.865Z" }, - { url = "https://files.pythonhosted.org/packages/03/5c/c4671451b2f1d76ebe352c0945d4cd13500adb5d05f5a51ee296d80152f7/PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", size = 748528, upload-time = "2023-08-28T18:43:23.207Z" }, - { url = "https://files.pythonhosted.org/packages/73/9c/766e78d1efc0d1fca637a6b62cea1b4510a7fb93617eb805223294fef681/PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", size = 130286, upload-time = "2023-07-17T23:58:02.964Z" }, - { url = "https://files.pythonhosted.org/packages/b3/34/65bb4b2d7908044963ebf614fe0fdb080773fc7030d7e39c8d3eddcd4257/PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", size = 144699, upload-time = "2023-07-17T23:58:05.586Z" }, - { url = "https://files.pythonhosted.org/packages/bc/06/1b305bf6aa704343be85444c9d011f626c763abb40c0edc1cad13bfd7f86/PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", size = 178692, upload-time = "2023-08-28T18:43:24.924Z" }, - { url = "https://files.pythonhosted.org/packages/84/02/404de95ced348b73dd84f70e15a41843d817ff8c1744516bf78358f2ffd2/PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", size = 165622, upload-time = "2023-08-28T18:43:26.54Z" }, - { url = "https://files.pythonhosted.org/packages/c7/4c/4a2908632fc980da6d918b9de9c1d9d7d7e70b2672b1ad5166ed27841ef7/PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef", size = 696937, upload-time = "2024-01-18T20:40:22.92Z" }, - { url = "https://files.pythonhosted.org/packages/b4/33/720548182ffa8344418126017aa1d4ab4aeec9a2275f04ce3f3573d8ace8/PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", size = 724969, upload-time = "2023-08-28T18:43:28.56Z" }, - { url = "https://files.pythonhosted.org/packages/4f/78/77b40157b6cb5f2d3d31a3d9b2efd1ba3505371f76730d267e8b32cf4b7f/PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", size = 712604, upload-time = "2023-08-28T18:43:30.206Z" }, - { url = "https://files.pythonhosted.org/packages/2e/97/3e0e089ee85e840f4b15bfa00e4e63d84a3691ababbfea92d6f820ea6f21/PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", size = 126098, upload-time = "2023-08-28T18:43:31.835Z" }, - { url = "https://files.pythonhosted.org/packages/2b/9f/fbade56564ad486809c27b322d0f7e6a89c01f6b4fe208402e90d4443a99/PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", size = 138675, upload-time = "2023-08-28T18:43:33.613Z" }, + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, ] [[package]] @@ -2400,46 +2468,46 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "implementation_name == 'pypy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3a/33/1a3683fc9a4bd64d8ccc0290da75c8f042184a1a49c146d28398414d3341/pyzmq-25.1.2.tar.gz", hash = "sha256:93f1aa311e8bb912e34f004cf186407a4e90eec4f0ecc0efd26056bf7eda0226", size = 1402339, upload-time = "2023-12-05T07:34:47.976Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/33/1a3683fc9a4bd64d8ccc0290da75c8f042184a1a49c146d28398414d3341/pyzmq-25.1.2.tar.gz", hash = "sha256:93f1aa311e8bb912e34f004cf186407a4e90eec4f0ecc0efd26056bf7eda0226", size = 1402339 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/f4/901edb48b2b2c00ad73de0db2ee76e24ce5903ef815ad0ad10e14555d989/pyzmq-25.1.2-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:e624c789359f1a16f83f35e2c705d07663ff2b4d4479bad35621178d8f0f6ea4", size = 1872310, upload-time = "2023-12-05T07:48:13.713Z" }, - { url = "https://files.pythonhosted.org/packages/5e/46/2de69c7c79fd78bf4c22a9e8165fa6312f5d49410f1be6ddab51a6fe7236/pyzmq-25.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:49151b0efece79f6a79d41a461d78535356136ee70084a1c22532fc6383f4ad0", size = 1249619, upload-time = "2023-12-05T07:50:38.691Z" }, - { url = "https://files.pythonhosted.org/packages/d1/f5/d6b9755713843bf9701ae86bf6fd97ec294a52cf2af719cd14fdf9392f65/pyzmq-25.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9a5f194cf730f2b24d6af1f833c14c10f41023da46a7f736f48b6d35061e76e", size = 897360, upload-time = "2023-12-05T07:42:26.268Z" }, - { url = "https://files.pythonhosted.org/packages/7c/88/c1aef8820f12e710d136024d231e70e24684a01314aa1814f0758960ba01/pyzmq-25.1.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:faf79a302f834d9e8304fafdc11d0d042266667ac45209afa57e5efc998e3872", size = 1156959, upload-time = "2023-12-05T07:44:29.904Z" }, - { url = "https://files.pythonhosted.org/packages/82/1b/b25d2c4ac3b4dae238c98e63395dbb88daf11968b168948d3c6289c3e95c/pyzmq-25.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f51a7b4ead28d3fca8dda53216314a553b0f7a91ee8fc46a72b402a78c3e43d", size = 1100585, upload-time = "2023-12-05T07:45:05.518Z" }, - { url = "https://files.pythonhosted.org/packages/67/bf/6bc0977acd934b66eacab79cec303ecf08ae4a6150d57c628aa919615488/pyzmq-25.1.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0ddd6d71d4ef17ba5a87becf7ddf01b371eaba553c603477679ae817a8d84d75", size = 1109267, upload-time = "2023-12-05T07:39:51.21Z" }, - { url = "https://files.pythonhosted.org/packages/64/fb/4f07424e56c6a5fb47306d9ba744c3c250250c2e7272f9c81efbf8daaccf/pyzmq-25.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:246747b88917e4867e2367b005fc8eefbb4a54b7db363d6c92f89d69abfff4b6", size = 1431853, upload-time = "2023-12-05T07:41:09.261Z" }, - { url = "https://files.pythonhosted.org/packages/a2/10/2b88c1d4beb59a1d45c13983c4b7c5dcd6ef7988db3c03d23b0cabc5adca/pyzmq-25.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:00c48ae2fd81e2a50c3485de1b9d5c7c57cd85dc8ec55683eac16846e57ac979", size = 1766212, upload-time = "2023-12-05T07:49:05.926Z" }, - { url = "https://files.pythonhosted.org/packages/bc/ab/c9a22eacfd5bd82620501ae426a3dd6ffa374ac335b21e54209d7a93d3fb/pyzmq-25.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5a68d491fc20762b630e5db2191dd07ff89834086740f70e978bb2ef2668be08", size = 1653737, upload-time = "2023-12-05T07:49:09.096Z" }, - { url = "https://files.pythonhosted.org/packages/d6/e5/71bd89e47eedb7ebec31ef9a49dcdb0517dbbb063bd5de363980a6911eb1/pyzmq-25.1.2-cp310-cp310-win32.whl", hash = "sha256:09dfe949e83087da88c4a76767df04b22304a682d6154de2c572625c62ad6886", size = 906288, upload-time = "2023-12-05T07:42:05.509Z" }, - { url = "https://files.pythonhosted.org/packages/9d/5f/2defc8a579e8b5679d92720ab3a4cb93e3a77d923070bf4c1a103d3ae478/pyzmq-25.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:fa99973d2ed20417744fca0073390ad65ce225b546febb0580358e36aa90dba6", size = 1170923, upload-time = "2023-12-05T07:44:54.296Z" }, - { url = "https://files.pythonhosted.org/packages/35/de/7579518bc58cebf92568b48e354a702fb52525d0fab166dc544f2a0615dc/pyzmq-25.1.2-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:82544e0e2d0c1811482d37eef297020a040c32e0687c1f6fc23a75b75db8062c", size = 1870360, upload-time = "2023-12-05T07:48:16.153Z" }, - { url = "https://files.pythonhosted.org/packages/ce/f9/58b6cc9a110b1832f666fa6b5a67dc4d520fabfc680ca87a8167b2061d5d/pyzmq-25.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:01171fc48542348cd1a360a4b6c3e7d8f46cdcf53a8d40f84db6707a6768acc1", size = 1249008, upload-time = "2023-12-05T07:50:40.442Z" }, - { url = "https://files.pythonhosted.org/packages/bc/4a/ac6469c01813cb3652ab4e30ec4a37815cc9949afc18af33f64e2ec704aa/pyzmq-25.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc69c96735ab501419c432110016329bf0dea8898ce16fab97c6d9106dc0b348", size = 904394, upload-time = "2023-12-05T07:42:27.815Z" }, - { url = "https://files.pythonhosted.org/packages/77/b7/8cee519b11bdd3f76c1a6eb537ab13c1bfef2964d725717705c86f524e4c/pyzmq-25.1.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3e124e6b1dd3dfbeb695435dff0e383256655bb18082e094a8dd1f6293114642", size = 1161453, upload-time = "2023-12-05T07:44:32.003Z" }, - { url = "https://files.pythonhosted.org/packages/b6/1d/c35a956a44b333b064ae1b1c588c2dfa0e01b7ec90884c1972bfcef119c3/pyzmq-25.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7598d2ba821caa37a0f9d54c25164a4fa351ce019d64d0b44b45540950458840", size = 1105501, upload-time = "2023-12-05T07:45:07.18Z" }, - { url = "https://files.pythonhosted.org/packages/18/d1/b3d1e985318ed7287737ea9e6b6e21748cc7c89accc2443347cd2c8d5f0f/pyzmq-25.1.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d1299d7e964c13607efd148ca1f07dcbf27c3ab9e125d1d0ae1d580a1682399d", size = 1109513, upload-time = "2023-12-05T07:39:53.338Z" }, - { url = "https://files.pythonhosted.org/packages/14/9b/341cdfb47440069010101403298dc24d449150370c6cb322e73bfa1949bd/pyzmq-25.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4e6f689880d5ad87918430957297c975203a082d9a036cc426648fcbedae769b", size = 1433541, upload-time = "2023-12-05T07:41:10.786Z" }, - { url = "https://files.pythonhosted.org/packages/fa/52/c6d4e76e020c554e965459d41a98201b4d45277a288648f53a4e5a2429cc/pyzmq-25.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cc69949484171cc961e6ecd4a8911b9ce7a0d1f738fcae717177c231bf77437b", size = 1766133, upload-time = "2023-12-05T07:49:11.204Z" }, - { url = "https://files.pythonhosted.org/packages/1d/6d/0cbd8dd5b8979fd6b9cf1852ed067b9d2cd6fa0c09c3bafe6874d2d2e03c/pyzmq-25.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9880078f683466b7f567b8624bfc16cad65077be046b6e8abb53bed4eeb82dd3", size = 1653636, upload-time = "2023-12-05T07:49:13.787Z" }, - { url = "https://files.pythonhosted.org/packages/f5/af/d90eed9cf3840685d54d4a35d5f9e242a8a48b5410d41146f14c1e098302/pyzmq-25.1.2-cp311-cp311-win32.whl", hash = "sha256:4e5837af3e5aaa99a091302df5ee001149baff06ad22b722d34e30df5f0d9097", size = 904865, upload-time = "2023-12-05T07:42:07.189Z" }, - { url = "https://files.pythonhosted.org/packages/20/d2/09443dc73053ad01c846d7fb77e09fe9d93c09d4e900215f3c8b7b56bfec/pyzmq-25.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:25c2dbb97d38b5ac9fd15586e048ec5eb1e38f3d47fe7d92167b0c77bb3584e9", size = 1171332, upload-time = "2023-12-05T07:44:56.111Z" }, - { url = "https://files.pythonhosted.org/packages/6e/f0/d71cf69dc039c9adc8b625efc3bad3684f3660a570e47f0f0c64df787b41/pyzmq-25.1.2-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:11e70516688190e9c2db14fcf93c04192b02d457b582a1f6190b154691b4c93a", size = 1871111, upload-time = "2023-12-05T07:48:17.868Z" }, - { url = "https://files.pythonhosted.org/packages/68/62/d365773edf56ad71993579ee574105f02f83530caf600ebf28bea15d88d0/pyzmq-25.1.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:313c3794d650d1fccaaab2df942af9f2c01d6217c846177cfcbc693c7410839e", size = 1248844, upload-time = "2023-12-05T07:50:42.922Z" }, - { url = "https://files.pythonhosted.org/packages/72/55/cc3163e20f40615a49245fa7041badec6103e8ee7e482dbb0feea00a7b84/pyzmq-25.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b3cbba2f47062b85fe0ef9de5b987612140a9ba3a9c6d2543c6dec9f7c2ab27", size = 899373, upload-time = "2023-12-05T07:42:29.595Z" }, - { url = "https://files.pythonhosted.org/packages/40/aa/ae292bd85deda637230970bbc53c1dc53696a99e82fc7cd6d373ec173853/pyzmq-25.1.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc31baa0c32a2ca660784d5af3b9487e13b61b3032cb01a115fce6588e1bed30", size = 1160901, upload-time = "2023-12-05T07:44:33.819Z" }, - { url = "https://files.pythonhosted.org/packages/93/b7/6e291eafbbbc66d0e87658dd21383ec2b4ab35edcfb283902c580a6db76f/pyzmq-25.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02c9087b109070c5ab0b383079fa1b5f797f8d43e9a66c07a4b8b8bdecfd88ee", size = 1101147, upload-time = "2023-12-05T07:45:10.058Z" }, - { url = "https://files.pythonhosted.org/packages/3a/f1/e296d5a507eac519d1fe1382851b1a4575f690bc2b2d2c8eca2ed7e4bd1f/pyzmq-25.1.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f8429b17cbb746c3e043cb986328da023657e79d5ed258b711c06a70c2ea7537", size = 1105315, upload-time = "2023-12-05T07:39:55.851Z" }, - { url = "https://files.pythonhosted.org/packages/56/63/5c2abb556ab4cf013d98e01782d5bd642238a0ed9b019e965a7d7e957f56/pyzmq-25.1.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5074adeacede5f810b7ef39607ee59d94e948b4fd954495bdb072f8c54558181", size = 1427747, upload-time = "2023-12-05T07:41:13.219Z" }, - { url = "https://files.pythonhosted.org/packages/b1/71/5dba5f6b12ef54fb977c9b9279075e151c04fc0dd6851e9663d9e66b593f/pyzmq-25.1.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7ae8f354b895cbd85212da245f1a5ad8159e7840e37d78b476bb4f4c3f32a9fe", size = 1762221, upload-time = "2023-12-05T07:49:16.352Z" }, - { url = "https://files.pythonhosted.org/packages/cf/49/54d7e8bb3df82a3509325b11491d33450dc91580d4826b62fa5e554bb9cf/pyzmq-25.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b264bf2cc96b5bc43ce0e852be995e400376bd87ceb363822e2cb1964fcdc737", size = 1649505, upload-time = "2023-12-05T07:49:18.952Z" }, - { url = "https://files.pythonhosted.org/packages/34/14/58e5037229bc37963e2ce804c2c075a3a541e3f84bf1c231e7c9779d36f1/pyzmq-25.1.2-cp312-cp312-win32.whl", hash = "sha256:02bbc1a87b76e04fd780b45e7f695471ae6de747769e540da909173d50ff8e2d", size = 954891, upload-time = "2023-12-05T07:42:09.208Z" }, - { url = "https://files.pythonhosted.org/packages/2c/2d/04fab685ef3a8e6e955220fd2a54dc99efaee960a88675bf5c92cd277164/pyzmq-25.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:ced111c2e81506abd1dc142e6cd7b68dd53747b3b7ae5edbea4578c5eeff96b7", size = 1252773, upload-time = "2023-12-05T07:44:58.16Z" }, - { url = "https://files.pythonhosted.org/packages/6b/fe/ed38fe12c540bafc1cae32c3ff638e9df32528f5cf91b5e400e6a8f5b3ec/pyzmq-25.1.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a8c1d566344aee826b74e472e16edae0a02e2a044f14f7c24e123002dcff1c05", size = 963654, upload-time = "2023-12-05T07:47:03.874Z" }, - { url = "https://files.pythonhosted.org/packages/44/97/a760a2dff0672c408f22f726f2ea10a7a516ffa5001ca5a3641e355a45f9/pyzmq-25.1.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:759cfd391a0996345ba94b6a5110fca9c557ad4166d86a6e81ea526c376a01e8", size = 609436, upload-time = "2023-12-05T07:42:37.762Z" }, - { url = "https://files.pythonhosted.org/packages/41/81/ace39daa19c78b2f4fc12ef217d9d5f1ac658d5828d692bbbb68240cd55b/pyzmq-25.1.2-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c61e346ac34b74028ede1c6b4bcecf649d69b707b3ff9dc0fab453821b04d1e", size = 843396, upload-time = "2023-12-05T07:44:43.727Z" }, - { url = "https://files.pythonhosted.org/packages/4c/43/150b0b203f5461a9aeadaa925c55167e2b4215c9322b6911a64360d2243e/pyzmq-25.1.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cb8fc1f8d69b411b8ec0b5f1ffbcaf14c1db95b6bccea21d83610987435f1a4", size = 800856, upload-time = "2023-12-05T07:45:21.117Z" }, - { url = "https://files.pythonhosted.org/packages/5f/91/a618b56aaabe40dddcd25db85624d7408768fd32f5bfcf81bc0af5b1ce75/pyzmq-25.1.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3c00c9b7d1ca8165c610437ca0c92e7b5607b2f9076f4eb4b095c85d6e680a1d", size = 413836, upload-time = "2023-12-05T07:53:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/5e/f4/901edb48b2b2c00ad73de0db2ee76e24ce5903ef815ad0ad10e14555d989/pyzmq-25.1.2-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:e624c789359f1a16f83f35e2c705d07663ff2b4d4479bad35621178d8f0f6ea4", size = 1872310 }, + { url = "https://files.pythonhosted.org/packages/5e/46/2de69c7c79fd78bf4c22a9e8165fa6312f5d49410f1be6ddab51a6fe7236/pyzmq-25.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:49151b0efece79f6a79d41a461d78535356136ee70084a1c22532fc6383f4ad0", size = 1249619 }, + { url = "https://files.pythonhosted.org/packages/d1/f5/d6b9755713843bf9701ae86bf6fd97ec294a52cf2af719cd14fdf9392f65/pyzmq-25.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9a5f194cf730f2b24d6af1f833c14c10f41023da46a7f736f48b6d35061e76e", size = 897360 }, + { url = "https://files.pythonhosted.org/packages/7c/88/c1aef8820f12e710d136024d231e70e24684a01314aa1814f0758960ba01/pyzmq-25.1.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:faf79a302f834d9e8304fafdc11d0d042266667ac45209afa57e5efc998e3872", size = 1156959 }, + { url = "https://files.pythonhosted.org/packages/82/1b/b25d2c4ac3b4dae238c98e63395dbb88daf11968b168948d3c6289c3e95c/pyzmq-25.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f51a7b4ead28d3fca8dda53216314a553b0f7a91ee8fc46a72b402a78c3e43d", size = 1100585 }, + { url = "https://files.pythonhosted.org/packages/67/bf/6bc0977acd934b66eacab79cec303ecf08ae4a6150d57c628aa919615488/pyzmq-25.1.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0ddd6d71d4ef17ba5a87becf7ddf01b371eaba553c603477679ae817a8d84d75", size = 1109267 }, + { url = "https://files.pythonhosted.org/packages/64/fb/4f07424e56c6a5fb47306d9ba744c3c250250c2e7272f9c81efbf8daaccf/pyzmq-25.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:246747b88917e4867e2367b005fc8eefbb4a54b7db363d6c92f89d69abfff4b6", size = 1431853 }, + { url = "https://files.pythonhosted.org/packages/a2/10/2b88c1d4beb59a1d45c13983c4b7c5dcd6ef7988db3c03d23b0cabc5adca/pyzmq-25.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:00c48ae2fd81e2a50c3485de1b9d5c7c57cd85dc8ec55683eac16846e57ac979", size = 1766212 }, + { url = "https://files.pythonhosted.org/packages/bc/ab/c9a22eacfd5bd82620501ae426a3dd6ffa374ac335b21e54209d7a93d3fb/pyzmq-25.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5a68d491fc20762b630e5db2191dd07ff89834086740f70e978bb2ef2668be08", size = 1653737 }, + { url = "https://files.pythonhosted.org/packages/d6/e5/71bd89e47eedb7ebec31ef9a49dcdb0517dbbb063bd5de363980a6911eb1/pyzmq-25.1.2-cp310-cp310-win32.whl", hash = "sha256:09dfe949e83087da88c4a76767df04b22304a682d6154de2c572625c62ad6886", size = 906288 }, + { url = "https://files.pythonhosted.org/packages/9d/5f/2defc8a579e8b5679d92720ab3a4cb93e3a77d923070bf4c1a103d3ae478/pyzmq-25.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:fa99973d2ed20417744fca0073390ad65ce225b546febb0580358e36aa90dba6", size = 1170923 }, + { url = "https://files.pythonhosted.org/packages/35/de/7579518bc58cebf92568b48e354a702fb52525d0fab166dc544f2a0615dc/pyzmq-25.1.2-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:82544e0e2d0c1811482d37eef297020a040c32e0687c1f6fc23a75b75db8062c", size = 1870360 }, + { url = "https://files.pythonhosted.org/packages/ce/f9/58b6cc9a110b1832f666fa6b5a67dc4d520fabfc680ca87a8167b2061d5d/pyzmq-25.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:01171fc48542348cd1a360a4b6c3e7d8f46cdcf53a8d40f84db6707a6768acc1", size = 1249008 }, + { url = "https://files.pythonhosted.org/packages/bc/4a/ac6469c01813cb3652ab4e30ec4a37815cc9949afc18af33f64e2ec704aa/pyzmq-25.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc69c96735ab501419c432110016329bf0dea8898ce16fab97c6d9106dc0b348", size = 904394 }, + { url = "https://files.pythonhosted.org/packages/77/b7/8cee519b11bdd3f76c1a6eb537ab13c1bfef2964d725717705c86f524e4c/pyzmq-25.1.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3e124e6b1dd3dfbeb695435dff0e383256655bb18082e094a8dd1f6293114642", size = 1161453 }, + { url = "https://files.pythonhosted.org/packages/b6/1d/c35a956a44b333b064ae1b1c588c2dfa0e01b7ec90884c1972bfcef119c3/pyzmq-25.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7598d2ba821caa37a0f9d54c25164a4fa351ce019d64d0b44b45540950458840", size = 1105501 }, + { url = "https://files.pythonhosted.org/packages/18/d1/b3d1e985318ed7287737ea9e6b6e21748cc7c89accc2443347cd2c8d5f0f/pyzmq-25.1.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d1299d7e964c13607efd148ca1f07dcbf27c3ab9e125d1d0ae1d580a1682399d", size = 1109513 }, + { url = "https://files.pythonhosted.org/packages/14/9b/341cdfb47440069010101403298dc24d449150370c6cb322e73bfa1949bd/pyzmq-25.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4e6f689880d5ad87918430957297c975203a082d9a036cc426648fcbedae769b", size = 1433541 }, + { url = "https://files.pythonhosted.org/packages/fa/52/c6d4e76e020c554e965459d41a98201b4d45277a288648f53a4e5a2429cc/pyzmq-25.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cc69949484171cc961e6ecd4a8911b9ce7a0d1f738fcae717177c231bf77437b", size = 1766133 }, + { url = "https://files.pythonhosted.org/packages/1d/6d/0cbd8dd5b8979fd6b9cf1852ed067b9d2cd6fa0c09c3bafe6874d2d2e03c/pyzmq-25.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9880078f683466b7f567b8624bfc16cad65077be046b6e8abb53bed4eeb82dd3", size = 1653636 }, + { url = "https://files.pythonhosted.org/packages/f5/af/d90eed9cf3840685d54d4a35d5f9e242a8a48b5410d41146f14c1e098302/pyzmq-25.1.2-cp311-cp311-win32.whl", hash = "sha256:4e5837af3e5aaa99a091302df5ee001149baff06ad22b722d34e30df5f0d9097", size = 904865 }, + { url = "https://files.pythonhosted.org/packages/20/d2/09443dc73053ad01c846d7fb77e09fe9d93c09d4e900215f3c8b7b56bfec/pyzmq-25.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:25c2dbb97d38b5ac9fd15586e048ec5eb1e38f3d47fe7d92167b0c77bb3584e9", size = 1171332 }, + { url = "https://files.pythonhosted.org/packages/6e/f0/d71cf69dc039c9adc8b625efc3bad3684f3660a570e47f0f0c64df787b41/pyzmq-25.1.2-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:11e70516688190e9c2db14fcf93c04192b02d457b582a1f6190b154691b4c93a", size = 1871111 }, + { url = "https://files.pythonhosted.org/packages/68/62/d365773edf56ad71993579ee574105f02f83530caf600ebf28bea15d88d0/pyzmq-25.1.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:313c3794d650d1fccaaab2df942af9f2c01d6217c846177cfcbc693c7410839e", size = 1248844 }, + { url = "https://files.pythonhosted.org/packages/72/55/cc3163e20f40615a49245fa7041badec6103e8ee7e482dbb0feea00a7b84/pyzmq-25.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b3cbba2f47062b85fe0ef9de5b987612140a9ba3a9c6d2543c6dec9f7c2ab27", size = 899373 }, + { url = "https://files.pythonhosted.org/packages/40/aa/ae292bd85deda637230970bbc53c1dc53696a99e82fc7cd6d373ec173853/pyzmq-25.1.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc31baa0c32a2ca660784d5af3b9487e13b61b3032cb01a115fce6588e1bed30", size = 1160901 }, + { url = "https://files.pythonhosted.org/packages/93/b7/6e291eafbbbc66d0e87658dd21383ec2b4ab35edcfb283902c580a6db76f/pyzmq-25.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02c9087b109070c5ab0b383079fa1b5f797f8d43e9a66c07a4b8b8bdecfd88ee", size = 1101147 }, + { url = "https://files.pythonhosted.org/packages/3a/f1/e296d5a507eac519d1fe1382851b1a4575f690bc2b2d2c8eca2ed7e4bd1f/pyzmq-25.1.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f8429b17cbb746c3e043cb986328da023657e79d5ed258b711c06a70c2ea7537", size = 1105315 }, + { url = "https://files.pythonhosted.org/packages/56/63/5c2abb556ab4cf013d98e01782d5bd642238a0ed9b019e965a7d7e957f56/pyzmq-25.1.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5074adeacede5f810b7ef39607ee59d94e948b4fd954495bdb072f8c54558181", size = 1427747 }, + { url = "https://files.pythonhosted.org/packages/b1/71/5dba5f6b12ef54fb977c9b9279075e151c04fc0dd6851e9663d9e66b593f/pyzmq-25.1.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7ae8f354b895cbd85212da245f1a5ad8159e7840e37d78b476bb4f4c3f32a9fe", size = 1762221 }, + { url = "https://files.pythonhosted.org/packages/cf/49/54d7e8bb3df82a3509325b11491d33450dc91580d4826b62fa5e554bb9cf/pyzmq-25.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b264bf2cc96b5bc43ce0e852be995e400376bd87ceb363822e2cb1964fcdc737", size = 1649505 }, + { url = "https://files.pythonhosted.org/packages/34/14/58e5037229bc37963e2ce804c2c075a3a541e3f84bf1c231e7c9779d36f1/pyzmq-25.1.2-cp312-cp312-win32.whl", hash = "sha256:02bbc1a87b76e04fd780b45e7f695471ae6de747769e540da909173d50ff8e2d", size = 954891 }, + { url = "https://files.pythonhosted.org/packages/2c/2d/04fab685ef3a8e6e955220fd2a54dc99efaee960a88675bf5c92cd277164/pyzmq-25.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:ced111c2e81506abd1dc142e6cd7b68dd53747b3b7ae5edbea4578c5eeff96b7", size = 1252773 }, + { url = "https://files.pythonhosted.org/packages/6b/fe/ed38fe12c540bafc1cae32c3ff638e9df32528f5cf91b5e400e6a8f5b3ec/pyzmq-25.1.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a8c1d566344aee826b74e472e16edae0a02e2a044f14f7c24e123002dcff1c05", size = 963654 }, + { url = "https://files.pythonhosted.org/packages/44/97/a760a2dff0672c408f22f726f2ea10a7a516ffa5001ca5a3641e355a45f9/pyzmq-25.1.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:759cfd391a0996345ba94b6a5110fca9c557ad4166d86a6e81ea526c376a01e8", size = 609436 }, + { url = "https://files.pythonhosted.org/packages/41/81/ace39daa19c78b2f4fc12ef217d9d5f1ac658d5828d692bbbb68240cd55b/pyzmq-25.1.2-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c61e346ac34b74028ede1c6b4bcecf649d69b707b3ff9dc0fab453821b04d1e", size = 843396 }, + { url = "https://files.pythonhosted.org/packages/4c/43/150b0b203f5461a9aeadaa925c55167e2b4215c9322b6911a64360d2243e/pyzmq-25.1.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cb8fc1f8d69b411b8ec0b5f1ffbcaf14c1db95b6bccea21d83610987435f1a4", size = 800856 }, + { url = "https://files.pythonhosted.org/packages/5f/91/a618b56aaabe40dddcd25db85624d7408768fd32f5bfcf81bc0af5b1ce75/pyzmq-25.1.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3c00c9b7d1ca8165c610437ca0c92e7b5607b2f9076f4eb4b095c85d6e680a1d", size = 413836 }, ] [[package]] @@ -2453,9 +2521,30 @@ dependencies = [ { name = "scikit-learn", version = "1.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3e/2d/bab8babd9dc9a9e4df6eb115540cee4322c1a74078fb6f3b3ebc452a22b3/qudida-0.0.4.tar.gz", hash = "sha256:db198e2887ab0c9aa0023e565afbff41dfb76b361f85fd5e13f780d75ba18cc8", size = 3100, upload-time = "2021-08-09T16:47:55.807Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/2d/bab8babd9dc9a9e4df6eb115540cee4322c1a74078fb6f3b3ebc452a22b3/qudida-0.0.4.tar.gz", hash = "sha256:db198e2887ab0c9aa0023e565afbff41dfb76b361f85fd5e13f780d75ba18cc8", size = 3100 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/a1/a5f4bebaa31d109003909809d88aeb0d4b201463a9ea29308d9e4f9e7655/qudida-0.0.4-py3-none-any.whl", hash = "sha256:4519714c40cd0f2e6c51e1735edae8f8b19f4efe1f33be13e9d644ca5f736dd6", size = 3478, upload-time = "2021-08-09T16:47:54.637Z" }, + { url = "https://files.pythonhosted.org/packages/f0/a1/a5f4bebaa31d109003909809d88aeb0d4b201463a9ea29308d9e4f9e7655/qudida-0.0.4-py3-none-any.whl", hash = "sha256:4519714c40cd0f2e6c51e1735edae8f8b19f4efe1f33be13e9d644ca5f736dd6", size = 3478 }, +] + +[[package]] +name = "rapidocr" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorlog" }, + { name = "numpy" }, + { name = "omegaconf" }, + { name = "opencv-python" }, + { name = "pillow" }, + { name = "pyclipper" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "shapely" }, + { name = "six" }, + { name = "tqdm" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/e4/09ec2657421f1f23eec6b40ecbee77bbf3ff053c1483d8f2ed62d285bcf3/rapidocr-3.4.0-py3-none-any.whl", hash = "sha256:08d72f4c3a566bc76ac5c8d65d1e1c39550222b3b41b73aef976914ce80f48db", size = 15055924 }, ] [[package]] @@ -2468,9 +2557,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" } +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" }, + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, ] [[package]] @@ -2481,9 +2570,9 @@ dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441, upload-time = "2025-07-25T07:32:58.125Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" }, + { url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368 }, ] [[package]] @@ -2496,9 +2585,9 @@ dependencies = [ { name = "ruamel-yaml" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/db/76b40afe343f8a8c5222300da425e0dace30ce639a94776468b1d157311b/rknn_toolkit_lite2-2.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:821e80c95e6838308c133915660b1a6ae78bb8d079b2cbbd46a02dae61192d33", size = 559386, upload-time = "2025-04-09T09:39:54.414Z" }, - { url = "https://files.pythonhosted.org/packages/c1/3d/e80e1742420f62cb628d40a8bf547d6f7c9dbe4e13dcb7b7e7c0b5620e74/rknn_toolkit_lite2-2.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bda74f1179e15fccb8726054a24898982522784b65bb340b20146955d254e800", size = 569160, upload-time = "2025-04-09T09:39:56.149Z" }, - { url = "https://files.pythonhosted.org/packages/ff/db/64c756f3f06b219e92ff4f0fd4e000870ee49f214d505ff01c8b0275e26d/rknn_toolkit_lite2-2.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1e4ec691fed900c0e6fde5e7d8eeba17f806aa45092b63b361ee775e2c1b50e", size = 527458, upload-time = "2025-04-09T09:39:58.881Z" }, + { url = "https://files.pythonhosted.org/packages/ab/db/76b40afe343f8a8c5222300da425e0dace30ce639a94776468b1d157311b/rknn_toolkit_lite2-2.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:821e80c95e6838308c133915660b1a6ae78bb8d079b2cbbd46a02dae61192d33", size = 559386 }, + { url = "https://files.pythonhosted.org/packages/c1/3d/e80e1742420f62cb628d40a8bf547d6f7c9dbe4e13dcb7b7e7c0b5620e74/rknn_toolkit_lite2-2.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bda74f1179e15fccb8726054a24898982522784b65bb340b20146955d254e800", size = 569160 }, + { url = "https://files.pythonhosted.org/packages/ff/db/64c756f3f06b219e92ff4f0fd4e000870ee49f214d505ff01c8b0275e26d/rknn_toolkit_lite2-2.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1e4ec691fed900c0e6fde5e7d8eeba17f806aa45092b63b361ee775e2c1b50e", size = 527458 }, ] [[package]] @@ -2508,79 +2597,79 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ruamel-yaml-clib", marker = "python_full_version < '3.13' and platform_python_implementation == 'CPython'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ea/46/f44d8be06b85bc7c4d8c95d658be2b68f27711f279bf9dd0612a5e4794f5/ruamel.yaml-0.18.10.tar.gz", hash = "sha256:20c86ab29ac2153f80a428e1254a8adf686d3383df04490514ca3b79a362db58", size = 143447, upload-time = "2025-01-06T14:08:51.334Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/46/f44d8be06b85bc7c4d8c95d658be2b68f27711f279bf9dd0612a5e4794f5/ruamel.yaml-0.18.10.tar.gz", hash = "sha256:20c86ab29ac2153f80a428e1254a8adf686d3383df04490514ca3b79a362db58", size = 143447 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/36/dfc1ebc0081e6d39924a2cc53654497f967a084a436bb64402dfce4254d9/ruamel.yaml-0.18.10-py3-none-any.whl", hash = "sha256:30f22513ab2301b3d2b577adc121c6471f28734d3d9728581245f1e76468b4f1", size = 117729, upload-time = "2025-01-06T14:08:47.471Z" }, + { url = "https://files.pythonhosted.org/packages/c2/36/dfc1ebc0081e6d39924a2cc53654497f967a084a436bb64402dfce4254d9/ruamel.yaml-0.18.10-py3-none-any.whl", hash = "sha256:30f22513ab2301b3d2b577adc121c6471f28734d3d9728581245f1e76468b4f1", size = 117729 }, ] [[package]] name = "ruamel-yaml-clib" version = "0.2.12" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/84/80203abff8ea4993a87d823a5f632e4d92831ef75d404c9fc78d0176d2b5/ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f", size = 225315, upload-time = "2024-10-20T10:10:56.22Z" } +sdist = { url = "https://files.pythonhosted.org/packages/20/84/80203abff8ea4993a87d823a5f632e4d92831ef75d404c9fc78d0176d2b5/ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f", size = 225315 } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/57/40a958e863e299f0c74ef32a3bde9f2d1ea8d69669368c0c502a0997f57f/ruamel.yaml.clib-0.2.12-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:11f891336688faf5156a36293a9c362bdc7c88f03a8a027c2c1d8e0bcde998e5", size = 131301, upload-time = "2024-10-20T10:12:35.876Z" }, - { url = "https://files.pythonhosted.org/packages/98/a8/29a3eb437b12b95f50a6bcc3d7d7214301c6c529d8fdc227247fa84162b5/ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a606ef75a60ecf3d924613892cc603b154178ee25abb3055db5062da811fd969", size = 633728, upload-time = "2024-10-20T10:12:37.858Z" }, - { url = "https://files.pythonhosted.org/packages/35/6d/ae05a87a3ad540259c3ad88d71275cbd1c0f2d30ae04c65dcbfb6dcd4b9f/ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd5415dded15c3822597455bc02bcd66e81ef8b7a48cb71a33628fc9fdde39df", size = 722230, upload-time = "2024-10-20T10:12:39.457Z" }, - { url = "https://files.pythonhosted.org/packages/7f/b7/20c6f3c0b656fe609675d69bc135c03aac9e3865912444be6339207b6648/ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76", size = 686712, upload-time = "2024-10-20T10:12:41.119Z" }, - { url = "https://files.pythonhosted.org/packages/cd/11/d12dbf683471f888d354dac59593873c2b45feb193c5e3e0f2ebf85e68b9/ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6", size = 663936, upload-time = "2024-10-21T11:26:37.419Z" }, - { url = "https://files.pythonhosted.org/packages/72/14/4c268f5077db5c83f743ee1daeb236269fa8577133a5cfa49f8b382baf13/ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd", size = 696580, upload-time = "2024-10-21T11:26:39.503Z" }, - { url = "https://files.pythonhosted.org/packages/30/fc/8cd12f189c6405a4c1cf37bd633aa740a9538c8e40497c231072d0fef5cf/ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a", size = 663393, upload-time = "2024-12-11T19:58:13.873Z" }, - { url = "https://files.pythonhosted.org/packages/80/29/c0a017b704aaf3cbf704989785cd9c5d5b8ccec2dae6ac0c53833c84e677/ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da", size = 100326, upload-time = "2024-10-20T10:12:42.967Z" }, - { url = "https://files.pythonhosted.org/packages/3a/65/fa39d74db4e2d0cd252355732d966a460a41cd01c6353b820a0952432839/ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28", size = 118079, upload-time = "2024-10-20T10:12:44.117Z" }, - { url = "https://files.pythonhosted.org/packages/fb/8f/683c6ad562f558cbc4f7c029abcd9599148c51c54b5ef0f24f2638da9fbb/ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6", size = 132224, upload-time = "2024-10-20T10:12:45.162Z" }, - { url = "https://files.pythonhosted.org/packages/3c/d2/b79b7d695e2f21da020bd44c782490578f300dd44f0a4c57a92575758a76/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e", size = 641480, upload-time = "2024-10-20T10:12:46.758Z" }, - { url = "https://files.pythonhosted.org/packages/68/6e/264c50ce2a31473a9fdbf4fa66ca9b2b17c7455b31ef585462343818bd6c/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e", size = 739068, upload-time = "2024-10-20T10:12:48.605Z" }, - { url = "https://files.pythonhosted.org/packages/86/29/88c2567bc893c84d88b4c48027367c3562ae69121d568e8a3f3a8d363f4d/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52", size = 703012, upload-time = "2024-10-20T10:12:51.124Z" }, - { url = "https://files.pythonhosted.org/packages/11/46/879763c619b5470820f0cd6ca97d134771e502776bc2b844d2adb6e37753/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642", size = 704352, upload-time = "2024-10-21T11:26:41.438Z" }, - { url = "https://files.pythonhosted.org/packages/02/80/ece7e6034256a4186bbe50dee28cd032d816974941a6abf6a9d65e4228a7/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2", size = 737344, upload-time = "2024-10-21T11:26:43.62Z" }, - { url = "https://files.pythonhosted.org/packages/f0/ca/e4106ac7e80efbabdf4bf91d3d32fc424e41418458251712f5672eada9ce/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3", size = 714498, upload-time = "2024-12-11T19:58:15.592Z" }, - { url = "https://files.pythonhosted.org/packages/67/58/b1f60a1d591b771298ffa0428237afb092c7f29ae23bad93420b1eb10703/ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4", size = 100205, upload-time = "2024-10-20T10:12:52.865Z" }, - { url = "https://files.pythonhosted.org/packages/b4/4f/b52f634c9548a9291a70dfce26ca7ebce388235c93588a1068028ea23fcc/ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb", size = 118185, upload-time = "2024-10-20T10:12:54.652Z" }, - { url = "https://files.pythonhosted.org/packages/48/41/e7a405afbdc26af961678474a55373e1b323605a4f5e2ddd4a80ea80f628/ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632", size = 133433, upload-time = "2024-10-20T10:12:55.657Z" }, - { url = "https://files.pythonhosted.org/packages/ec/b0/b850385604334c2ce90e3ee1013bd911aedf058a934905863a6ea95e9eb4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d", size = 647362, upload-time = "2024-10-20T10:12:57.155Z" }, - { url = "https://files.pythonhosted.org/packages/44/d0/3f68a86e006448fb6c005aee66565b9eb89014a70c491d70c08de597f8e4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c", size = 754118, upload-time = "2024-10-20T10:12:58.501Z" }, - { url = "https://files.pythonhosted.org/packages/52/a9/d39f3c5ada0a3bb2870d7db41901125dbe2434fa4f12ca8c5b83a42d7c53/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd", size = 706497, upload-time = "2024-10-20T10:13:00.211Z" }, - { url = "https://files.pythonhosted.org/packages/b0/fa/097e38135dadd9ac25aecf2a54be17ddf6e4c23e43d538492a90ab3d71c6/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31", size = 698042, upload-time = "2024-10-21T11:26:46.038Z" }, - { url = "https://files.pythonhosted.org/packages/ec/d5/a659ca6f503b9379b930f13bc6b130c9f176469b73b9834296822a83a132/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680", size = 745831, upload-time = "2024-10-21T11:26:47.487Z" }, - { url = "https://files.pythonhosted.org/packages/db/5d/36619b61ffa2429eeaefaab4f3374666adf36ad8ac6330d855848d7d36fd/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d", size = 715692, upload-time = "2024-12-11T19:58:17.252Z" }, - { url = "https://files.pythonhosted.org/packages/b1/82/85cb92f15a4231c89b95dfe08b09eb6adca929ef7df7e17ab59902b6f589/ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5", size = 98777, upload-time = "2024-10-20T10:13:01.395Z" }, - { url = "https://files.pythonhosted.org/packages/d7/8f/c3654f6f1ddb75daf3922c3d8fc6005b1ab56671ad56ffb874d908bfa668/ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4", size = 115523, upload-time = "2024-10-20T10:13:02.768Z" }, - { url = "https://files.pythonhosted.org/packages/29/00/4864119668d71a5fa45678f380b5923ff410701565821925c69780356ffa/ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a", size = 132011, upload-time = "2024-10-20T10:13:04.377Z" }, - { url = "https://files.pythonhosted.org/packages/7f/5e/212f473a93ae78c669ffa0cb051e3fee1139cb2d385d2ae1653d64281507/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:e7e3736715fbf53e9be2a79eb4db68e4ed857017344d697e8b9749444ae57475", size = 642488, upload-time = "2024-10-20T10:13:05.906Z" }, - { url = "https://files.pythonhosted.org/packages/1f/8f/ecfbe2123ade605c49ef769788f79c38ddb1c8fa81e01f4dbf5cf1a44b16/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7e75b4965e1d4690e93021adfcecccbca7d61c7bddd8e22406ef2ff20d74ef", size = 745066, upload-time = "2024-10-20T10:13:07.26Z" }, - { url = "https://files.pythonhosted.org/packages/e2/a9/28f60726d29dfc01b8decdb385de4ced2ced9faeb37a847bd5cf26836815/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6", size = 701785, upload-time = "2024-10-20T10:13:08.504Z" }, - { url = "https://files.pythonhosted.org/packages/84/7e/8e7ec45920daa7f76046578e4f677a3215fe8f18ee30a9cb7627a19d9b4c/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf", size = 693017, upload-time = "2024-10-21T11:26:48.866Z" }, - { url = "https://files.pythonhosted.org/packages/c5/b3/d650eaade4ca225f02a648321e1ab835b9d361c60d51150bac49063b83fa/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1", size = 741270, upload-time = "2024-10-21T11:26:50.213Z" }, - { url = "https://files.pythonhosted.org/packages/87/b8/01c29b924dcbbed75cc45b30c30d565d763b9c4d540545a0eeecffb8f09c/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01", size = 709059, upload-time = "2024-12-11T19:58:18.846Z" }, - { url = "https://files.pythonhosted.org/packages/30/8c/ed73f047a73638257aa9377ad356bea4d96125b305c34a28766f4445cc0f/ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6", size = 98583, upload-time = "2024-10-20T10:13:09.658Z" }, - { url = "https://files.pythonhosted.org/packages/b0/85/e8e751d8791564dd333d5d9a4eab0a7a115f7e349595417fd50ecae3395c/ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3", size = 115190, upload-time = "2024-10-20T10:13:10.66Z" }, + { url = "https://files.pythonhosted.org/packages/70/57/40a958e863e299f0c74ef32a3bde9f2d1ea8d69669368c0c502a0997f57f/ruamel.yaml.clib-0.2.12-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:11f891336688faf5156a36293a9c362bdc7c88f03a8a027c2c1d8e0bcde998e5", size = 131301 }, + { url = "https://files.pythonhosted.org/packages/98/a8/29a3eb437b12b95f50a6bcc3d7d7214301c6c529d8fdc227247fa84162b5/ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a606ef75a60ecf3d924613892cc603b154178ee25abb3055db5062da811fd969", size = 633728 }, + { url = "https://files.pythonhosted.org/packages/35/6d/ae05a87a3ad540259c3ad88d71275cbd1c0f2d30ae04c65dcbfb6dcd4b9f/ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd5415dded15c3822597455bc02bcd66e81ef8b7a48cb71a33628fc9fdde39df", size = 722230 }, + { url = "https://files.pythonhosted.org/packages/7f/b7/20c6f3c0b656fe609675d69bc135c03aac9e3865912444be6339207b6648/ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76", size = 686712 }, + { url = "https://files.pythonhosted.org/packages/cd/11/d12dbf683471f888d354dac59593873c2b45feb193c5e3e0f2ebf85e68b9/ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6", size = 663936 }, + { url = "https://files.pythonhosted.org/packages/72/14/4c268f5077db5c83f743ee1daeb236269fa8577133a5cfa49f8b382baf13/ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd", size = 696580 }, + { url = "https://files.pythonhosted.org/packages/30/fc/8cd12f189c6405a4c1cf37bd633aa740a9538c8e40497c231072d0fef5cf/ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a", size = 663393 }, + { url = "https://files.pythonhosted.org/packages/80/29/c0a017b704aaf3cbf704989785cd9c5d5b8ccec2dae6ac0c53833c84e677/ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da", size = 100326 }, + { url = "https://files.pythonhosted.org/packages/3a/65/fa39d74db4e2d0cd252355732d966a460a41cd01c6353b820a0952432839/ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28", size = 118079 }, + { url = "https://files.pythonhosted.org/packages/fb/8f/683c6ad562f558cbc4f7c029abcd9599148c51c54b5ef0f24f2638da9fbb/ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6", size = 132224 }, + { url = "https://files.pythonhosted.org/packages/3c/d2/b79b7d695e2f21da020bd44c782490578f300dd44f0a4c57a92575758a76/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e", size = 641480 }, + { url = "https://files.pythonhosted.org/packages/68/6e/264c50ce2a31473a9fdbf4fa66ca9b2b17c7455b31ef585462343818bd6c/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e", size = 739068 }, + { url = "https://files.pythonhosted.org/packages/86/29/88c2567bc893c84d88b4c48027367c3562ae69121d568e8a3f3a8d363f4d/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52", size = 703012 }, + { url = "https://files.pythonhosted.org/packages/11/46/879763c619b5470820f0cd6ca97d134771e502776bc2b844d2adb6e37753/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642", size = 704352 }, + { url = "https://files.pythonhosted.org/packages/02/80/ece7e6034256a4186bbe50dee28cd032d816974941a6abf6a9d65e4228a7/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2", size = 737344 }, + { url = "https://files.pythonhosted.org/packages/f0/ca/e4106ac7e80efbabdf4bf91d3d32fc424e41418458251712f5672eada9ce/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3", size = 714498 }, + { url = "https://files.pythonhosted.org/packages/67/58/b1f60a1d591b771298ffa0428237afb092c7f29ae23bad93420b1eb10703/ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4", size = 100205 }, + { url = "https://files.pythonhosted.org/packages/b4/4f/b52f634c9548a9291a70dfce26ca7ebce388235c93588a1068028ea23fcc/ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb", size = 118185 }, + { url = "https://files.pythonhosted.org/packages/48/41/e7a405afbdc26af961678474a55373e1b323605a4f5e2ddd4a80ea80f628/ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632", size = 133433 }, + { url = "https://files.pythonhosted.org/packages/ec/b0/b850385604334c2ce90e3ee1013bd911aedf058a934905863a6ea95e9eb4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d", size = 647362 }, + { url = "https://files.pythonhosted.org/packages/44/d0/3f68a86e006448fb6c005aee66565b9eb89014a70c491d70c08de597f8e4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c", size = 754118 }, + { url = "https://files.pythonhosted.org/packages/52/a9/d39f3c5ada0a3bb2870d7db41901125dbe2434fa4f12ca8c5b83a42d7c53/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd", size = 706497 }, + { url = "https://files.pythonhosted.org/packages/b0/fa/097e38135dadd9ac25aecf2a54be17ddf6e4c23e43d538492a90ab3d71c6/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31", size = 698042 }, + { url = "https://files.pythonhosted.org/packages/ec/d5/a659ca6f503b9379b930f13bc6b130c9f176469b73b9834296822a83a132/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680", size = 745831 }, + { url = "https://files.pythonhosted.org/packages/db/5d/36619b61ffa2429eeaefaab4f3374666adf36ad8ac6330d855848d7d36fd/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d", size = 715692 }, + { url = "https://files.pythonhosted.org/packages/b1/82/85cb92f15a4231c89b95dfe08b09eb6adca929ef7df7e17ab59902b6f589/ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5", size = 98777 }, + { url = "https://files.pythonhosted.org/packages/d7/8f/c3654f6f1ddb75daf3922c3d8fc6005b1ab56671ad56ffb874d908bfa668/ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4", size = 115523 }, + { url = "https://files.pythonhosted.org/packages/29/00/4864119668d71a5fa45678f380b5923ff410701565821925c69780356ffa/ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a", size = 132011 }, + { url = "https://files.pythonhosted.org/packages/7f/5e/212f473a93ae78c669ffa0cb051e3fee1139cb2d385d2ae1653d64281507/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:e7e3736715fbf53e9be2a79eb4db68e4ed857017344d697e8b9749444ae57475", size = 642488 }, + { url = "https://files.pythonhosted.org/packages/1f/8f/ecfbe2123ade605c49ef769788f79c38ddb1c8fa81e01f4dbf5cf1a44b16/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7e75b4965e1d4690e93021adfcecccbca7d61c7bddd8e22406ef2ff20d74ef", size = 745066 }, + { url = "https://files.pythonhosted.org/packages/e2/a9/28f60726d29dfc01b8decdb385de4ced2ced9faeb37a847bd5cf26836815/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6", size = 701785 }, + { url = "https://files.pythonhosted.org/packages/84/7e/8e7ec45920daa7f76046578e4f677a3215fe8f18ee30a9cb7627a19d9b4c/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf", size = 693017 }, + { url = "https://files.pythonhosted.org/packages/c5/b3/d650eaade4ca225f02a648321e1ab835b9d361c60d51150bac49063b83fa/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1", size = 741270 }, + { url = "https://files.pythonhosted.org/packages/87/b8/01c29b924dcbbed75cc45b30c30d565d763b9c4d540545a0eeecffb8f09c/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01", size = 709059 }, + { url = "https://files.pythonhosted.org/packages/30/8c/ed73f047a73638257aa9377ad356bea4d96125b305c34a28766f4445cc0f/ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6", size = 98583 }, + { url = "https://files.pythonhosted.org/packages/b0/85/e8e751d8791564dd333d5d9a4eab0a7a115f7e349595417fd50ecae3395c/ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3", size = 115190 }, ] [[package]] name = "ruff" -version = "0.13.0" +version = "0.12.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6e/1a/1f4b722862840295bcaba8c9e5261572347509548faaa99b2d57ee7bfe6a/ruff-0.13.0.tar.gz", hash = "sha256:5b4b1ee7eb35afae128ab94459b13b2baaed282b1fb0f472a73c82c996c8ae60", size = 5372863, upload-time = "2025-09-10T16:25:37.917Z" } +sdist = { url = "https://files.pythonhosted.org/packages/de/55/16ab6a7d88d93001e1ae4c34cbdcfb376652d761799459ff27c1dc20f6fa/ruff-0.12.11.tar.gz", hash = "sha256:c6b09ae8426a65bbee5425b9d0b82796dbb07cb1af045743c79bfb163001165d", size = 5347103 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/fe/6f87b419dbe166fd30a991390221f14c5b68946f389ea07913e1719741e0/ruff-0.13.0-py3-none-linux_armv6l.whl", hash = "sha256:137f3d65d58ee828ae136a12d1dc33d992773d8f7644bc6b82714570f31b2004", size = 12187826, upload-time = "2025-09-10T16:24:39.5Z" }, - { url = "https://files.pythonhosted.org/packages/e4/25/c92296b1fc36d2499e12b74a3fdb230f77af7bdf048fad7b0a62e94ed56a/ruff-0.13.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:21ae48151b66e71fd111b7d79f9ad358814ed58c339631450c66a4be33cc28b9", size = 12933428, upload-time = "2025-09-10T16:24:43.866Z" }, - { url = "https://files.pythonhosted.org/packages/44/cf/40bc7221a949470307d9c35b4ef5810c294e6cfa3caafb57d882731a9f42/ruff-0.13.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:64de45f4ca5441209e41742d527944635a05a6e7c05798904f39c85bafa819e3", size = 12095543, upload-time = "2025-09-10T16:24:46.638Z" }, - { url = "https://files.pythonhosted.org/packages/f1/03/8b5ff2a211efb68c63a1d03d157e924997ada87d01bebffbd13a0f3fcdeb/ruff-0.13.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b2c653ae9b9d46e0ef62fc6fbf5b979bda20a0b1d2b22f8f7eb0cde9f4963b8", size = 12312489, upload-time = "2025-09-10T16:24:49.556Z" }, - { url = "https://files.pythonhosted.org/packages/37/fc/2336ef6d5e9c8d8ea8305c5f91e767d795cd4fc171a6d97ef38a5302dadc/ruff-0.13.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4cec632534332062bc9eb5884a267b689085a1afea9801bf94e3ba7498a2d207", size = 11991631, upload-time = "2025-09-10T16:24:53.439Z" }, - { url = "https://files.pythonhosted.org/packages/39/7f/f6d574d100fca83d32637d7f5541bea2f5e473c40020bbc7fc4a4d5b7294/ruff-0.13.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dcd628101d9f7d122e120ac7c17e0a0f468b19bc925501dbe03c1cb7f5415b24", size = 13720602, upload-time = "2025-09-10T16:24:56.392Z" }, - { url = "https://files.pythonhosted.org/packages/fd/c8/a8a5b81d8729b5d1f663348d11e2a9d65a7a9bd3c399763b1a51c72be1ce/ruff-0.13.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:afe37db8e1466acb173bb2a39ca92df00570e0fd7c94c72d87b51b21bb63efea", size = 14697751, upload-time = "2025-09-10T16:24:59.89Z" }, - { url = "https://files.pythonhosted.org/packages/57/f5/183ec292272ce7ec5e882aea74937f7288e88ecb500198b832c24debc6d3/ruff-0.13.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f96a8d90bb258d7d3358b372905fe7333aaacf6c39e2408b9f8ba181f4b6ef2", size = 14095317, upload-time = "2025-09-10T16:25:03.025Z" }, - { url = "https://files.pythonhosted.org/packages/9f/8d/7f9771c971724701af7926c14dab31754e7b303d127b0d3f01116faef456/ruff-0.13.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b5e3d883e4f924c5298e3f2ee0f3085819c14f68d1e5b6715597681433f153", size = 13144418, upload-time = "2025-09-10T16:25:06.272Z" }, - { url = "https://files.pythonhosted.org/packages/a8/a6/7985ad1778e60922d4bef546688cd8a25822c58873e9ff30189cfe5dc4ab/ruff-0.13.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03447f3d18479df3d24917a92d768a89f873a7181a064858ea90a804a7538991", size = 13370843, upload-time = "2025-09-10T16:25:09.965Z" }, - { url = "https://files.pythonhosted.org/packages/64/1c/bafdd5a7a05a50cc51d9f5711da704942d8dd62df3d8c70c311e98ce9f8a/ruff-0.13.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:fbc6b1934eb1c0033da427c805e27d164bb713f8e273a024a7e86176d7f462cf", size = 13321891, upload-time = "2025-09-10T16:25:12.969Z" }, - { url = "https://files.pythonhosted.org/packages/bc/3e/7817f989cb9725ef7e8d2cee74186bf90555279e119de50c750c4b7a72fe/ruff-0.13.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a8ab6a3e03665d39d4a25ee199d207a488724f022db0e1fe4002968abdb8001b", size = 12119119, upload-time = "2025-09-10T16:25:16.621Z" }, - { url = "https://files.pythonhosted.org/packages/58/07/9df080742e8d1080e60c426dce6e96a8faf9a371e2ce22eef662e3839c95/ruff-0.13.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d2a5c62f8ccc6dd2fe259917482de7275cecc86141ee10432727c4816235bc41", size = 11961594, upload-time = "2025-09-10T16:25:19.49Z" }, - { url = "https://files.pythonhosted.org/packages/6a/f4/ae1185349197d26a2316840cb4d6c3fba61d4ac36ed728bf0228b222d71f/ruff-0.13.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b7b85ca27aeeb1ab421bc787009831cffe6048faae08ad80867edab9f2760945", size = 12933377, upload-time = "2025-09-10T16:25:22.371Z" }, - { url = "https://files.pythonhosted.org/packages/b6/39/e776c10a3b349fc8209a905bfb327831d7516f6058339a613a8d2aaecacd/ruff-0.13.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:79ea0c44a3032af768cabfd9616e44c24303af49d633b43e3a5096e009ebe823", size = 13418555, upload-time = "2025-09-10T16:25:25.681Z" }, - { url = "https://files.pythonhosted.org/packages/46/09/dca8df3d48e8b3f4202bf20b1658898e74b6442ac835bfe2c1816d926697/ruff-0.13.0-py3-none-win32.whl", hash = "sha256:4e473e8f0e6a04e4113f2e1de12a5039579892329ecc49958424e5568ef4f768", size = 12141613, upload-time = "2025-09-10T16:25:28.664Z" }, - { url = "https://files.pythonhosted.org/packages/61/21/0647eb71ed99b888ad50e44d8ec65d7148babc0e242d531a499a0bbcda5f/ruff-0.13.0-py3-none-win_amd64.whl", hash = "sha256:48e5c25c7a3713eea9ce755995767f4dcd1b0b9599b638b12946e892123d1efb", size = 13258250, upload-time = "2025-09-10T16:25:31.773Z" }, - { url = "https://files.pythonhosted.org/packages/e1/a3/03216a6a86c706df54422612981fb0f9041dbb452c3401501d4a22b942c9/ruff-0.13.0-py3-none-win_arm64.whl", hash = "sha256:ab80525317b1e1d38614addec8ac954f1b3e662de9d59114ecbf771d00cf613e", size = 12312357, upload-time = "2025-09-10T16:25:35.595Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a2/3b3573e474de39a7a475f3fbaf36a25600bfeb238e1a90392799163b64a0/ruff-0.12.11-py3-none-linux_armv6l.whl", hash = "sha256:93fce71e1cac3a8bf9200e63a38ac5c078f3b6baebffb74ba5274fb2ab276065", size = 11979885 }, + { url = "https://files.pythonhosted.org/packages/76/e4/235ad6d1785a2012d3ded2350fd9bc5c5af8c6f56820e696b0118dfe7d24/ruff-0.12.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8e33ac7b28c772440afa80cebb972ffd823621ded90404f29e5ab6d1e2d4b93", size = 12742364 }, + { url = "https://files.pythonhosted.org/packages/2c/0d/15b72c5fe6b1e402a543aa9d8960e0a7e19dfb079f5b0b424db48b7febab/ruff-0.12.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d69fb9d4937aa19adb2e9f058bc4fbfe986c2040acb1a4a9747734834eaa0bfd", size = 11920111 }, + { url = "https://files.pythonhosted.org/packages/3e/c0/f66339d7893798ad3e17fa5a1e587d6fd9806f7c1c062b63f8b09dda6702/ruff-0.12.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:411954eca8464595077a93e580e2918d0a01a19317af0a72132283e28ae21bee", size = 12160060 }, + { url = "https://files.pythonhosted.org/packages/03/69/9870368326db26f20c946205fb2d0008988aea552dbaec35fbacbb46efaa/ruff-0.12.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a2c0a2e1a450f387bf2c6237c727dd22191ae8c00e448e0672d624b2bbd7fb0", size = 11799848 }, + { url = "https://files.pythonhosted.org/packages/25/8c/dd2c7f990e9b3a8a55eee09d4e675027d31727ce33cdb29eab32d025bdc9/ruff-0.12.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ca4c3a7f937725fd2413c0e884b5248a19369ab9bdd850b5781348ba283f644", size = 13536288 }, + { url = "https://files.pythonhosted.org/packages/7a/30/d5496fa09aba59b5e01ea76775a4c8897b13055884f56f1c35a4194c2297/ruff-0.12.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4d1df0098124006f6a66ecf3581a7f7e754c4df7644b2e6704cd7ca80ff95211", size = 14490633 }, + { url = "https://files.pythonhosted.org/packages/9b/2f/81f998180ad53445d403c386549d6946d0748e536d58fce5b5e173511183/ruff-0.12.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a8dd5f230efc99a24ace3b77e3555d3fbc0343aeed3fc84c8d89e75ab2ff793", size = 13888430 }, + { url = "https://files.pythonhosted.org/packages/87/71/23a0d1d5892a377478c61dbbcffe82a3476b050f38b5162171942a029ef3/ruff-0.12.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4dc75533039d0ed04cd33fb8ca9ac9620b99672fe7ff1533b6402206901c34ee", size = 12913133 }, + { url = "https://files.pythonhosted.org/packages/80/22/3c6cef96627f89b344c933781ed38329bfb87737aa438f15da95907cbfd5/ruff-0.12.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fc58f9266d62c6eccc75261a665f26b4ef64840887fc6cbc552ce5b29f96cc8", size = 13169082 }, + { url = "https://files.pythonhosted.org/packages/05/b5/68b3ff96160d8b49e8dd10785ff3186be18fd650d356036a3770386e6c7f/ruff-0.12.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5a0113bd6eafd545146440225fe60b4e9489f59eb5f5f107acd715ba5f0b3d2f", size = 13139490 }, + { url = "https://files.pythonhosted.org/packages/59/b9/050a3278ecd558f74f7ee016fbdf10591d50119df8d5f5da45a22c6afafc/ruff-0.12.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0d737b4059d66295c3ea5720e6efc152623bb83fde5444209b69cd33a53e2000", size = 11958928 }, + { url = "https://files.pythonhosted.org/packages/f9/bc/93be37347db854806904a43b0493af8d6873472dfb4b4b8cbb27786eb651/ruff-0.12.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:916fc5defee32dbc1fc1650b576a8fed68f5e8256e2180d4d9855aea43d6aab2", size = 11764513 }, + { url = "https://files.pythonhosted.org/packages/7a/a1/1471751e2015a81fd8e166cd311456c11df74c7e8769d4aabfbc7584c7ac/ruff-0.12.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c984f07d7adb42d3ded5be894fb4007f30f82c87559438b4879fe7aa08c62b39", size = 12745154 }, + { url = "https://files.pythonhosted.org/packages/68/ab/2542b14890d0f4872dd81b7b2a6aed3ac1786fae1ce9b17e11e6df9e31e3/ruff-0.12.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e07fbb89f2e9249f219d88331c833860489b49cdf4b032b8e4432e9b13e8a4b9", size = 13227653 }, + { url = "https://files.pythonhosted.org/packages/22/16/2fbfc61047dbfd009c58a28369a693a1484ad15441723be1cd7fe69bb679/ruff-0.12.11-py3-none-win32.whl", hash = "sha256:c792e8f597c9c756e9bcd4d87cf407a00b60af77078c96f7b6366ea2ce9ba9d3", size = 11944270 }, + { url = "https://files.pythonhosted.org/packages/08/a5/34276984705bfe069cd383101c45077ee029c3fe3b28225bf67aa35f0647/ruff-0.12.11-py3-none-win_amd64.whl", hash = "sha256:a3283325960307915b6deb3576b96919ee89432ebd9c48771ca12ee8afe4a0fd", size = 13046600 }, + { url = "https://files.pythonhosted.org/packages/84/a8/001d4a7c2b37623a3fd7463208267fb906df40ff31db496157549cfd6e72/ruff-0.12.11-py3-none-win_arm64.whl", hash = "sha256:bae4d6e6a2676f8fb0f98b74594a048bae1b944aab17e9f5d504062303c6dbea", size = 12135290 }, ] [[package]] @@ -2598,23 +2687,23 @@ dependencies = [ { name = "scipy", version = "1.16.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "tifffile" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/65/c1/a49da20845f0f0e1afbb1c2586d406dc0acb84c26ae293bad6d7e7f718bc/scikit_image-0.22.0.tar.gz", hash = "sha256:018d734df1d2da2719087d15f679d19285fce97cd37695103deadfaef2873236", size = 22685018, upload-time = "2023-10-03T21:36:34.274Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/c1/a49da20845f0f0e1afbb1c2586d406dc0acb84c26ae293bad6d7e7f718bc/scikit_image-0.22.0.tar.gz", hash = "sha256:018d734df1d2da2719087d15f679d19285fce97cd37695103deadfaef2873236", size = 22685018 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/8c/381ae42b37cf3e9e99a1deb3ffe76ca5ff5dd18ffa368293476164507fad/scikit_image-0.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74ec5c1d4693506842cc7c9487c89d8fc32aed064e9363def7af08b8f8cbb31d", size = 13905039, upload-time = "2023-10-03T21:35:27.279Z" }, - { url = "https://files.pythonhosted.org/packages/16/06/4bfba08f5cce26d5070bb2cf4e3f9f479480978806355d1c5bea6f26a17c/scikit_image-0.22.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:a05ae4fe03d802587ed8974e900b943275548cde6a6807b785039d63e9a7a5ff", size = 13279212, upload-time = "2023-10-03T21:35:30.864Z" }, - { url = "https://files.pythonhosted.org/packages/74/57/dbf744ca00eea2a09b1848c9dec28a43978c16dc049b1fba949cb050bedf/scikit_image-0.22.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a92dca3d95b1301442af055e196a54b5a5128c6768b79fc0a4098f1d662dee6", size = 14091779, upload-time = "2023-10-03T21:35:34.273Z" }, - { url = "https://files.pythonhosted.org/packages/f1/6c/49f5a0ce8ddcdbdac5ac69c129654938cc6de0a936303caa6cad495ceb2a/scikit_image-0.22.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3663d063d8bf2fb9bdfb0ca967b9ee3b6593139c860c7abc2d2351a8a8863938", size = 14682042, upload-time = "2023-10-03T21:35:37.787Z" }, - { url = "https://files.pythonhosted.org/packages/86/f0/18895318109f9b508f2310f136922e455a453550826a8240b412063c2528/scikit_image-0.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:ebdbdc901bae14dab637f8d5c99f6d5cc7aaf4a3b6f4003194e003e9f688a6fc", size = 24492345, upload-time = "2023-10-03T21:35:41.122Z" }, - { url = "https://files.pythonhosted.org/packages/9f/d9/dc99e527d1a0050f0353d2fff3548273b4df6151884806e324f26572fd6b/scikit_image-0.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:95d6da2d8a44a36ae04437c76d32deb4e3c993ffc846b394b9949fd8ded73cb2", size = 13883619, upload-time = "2023-10-03T21:35:44.88Z" }, - { url = "https://files.pythonhosted.org/packages/80/37/7670020b112ff9a47e49b1e36f438d000db5b632aab8a8fd7e6be545d065/scikit_image-0.22.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:2c6ef454a85f569659b813ac2a93948022b0298516b757c9c6c904132be327e2", size = 13264761, upload-time = "2023-10-03T21:35:48.865Z" }, - { url = "https://files.pythonhosted.org/packages/ad/85/dadf1194793ac1c895370f3ed048bb91dda083775b42e11d9672a50494d5/scikit_image-0.22.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e87872f067444ee90a00dd49ca897208308645382e8a24bd3e76f301af2352cd", size = 14070710, upload-time = "2023-10-03T21:35:51.711Z" }, - { url = "https://files.pythonhosted.org/packages/d4/34/e27bf2bfe7b52b884b49bd71ea91ff81e4737246735ee5ea383314c31876/scikit_image-0.22.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5c378db54e61b491b9edeefff87e49fcf7fdf729bb93c777d7a5f15d36f743e", size = 14664172, upload-time = "2023-10-03T21:35:55.752Z" }, - { url = "https://files.pythonhosted.org/packages/ce/d0/a3f60c9f57ed295b3076e4acdb29a37bbd8823452562ab2ad51b03d6f377/scikit_image-0.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:2bcb74adb0634258a67f66c2bb29978c9a3e222463e003b67ba12056c003971b", size = 24491321, upload-time = "2023-10-03T21:35:58.847Z" }, - { url = "https://files.pythonhosted.org/packages/da/a4/b0b69bde4d6360e801d647691591dc9967a25a18a4c63ecf7f87d94e3fac/scikit_image-0.22.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:003ca2274ac0fac252280e7179ff986ff783407001459ddea443fe7916e38cff", size = 13968808, upload-time = "2023-10-03T21:36:02.526Z" }, - { url = "https://files.pythonhosted.org/packages/e4/65/3c0f77e7a9bae100a8f7f5cebde410fca1a3cf64e1ecdd343666e27b11d4/scikit_image-0.22.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:cf3c0c15b60ae3e557a0c7575fbd352f0c3ce0afca562febfe3ab80efbeec0e9", size = 13323763, upload-time = "2023-10-03T21:36:05.504Z" }, - { url = "https://files.pythonhosted.org/packages/4a/ed/7faf9f7a55d5b3095d33990a85603b66866cce2a608b27f0e1487d70a451/scikit_image-0.22.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5b23908dd4d120e6aecb1ed0277563e8cbc8d6c0565bdc4c4c6475d53608452", size = 13877233, upload-time = "2023-10-03T21:36:08.352Z" }, - { url = "https://files.pythonhosted.org/packages/ae/9d/09d06f36ce71fa276e1d9453fb4b04250a7038292b13b8c273a5a1a8f7c0/scikit_image-0.22.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be79d7493f320a964f8fcf603121595ba82f84720de999db0fcca002266a549a", size = 14954814, upload-time = "2023-10-03T21:36:11.871Z" }, - { url = "https://files.pythonhosted.org/packages/dc/35/e6327ae498c6f557cb0a7c3fc284effe7958d2d1c43fb61cd77804fc2c4f/scikit_image-0.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:722b970aa5da725dca55252c373b18bbea7858c1cdb406e19f9b01a4a73b30b2", size = 25004857, upload-time = "2023-10-03T21:36:15.457Z" }, + { url = "https://files.pythonhosted.org/packages/9c/8c/381ae42b37cf3e9e99a1deb3ffe76ca5ff5dd18ffa368293476164507fad/scikit_image-0.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74ec5c1d4693506842cc7c9487c89d8fc32aed064e9363def7af08b8f8cbb31d", size = 13905039 }, + { url = "https://files.pythonhosted.org/packages/16/06/4bfba08f5cce26d5070bb2cf4e3f9f479480978806355d1c5bea6f26a17c/scikit_image-0.22.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:a05ae4fe03d802587ed8974e900b943275548cde6a6807b785039d63e9a7a5ff", size = 13279212 }, + { url = "https://files.pythonhosted.org/packages/74/57/dbf744ca00eea2a09b1848c9dec28a43978c16dc049b1fba949cb050bedf/scikit_image-0.22.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a92dca3d95b1301442af055e196a54b5a5128c6768b79fc0a4098f1d662dee6", size = 14091779 }, + { url = "https://files.pythonhosted.org/packages/f1/6c/49f5a0ce8ddcdbdac5ac69c129654938cc6de0a936303caa6cad495ceb2a/scikit_image-0.22.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3663d063d8bf2fb9bdfb0ca967b9ee3b6593139c860c7abc2d2351a8a8863938", size = 14682042 }, + { url = "https://files.pythonhosted.org/packages/86/f0/18895318109f9b508f2310f136922e455a453550826a8240b412063c2528/scikit_image-0.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:ebdbdc901bae14dab637f8d5c99f6d5cc7aaf4a3b6f4003194e003e9f688a6fc", size = 24492345 }, + { url = "https://files.pythonhosted.org/packages/9f/d9/dc99e527d1a0050f0353d2fff3548273b4df6151884806e324f26572fd6b/scikit_image-0.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:95d6da2d8a44a36ae04437c76d32deb4e3c993ffc846b394b9949fd8ded73cb2", size = 13883619 }, + { url = "https://files.pythonhosted.org/packages/80/37/7670020b112ff9a47e49b1e36f438d000db5b632aab8a8fd7e6be545d065/scikit_image-0.22.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:2c6ef454a85f569659b813ac2a93948022b0298516b757c9c6c904132be327e2", size = 13264761 }, + { url = "https://files.pythonhosted.org/packages/ad/85/dadf1194793ac1c895370f3ed048bb91dda083775b42e11d9672a50494d5/scikit_image-0.22.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e87872f067444ee90a00dd49ca897208308645382e8a24bd3e76f301af2352cd", size = 14070710 }, + { url = "https://files.pythonhosted.org/packages/d4/34/e27bf2bfe7b52b884b49bd71ea91ff81e4737246735ee5ea383314c31876/scikit_image-0.22.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5c378db54e61b491b9edeefff87e49fcf7fdf729bb93c777d7a5f15d36f743e", size = 14664172 }, + { url = "https://files.pythonhosted.org/packages/ce/d0/a3f60c9f57ed295b3076e4acdb29a37bbd8823452562ab2ad51b03d6f377/scikit_image-0.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:2bcb74adb0634258a67f66c2bb29978c9a3e222463e003b67ba12056c003971b", size = 24491321 }, + { url = "https://files.pythonhosted.org/packages/da/a4/b0b69bde4d6360e801d647691591dc9967a25a18a4c63ecf7f87d94e3fac/scikit_image-0.22.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:003ca2274ac0fac252280e7179ff986ff783407001459ddea443fe7916e38cff", size = 13968808 }, + { url = "https://files.pythonhosted.org/packages/e4/65/3c0f77e7a9bae100a8f7f5cebde410fca1a3cf64e1ecdd343666e27b11d4/scikit_image-0.22.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:cf3c0c15b60ae3e557a0c7575fbd352f0c3ce0afca562febfe3ab80efbeec0e9", size = 13323763 }, + { url = "https://files.pythonhosted.org/packages/4a/ed/7faf9f7a55d5b3095d33990a85603b66866cce2a608b27f0e1487d70a451/scikit_image-0.22.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5b23908dd4d120e6aecb1ed0277563e8cbc8d6c0565bdc4c4c6475d53608452", size = 13877233 }, + { url = "https://files.pythonhosted.org/packages/ae/9d/09d06f36ce71fa276e1d9453fb4b04250a7038292b13b8c273a5a1a8f7c0/scikit_image-0.22.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be79d7493f320a964f8fcf603121595ba82f84720de999db0fcca002266a549a", size = 14954814 }, + { url = "https://files.pythonhosted.org/packages/dc/35/e6327ae498c6f557cb0a7c3fc284effe7958d2d1c43fb61cd77804fc2c4f/scikit_image-0.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:722b970aa5da725dca55252c373b18bbea7858c1cdb406e19f9b01a4a73b30b2", size = 25004857 }, ] [[package]] @@ -2632,23 +2721,23 @@ dependencies = [ { name = "scipy", version = "1.11.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "threadpoolctl", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/88/00/835e3d280fdd7784e76bdef91dd9487582d7951a7254f59fc8004fc8b213/scikit-learn-1.3.2.tar.gz", hash = "sha256:a2f54c76accc15a34bfb9066e6c7a56c1e7235dda5762b990792330b52ccfb05", size = 7510251, upload-time = "2023-10-23T13:47:55.287Z" } +sdist = { url = "https://files.pythonhosted.org/packages/88/00/835e3d280fdd7784e76bdef91dd9487582d7951a7254f59fc8004fc8b213/scikit-learn-1.3.2.tar.gz", hash = "sha256:a2f54c76accc15a34bfb9066e6c7a56c1e7235dda5762b990792330b52ccfb05", size = 7510251 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/53/570b55a6e10b8694ac1e3024d2df5cd443f1b4ff6d28430845da8b9019b3/scikit_learn-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e326c0eb5cf4d6ba40f93776a20e9a7a69524c4db0757e7ce24ba222471ee8a1", size = 10209999, upload-time = "2023-10-23T13:46:30.373Z" }, - { url = "https://files.pythonhosted.org/packages/70/d0/50ace22129f79830e3cf682d0a2bd4843ef91573299d43112d52790163a8/scikit_learn-1.3.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:535805c2a01ccb40ca4ab7d081d771aea67e535153e35a1fd99418fcedd1648a", size = 9479353, upload-time = "2023-10-23T13:46:34.368Z" }, - { url = "https://files.pythonhosted.org/packages/8f/46/fcc35ed7606c50d3072eae5a107a45cfa5b7f5fa8cc48610edd8cc8e8550/scikit_learn-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1215e5e58e9880b554b01187b8c9390bf4dc4692eedeaf542d3273f4785e342c", size = 10304705, upload-time = "2023-10-23T13:46:37.868Z" }, - { url = "https://files.pythonhosted.org/packages/d0/0b/26ad95cf0b747be967b15fb71a06f5ac67aba0fd2f9cd174de6edefc4674/scikit_learn-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ee107923a623b9f517754ea2f69ea3b62fc898a3641766cb7deb2f2ce450161", size = 10827807, upload-time = "2023-10-23T13:46:41.59Z" }, - { url = "https://files.pythonhosted.org/packages/69/8a/cf17d6443f5f537e099be81535a56ab68a473f9393fbffda38cd19899fc8/scikit_learn-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:35a22e8015048c628ad099da9df5ab3004cdbf81edc75b396fd0cff8699ac58c", size = 9255427, upload-time = "2023-10-23T13:46:44.826Z" }, - { url = "https://files.pythonhosted.org/packages/08/5d/e5acecd6e99a6b656e42e7a7b18284e2f9c9f512e8ed6979e1e75d25f05f/scikit_learn-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6fb6bc98f234fda43163ddbe36df8bcde1d13ee176c6dc9b92bb7d3fc842eb66", size = 10116376, upload-time = "2023-10-23T13:46:48.147Z" }, - { url = "https://files.pythonhosted.org/packages/40/c6/2e91eefb757822e70d351e02cc38d07c137212ae7c41ac12746415b4860a/scikit_learn-1.3.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:18424efee518a1cde7b0b53a422cde2f6625197de6af36da0b57ec502f126157", size = 9383415, upload-time = "2023-10-23T13:46:51.324Z" }, - { url = "https://files.pythonhosted.org/packages/fa/fd/b3637639e73bb72b12803c5245f2a7299e09b2acd85a0f23937c53369a1c/scikit_learn-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3271552a5eb16f208a6f7f617b8cc6d1f137b52c8a1ef8edf547db0259b2c9fb", size = 10279163, upload-time = "2023-10-23T13:46:54.642Z" }, - { url = "https://files.pythonhosted.org/packages/0c/2a/d3ff6091406bc2207e0adb832ebd15e40ac685811c7e2e3b432bfd969b71/scikit_learn-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4144a5004a676d5022b798d9e573b05139e77f271253a4703eed295bde0433", size = 10884422, upload-time = "2023-10-23T13:46:58.087Z" }, - { url = "https://files.pythonhosted.org/packages/4e/ba/ce9bd1cd4953336a0e213b29cb80bb11816f2a93de8c99f88ef0b446ad0c/scikit_learn-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:67f37d708f042a9b8d59551cf94d30431e01374e00dc2645fa186059c6c5d78b", size = 9207060, upload-time = "2023-10-23T13:47:00.948Z" }, - { url = "https://files.pythonhosted.org/packages/26/7e/2c3b82c8c29aa384c8bf859740419278627d2cdd0050db503c8840e72477/scikit_learn-1.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8db94cd8a2e038b37a80a04df8783e09caac77cbe052146432e67800e430c028", size = 9979322, upload-time = "2023-10-23T13:47:03.977Z" }, - { url = "https://files.pythonhosted.org/packages/cf/fc/6c52ffeb587259b6b893b7cac268f1eb1b5426bcce1aa20e53523bfe6944/scikit_learn-1.3.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:61a6efd384258789aa89415a410dcdb39a50e19d3d8410bd29be365bcdd512d5", size = 9270688, upload-time = "2023-10-23T13:47:07.316Z" }, - { url = "https://files.pythonhosted.org/packages/e5/a7/6f4ae76f72ae9de162b97acbf1f53acbe404c555f968d13da21e4112a002/scikit_learn-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb06f8dce3f5ddc5dee1715a9b9f19f20d295bed8e3cd4fa51e1d050347de525", size = 10280398, upload-time = "2023-10-23T13:47:10.796Z" }, - { url = "https://files.pythonhosted.org/packages/5d/b7/ee35904c07a0666784349529412fbb9814a56382b650d30fd9d6be5e5054/scikit_learn-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b2de18d86f630d68fe1f87af690d451388bb186480afc719e5f770590c2ef6c", size = 10796478, upload-time = "2023-10-23T13:47:14.077Z" }, - { url = "https://files.pythonhosted.org/packages/fe/6b/db949ed5ac367987b1f250f070f340b7715d22f0c9c965bdf07de6ca75a3/scikit_learn-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:0402638c9a7c219ee52c94cbebc8fcb5eb9fe9c773717965c1f4185588ad3107", size = 9133979, upload-time = "2023-10-23T13:47:17.389Z" }, + { url = "https://files.pythonhosted.org/packages/0d/53/570b55a6e10b8694ac1e3024d2df5cd443f1b4ff6d28430845da8b9019b3/scikit_learn-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e326c0eb5cf4d6ba40f93776a20e9a7a69524c4db0757e7ce24ba222471ee8a1", size = 10209999 }, + { url = "https://files.pythonhosted.org/packages/70/d0/50ace22129f79830e3cf682d0a2bd4843ef91573299d43112d52790163a8/scikit_learn-1.3.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:535805c2a01ccb40ca4ab7d081d771aea67e535153e35a1fd99418fcedd1648a", size = 9479353 }, + { url = "https://files.pythonhosted.org/packages/8f/46/fcc35ed7606c50d3072eae5a107a45cfa5b7f5fa8cc48610edd8cc8e8550/scikit_learn-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1215e5e58e9880b554b01187b8c9390bf4dc4692eedeaf542d3273f4785e342c", size = 10304705 }, + { url = "https://files.pythonhosted.org/packages/d0/0b/26ad95cf0b747be967b15fb71a06f5ac67aba0fd2f9cd174de6edefc4674/scikit_learn-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ee107923a623b9f517754ea2f69ea3b62fc898a3641766cb7deb2f2ce450161", size = 10827807 }, + { url = "https://files.pythonhosted.org/packages/69/8a/cf17d6443f5f537e099be81535a56ab68a473f9393fbffda38cd19899fc8/scikit_learn-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:35a22e8015048c628ad099da9df5ab3004cdbf81edc75b396fd0cff8699ac58c", size = 9255427 }, + { url = "https://files.pythonhosted.org/packages/08/5d/e5acecd6e99a6b656e42e7a7b18284e2f9c9f512e8ed6979e1e75d25f05f/scikit_learn-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6fb6bc98f234fda43163ddbe36df8bcde1d13ee176c6dc9b92bb7d3fc842eb66", size = 10116376 }, + { url = "https://files.pythonhosted.org/packages/40/c6/2e91eefb757822e70d351e02cc38d07c137212ae7c41ac12746415b4860a/scikit_learn-1.3.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:18424efee518a1cde7b0b53a422cde2f6625197de6af36da0b57ec502f126157", size = 9383415 }, + { url = "https://files.pythonhosted.org/packages/fa/fd/b3637639e73bb72b12803c5245f2a7299e09b2acd85a0f23937c53369a1c/scikit_learn-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3271552a5eb16f208a6f7f617b8cc6d1f137b52c8a1ef8edf547db0259b2c9fb", size = 10279163 }, + { url = "https://files.pythonhosted.org/packages/0c/2a/d3ff6091406bc2207e0adb832ebd15e40ac685811c7e2e3b432bfd969b71/scikit_learn-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4144a5004a676d5022b798d9e573b05139e77f271253a4703eed295bde0433", size = 10884422 }, + { url = "https://files.pythonhosted.org/packages/4e/ba/ce9bd1cd4953336a0e213b29cb80bb11816f2a93de8c99f88ef0b446ad0c/scikit_learn-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:67f37d708f042a9b8d59551cf94d30431e01374e00dc2645fa186059c6c5d78b", size = 9207060 }, + { url = "https://files.pythonhosted.org/packages/26/7e/2c3b82c8c29aa384c8bf859740419278627d2cdd0050db503c8840e72477/scikit_learn-1.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8db94cd8a2e038b37a80a04df8783e09caac77cbe052146432e67800e430c028", size = 9979322 }, + { url = "https://files.pythonhosted.org/packages/cf/fc/6c52ffeb587259b6b893b7cac268f1eb1b5426bcce1aa20e53523bfe6944/scikit_learn-1.3.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:61a6efd384258789aa89415a410dcdb39a50e19d3d8410bd29be365bcdd512d5", size = 9270688 }, + { url = "https://files.pythonhosted.org/packages/e5/a7/6f4ae76f72ae9de162b97acbf1f53acbe404c555f968d13da21e4112a002/scikit_learn-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb06f8dce3f5ddc5dee1715a9b9f19f20d295bed8e3cd4fa51e1d050347de525", size = 10280398 }, + { url = "https://files.pythonhosted.org/packages/5d/b7/ee35904c07a0666784349529412fbb9814a56382b650d30fd9d6be5e5054/scikit_learn-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b2de18d86f630d68fe1f87af690d451388bb186480afc719e5f770590c2ef6c", size = 10796478 }, + { url = "https://files.pythonhosted.org/packages/fe/6b/db949ed5ac367987b1f250f070f340b7715d22f0c9c965bdf07de6ca75a3/scikit_learn-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:0402638c9a7c219ee52c94cbebc8fcb5eb9fe9c773717965c1f4185588ad3107", size = 9133979 }, ] [[package]] @@ -2675,33 +2764,33 @@ dependencies = [ { name = "scipy", version = "1.16.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "threadpoolctl", marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/41/84/5f4af978fff619706b8961accac84780a6d298d82a8873446f72edb4ead0/scikit_learn-1.7.1.tar.gz", hash = "sha256:24b3f1e976a4665aa74ee0fcaac2b8fccc6ae77c8e07ab25da3ba6d3292b9802", size = 7190445, upload-time = "2025-07-18T08:01:54.5Z" } +sdist = { url = "https://files.pythonhosted.org/packages/41/84/5f4af978fff619706b8961accac84780a6d298d82a8873446f72edb4ead0/scikit_learn-1.7.1.tar.gz", hash = "sha256:24b3f1e976a4665aa74ee0fcaac2b8fccc6ae77c8e07ab25da3ba6d3292b9802", size = 7190445 } wheels = [ - { url = "https://files.pythonhosted.org/packages/74/88/0dd5be14ef19f2d80a77780be35a33aa94e8a3b3223d80bee8892a7832b4/scikit_learn-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:406204dd4004f0517f0b23cf4b28c6245cbd51ab1b6b78153bc784def214946d", size = 9338868, upload-time = "2025-07-18T08:01:00.25Z" }, - { url = "https://files.pythonhosted.org/packages/fd/52/3056b6adb1ac58a0bc335fc2ed2fcf599974d908855e8cb0ca55f797593c/scikit_learn-1.7.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:16af2e44164f05d04337fd1fc3ae7c4ea61fd9b0d527e22665346336920fe0e1", size = 8655943, upload-time = "2025-07-18T08:01:02.974Z" }, - { url = "https://files.pythonhosted.org/packages/fb/a4/e488acdece6d413f370a9589a7193dac79cd486b2e418d3276d6ea0b9305/scikit_learn-1.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2f2e78e56a40c7587dea9a28dc4a49500fa2ead366869418c66f0fd75b80885c", size = 9652056, upload-time = "2025-07-18T08:01:04.978Z" }, - { url = "https://files.pythonhosted.org/packages/18/41/bceacec1285b94eb9e4659b24db46c23346d7e22cf258d63419eb5dec6f7/scikit_learn-1.7.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b62b76ad408a821475b43b7bb90a9b1c9a4d8d125d505c2df0539f06d6e631b1", size = 9473691, upload-time = "2025-07-18T08:01:07.006Z" }, - { url = "https://files.pythonhosted.org/packages/12/7b/e1ae4b7e1dd85c4ca2694ff9cc4a9690970fd6150d81b975e6c5c6f8ee7c/scikit_learn-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:9963b065677a4ce295e8ccdee80a1dd62b37249e667095039adcd5bce6e90deb", size = 8900873, upload-time = "2025-07-18T08:01:09.332Z" }, - { url = "https://files.pythonhosted.org/packages/b4/bd/a23177930abd81b96daffa30ef9c54ddbf544d3226b8788ce4c3ef1067b4/scikit_learn-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90c8494ea23e24c0fb371afc474618c1019dc152ce4a10e4607e62196113851b", size = 9334838, upload-time = "2025-07-18T08:01:11.239Z" }, - { url = "https://files.pythonhosted.org/packages/8d/a1/d3a7628630a711e2ac0d1a482910da174b629f44e7dd8cfcd6924a4ef81a/scikit_learn-1.7.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:bb870c0daf3bf3be145ec51df8ac84720d9972170786601039f024bf6d61a518", size = 8651241, upload-time = "2025-07-18T08:01:13.234Z" }, - { url = "https://files.pythonhosted.org/packages/26/92/85ec172418f39474c1cd0221d611345d4f433fc4ee2fc68e01f524ccc4e4/scikit_learn-1.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:40daccd1b5623f39e8943ab39735cadf0bdce80e67cdca2adcb5426e987320a8", size = 9718677, upload-time = "2025-07-18T08:01:15.649Z" }, - { url = "https://files.pythonhosted.org/packages/df/ce/abdb1dcbb1d2b66168ec43b23ee0cee356b4cc4100ddee3943934ebf1480/scikit_learn-1.7.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:30d1f413cfc0aa5a99132a554f1d80517563c34a9d3e7c118fde2d273c6fe0f7", size = 9511189, upload-time = "2025-07-18T08:01:18.013Z" }, - { url = "https://files.pythonhosted.org/packages/b2/3b/47b5eaee01ef2b5a80ba3f7f6ecf79587cb458690857d4777bfd77371c6f/scikit_learn-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:c711d652829a1805a95d7fe96654604a8f16eab5a9e9ad87b3e60173415cb650", size = 8914794, upload-time = "2025-07-18T08:01:20.357Z" }, - { url = "https://files.pythonhosted.org/packages/cb/16/57f176585b35ed865f51b04117947fe20f130f78940c6477b6d66279c9c2/scikit_learn-1.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3cee419b49b5bbae8796ecd690f97aa412ef1674410c23fc3257c6b8b85b8087", size = 9260431, upload-time = "2025-07-18T08:01:22.77Z" }, - { url = "https://files.pythonhosted.org/packages/67/4e/899317092f5efcab0e9bc929e3391341cec8fb0e816c4789686770024580/scikit_learn-1.7.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2fd8b8d35817b0d9ebf0b576f7d5ffbbabdb55536b0655a8aaae629d7ffd2e1f", size = 8637191, upload-time = "2025-07-18T08:01:24.731Z" }, - { url = "https://files.pythonhosted.org/packages/f3/1b/998312db6d361ded1dd56b457ada371a8d8d77ca2195a7d18fd8a1736f21/scikit_learn-1.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:588410fa19a96a69763202f1d6b7b91d5d7a5d73be36e189bc6396bfb355bd87", size = 9486346, upload-time = "2025-07-18T08:01:26.713Z" }, - { url = "https://files.pythonhosted.org/packages/ad/09/a2aa0b4e644e5c4ede7006748f24e72863ba2ae71897fecfd832afea01b4/scikit_learn-1.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e3142f0abe1ad1d1c31a2ae987621e41f6b578144a911ff4ac94781a583adad7", size = 9290988, upload-time = "2025-07-18T08:01:28.938Z" }, - { url = "https://files.pythonhosted.org/packages/15/fa/c61a787e35f05f17fc10523f567677ec4eeee5f95aa4798dbbbcd9625617/scikit_learn-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3ddd9092c1bd469acab337d87930067c87eac6bd544f8d5027430983f1e1ae88", size = 8735568, upload-time = "2025-07-18T08:01:30.936Z" }, - { url = "https://files.pythonhosted.org/packages/52/f8/e0533303f318a0f37b88300d21f79b6ac067188d4824f1047a37214ab718/scikit_learn-1.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b7839687fa46d02e01035ad775982f2470be2668e13ddd151f0f55a5bf123bae", size = 9213143, upload-time = "2025-07-18T08:01:32.942Z" }, - { url = "https://files.pythonhosted.org/packages/71/f3/f1df377d1bdfc3e3e2adc9c119c238b182293e6740df4cbeac6de2cc3e23/scikit_learn-1.7.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a10f276639195a96c86aa572ee0698ad64ee939a7b042060b98bd1930c261d10", size = 8591977, upload-time = "2025-07-18T08:01:34.967Z" }, - { url = "https://files.pythonhosted.org/packages/99/72/c86a4cd867816350fe8dee13f30222340b9cd6b96173955819a5561810c5/scikit_learn-1.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:13679981fdaebc10cc4c13c43344416a86fcbc61449cb3e6517e1df9d12c8309", size = 9436142, upload-time = "2025-07-18T08:01:37.397Z" }, - { url = "https://files.pythonhosted.org/packages/e8/66/277967b29bd297538dc7a6ecfb1a7dce751beabd0d7f7a2233be7a4f7832/scikit_learn-1.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f1262883c6a63f067a980a8cdd2d2e7f2513dddcef6a9eaada6416a7a7cbe43", size = 9282996, upload-time = "2025-07-18T08:01:39.721Z" }, - { url = "https://files.pythonhosted.org/packages/e2/47/9291cfa1db1dae9880420d1e07dbc7e8dd4a7cdbc42eaba22512e6bde958/scikit_learn-1.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:ca6d31fb10e04d50bfd2b50d66744729dbb512d4efd0223b864e2fdbfc4cee11", size = 8707418, upload-time = "2025-07-18T08:01:42.124Z" }, - { url = "https://files.pythonhosted.org/packages/61/95/45726819beccdaa34d3362ea9b2ff9f2b5d3b8bf721bd632675870308ceb/scikit_learn-1.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:781674d096303cfe3d351ae6963ff7c958db61cde3421cd490e3a5a58f2a94ae", size = 9561466, upload-time = "2025-07-18T08:01:44.195Z" }, - { url = "https://files.pythonhosted.org/packages/ee/1c/6f4b3344805de783d20a51eb24d4c9ad4b11a7f75c1801e6ec6d777361fd/scikit_learn-1.7.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:10679f7f125fe7ecd5fad37dd1aa2daae7e3ad8df7f3eefa08901b8254b3e12c", size = 9040467, upload-time = "2025-07-18T08:01:46.671Z" }, - { url = "https://files.pythonhosted.org/packages/6f/80/abe18fe471af9f1d181904203d62697998b27d9b62124cd281d740ded2f9/scikit_learn-1.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1f812729e38c8cb37f760dce71a9b83ccfb04f59b3dca7c6079dcdc60544fa9e", size = 9532052, upload-time = "2025-07-18T08:01:48.676Z" }, - { url = "https://files.pythonhosted.org/packages/14/82/b21aa1e0c4cee7e74864d3a5a721ab8fcae5ca55033cb6263dca297ed35b/scikit_learn-1.7.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88e1a20131cf741b84b89567e1717f27a2ced228e0f29103426102bc2e3b8ef7", size = 9361575, upload-time = "2025-07-18T08:01:50.639Z" }, - { url = "https://files.pythonhosted.org/packages/f2/20/f4777fcd5627dc6695fa6b92179d0edb7a3ac1b91bcd9a1c7f64fa7ade23/scikit_learn-1.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b1bd1d919210b6a10b7554b717c9000b5485aa95a1d0f177ae0d7ee8ec750da5", size = 9277310, upload-time = "2025-07-18T08:01:52.547Z" }, + { url = "https://files.pythonhosted.org/packages/74/88/0dd5be14ef19f2d80a77780be35a33aa94e8a3b3223d80bee8892a7832b4/scikit_learn-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:406204dd4004f0517f0b23cf4b28c6245cbd51ab1b6b78153bc784def214946d", size = 9338868 }, + { url = "https://files.pythonhosted.org/packages/fd/52/3056b6adb1ac58a0bc335fc2ed2fcf599974d908855e8cb0ca55f797593c/scikit_learn-1.7.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:16af2e44164f05d04337fd1fc3ae7c4ea61fd9b0d527e22665346336920fe0e1", size = 8655943 }, + { url = "https://files.pythonhosted.org/packages/fb/a4/e488acdece6d413f370a9589a7193dac79cd486b2e418d3276d6ea0b9305/scikit_learn-1.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2f2e78e56a40c7587dea9a28dc4a49500fa2ead366869418c66f0fd75b80885c", size = 9652056 }, + { url = "https://files.pythonhosted.org/packages/18/41/bceacec1285b94eb9e4659b24db46c23346d7e22cf258d63419eb5dec6f7/scikit_learn-1.7.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b62b76ad408a821475b43b7bb90a9b1c9a4d8d125d505c2df0539f06d6e631b1", size = 9473691 }, + { url = "https://files.pythonhosted.org/packages/12/7b/e1ae4b7e1dd85c4ca2694ff9cc4a9690970fd6150d81b975e6c5c6f8ee7c/scikit_learn-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:9963b065677a4ce295e8ccdee80a1dd62b37249e667095039adcd5bce6e90deb", size = 8900873 }, + { url = "https://files.pythonhosted.org/packages/b4/bd/a23177930abd81b96daffa30ef9c54ddbf544d3226b8788ce4c3ef1067b4/scikit_learn-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90c8494ea23e24c0fb371afc474618c1019dc152ce4a10e4607e62196113851b", size = 9334838 }, + { url = "https://files.pythonhosted.org/packages/8d/a1/d3a7628630a711e2ac0d1a482910da174b629f44e7dd8cfcd6924a4ef81a/scikit_learn-1.7.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:bb870c0daf3bf3be145ec51df8ac84720d9972170786601039f024bf6d61a518", size = 8651241 }, + { url = "https://files.pythonhosted.org/packages/26/92/85ec172418f39474c1cd0221d611345d4f433fc4ee2fc68e01f524ccc4e4/scikit_learn-1.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:40daccd1b5623f39e8943ab39735cadf0bdce80e67cdca2adcb5426e987320a8", size = 9718677 }, + { url = "https://files.pythonhosted.org/packages/df/ce/abdb1dcbb1d2b66168ec43b23ee0cee356b4cc4100ddee3943934ebf1480/scikit_learn-1.7.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:30d1f413cfc0aa5a99132a554f1d80517563c34a9d3e7c118fde2d273c6fe0f7", size = 9511189 }, + { url = "https://files.pythonhosted.org/packages/b2/3b/47b5eaee01ef2b5a80ba3f7f6ecf79587cb458690857d4777bfd77371c6f/scikit_learn-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:c711d652829a1805a95d7fe96654604a8f16eab5a9e9ad87b3e60173415cb650", size = 8914794 }, + { url = "https://files.pythonhosted.org/packages/cb/16/57f176585b35ed865f51b04117947fe20f130f78940c6477b6d66279c9c2/scikit_learn-1.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3cee419b49b5bbae8796ecd690f97aa412ef1674410c23fc3257c6b8b85b8087", size = 9260431 }, + { url = "https://files.pythonhosted.org/packages/67/4e/899317092f5efcab0e9bc929e3391341cec8fb0e816c4789686770024580/scikit_learn-1.7.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2fd8b8d35817b0d9ebf0b576f7d5ffbbabdb55536b0655a8aaae629d7ffd2e1f", size = 8637191 }, + { url = "https://files.pythonhosted.org/packages/f3/1b/998312db6d361ded1dd56b457ada371a8d8d77ca2195a7d18fd8a1736f21/scikit_learn-1.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:588410fa19a96a69763202f1d6b7b91d5d7a5d73be36e189bc6396bfb355bd87", size = 9486346 }, + { url = "https://files.pythonhosted.org/packages/ad/09/a2aa0b4e644e5c4ede7006748f24e72863ba2ae71897fecfd832afea01b4/scikit_learn-1.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e3142f0abe1ad1d1c31a2ae987621e41f6b578144a911ff4ac94781a583adad7", size = 9290988 }, + { url = "https://files.pythonhosted.org/packages/15/fa/c61a787e35f05f17fc10523f567677ec4eeee5f95aa4798dbbbcd9625617/scikit_learn-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3ddd9092c1bd469acab337d87930067c87eac6bd544f8d5027430983f1e1ae88", size = 8735568 }, + { url = "https://files.pythonhosted.org/packages/52/f8/e0533303f318a0f37b88300d21f79b6ac067188d4824f1047a37214ab718/scikit_learn-1.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b7839687fa46d02e01035ad775982f2470be2668e13ddd151f0f55a5bf123bae", size = 9213143 }, + { url = "https://files.pythonhosted.org/packages/71/f3/f1df377d1bdfc3e3e2adc9c119c238b182293e6740df4cbeac6de2cc3e23/scikit_learn-1.7.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a10f276639195a96c86aa572ee0698ad64ee939a7b042060b98bd1930c261d10", size = 8591977 }, + { url = "https://files.pythonhosted.org/packages/99/72/c86a4cd867816350fe8dee13f30222340b9cd6b96173955819a5561810c5/scikit_learn-1.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:13679981fdaebc10cc4c13c43344416a86fcbc61449cb3e6517e1df9d12c8309", size = 9436142 }, + { url = "https://files.pythonhosted.org/packages/e8/66/277967b29bd297538dc7a6ecfb1a7dce751beabd0d7f7a2233be7a4f7832/scikit_learn-1.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f1262883c6a63f067a980a8cdd2d2e7f2513dddcef6a9eaada6416a7a7cbe43", size = 9282996 }, + { url = "https://files.pythonhosted.org/packages/e2/47/9291cfa1db1dae9880420d1e07dbc7e8dd4a7cdbc42eaba22512e6bde958/scikit_learn-1.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:ca6d31fb10e04d50bfd2b50d66744729dbb512d4efd0223b864e2fdbfc4cee11", size = 8707418 }, + { url = "https://files.pythonhosted.org/packages/61/95/45726819beccdaa34d3362ea9b2ff9f2b5d3b8bf721bd632675870308ceb/scikit_learn-1.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:781674d096303cfe3d351ae6963ff7c958db61cde3421cd490e3a5a58f2a94ae", size = 9561466 }, + { url = "https://files.pythonhosted.org/packages/ee/1c/6f4b3344805de783d20a51eb24d4c9ad4b11a7f75c1801e6ec6d777361fd/scikit_learn-1.7.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:10679f7f125fe7ecd5fad37dd1aa2daae7e3ad8df7f3eefa08901b8254b3e12c", size = 9040467 }, + { url = "https://files.pythonhosted.org/packages/6f/80/abe18fe471af9f1d181904203d62697998b27d9b62124cd281d740ded2f9/scikit_learn-1.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1f812729e38c8cb37f760dce71a9b83ccfb04f59b3dca7c6079dcdc60544fa9e", size = 9532052 }, + { url = "https://files.pythonhosted.org/packages/14/82/b21aa1e0c4cee7e74864d3a5a721ab8fcae5ca55033cb6263dca297ed35b/scikit_learn-1.7.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88e1a20131cf741b84b89567e1717f27a2ced228e0f29103426102bc2e3b8ef7", size = 9361575 }, + { url = "https://files.pythonhosted.org/packages/f2/20/f4777fcd5627dc6695fa6b92179d0edb7a3ac1b91bcd9a1c7f64fa7ade23/scikit_learn-1.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b1bd1d919210b6a10b7554b717c9000b5485aa95a1d0f177ae0d7ee8ec750da5", size = 9277310 }, ] [[package]] @@ -2716,26 +2805,26 @@ resolution-markers = [ dependencies = [ { name = "numpy", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6e/1f/91144ba78dccea567a6466262922786ffc97be1e9b06ed9574ef0edc11e1/scipy-1.11.4.tar.gz", hash = "sha256:90a2b78e7f5733b9de748f589f09225013685f9b218275257f8a8168ededaeaa", size = 56336202, upload-time = "2023-11-18T21:06:08.277Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/1f/91144ba78dccea567a6466262922786ffc97be1e9b06ed9574ef0edc11e1/scipy-1.11.4.tar.gz", hash = "sha256:90a2b78e7f5733b9de748f589f09225013685f9b218275257f8a8168ededaeaa", size = 56336202 } wheels = [ - { url = "https://files.pythonhosted.org/packages/34/c6/a32add319475d21f89733c034b99c81b3a7c6c7c19f96f80c7ca3ff1bbd4/scipy-1.11.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc9a714581f561af0848e6b69947fda0614915f072dfd14142ed1bfe1b806710", size = 37293259, upload-time = "2023-11-18T21:01:18.805Z" }, - { url = "https://files.pythonhosted.org/packages/de/0d/4fa68303568c70fd56fbf40668b6c6807cfee4cad975f07d80bdd26d013e/scipy-1.11.4-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:cf00bd2b1b0211888d4dc75656c0412213a8b25e80d73898083f402b50f47e41", size = 29760656, upload-time = "2023-11-18T21:01:41.815Z" }, - { url = "https://files.pythonhosted.org/packages/13/e5/8012be7857db6cbbbdbeea8a154dbacdfae845e95e1e19c028e82236d4a0/scipy-1.11.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9999c008ccf00e8fbcce1236f85ade5c569d13144f77a1946bef8863e8f6eb4", size = 32922489, upload-time = "2023-11-18T21:01:50.637Z" }, - { url = "https://files.pythonhosted.org/packages/e0/9e/80e2205d138960a49caea391f3710600895dd8292b6868dc9aff7aa593f9/scipy-1.11.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:933baf588daa8dc9a92c20a0be32f56d43faf3d1a60ab11b3f08c356430f6e56", size = 36442040, upload-time = "2023-11-18T21:02:00.119Z" }, - { url = "https://files.pythonhosted.org/packages/69/60/30a9c3fbe5066a3a93eefe3e2d44553df13587e6f792e1bff20dfed3d17e/scipy-1.11.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8fce70f39076a5aa62e92e69a7f62349f9574d8405c0a5de6ed3ef72de07f446", size = 36643257, upload-time = "2023-11-18T21:02:06.798Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ec/b46756f80e3f4c5f0989f6e4492c2851f156d9c239d554754a3c8cffd4e2/scipy-1.11.4-cp310-cp310-win_amd64.whl", hash = "sha256:6550466fbeec7453d7465e74d4f4b19f905642c89a7525571ee91dd7adabb5a3", size = 44149285, upload-time = "2023-11-18T21:02:15.592Z" }, - { url = "https://files.pythonhosted.org/packages/b8/f2/1aefbd5e54ebd8c6163ccf7f73e5d17bc8cb38738d312befc524fce84bb4/scipy-1.11.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f313b39a7e94f296025e3cffc2c567618174c0b1dde173960cf23808f9fae4be", size = 37159197, upload-time = "2023-11-18T21:02:21.959Z" }, - { url = "https://files.pythonhosted.org/packages/4b/48/20e77ddb1f473d4717a7d4d3fc8d15557f406f7708496054c59f635b7734/scipy-1.11.4-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:1b7c3dca977f30a739e0409fb001056484661cb2541a01aba0bb0029f7b68db8", size = 29675057, upload-time = "2023-11-18T21:02:28.169Z" }, - { url = "https://files.pythonhosted.org/packages/75/2e/a781862190d0e7e76afa74752ef363488a9a9d6ea86e46d5e5506cee8df6/scipy-1.11.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00150c5eae7b610c32589dda259eacc7c4f1665aedf25d921907f4d08a951b1c", size = 32882747, upload-time = "2023-11-18T21:02:33.683Z" }, - { url = "https://files.pythonhosted.org/packages/6b/d4/d62ce38ba00dc67d7ec4ec5cc19d36958d8ed70e63778715ad626bcbc796/scipy-1.11.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:530f9ad26440e85766509dbf78edcfe13ffd0ab7fec2560ee5c36ff74d6269ff", size = 36402732, upload-time = "2023-11-18T21:02:39.762Z" }, - { url = "https://files.pythonhosted.org/packages/88/86/827b56aea1ed04adbb044a675672a73c84d81076a350092bbfcfc1ae723b/scipy-1.11.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5e347b14fe01003d3b78e196e84bd3f48ffe4c8a7b8a1afbcb8f5505cb710993", size = 36622138, upload-time = "2023-11-18T21:02:45.968Z" }, - { url = "https://files.pythonhosted.org/packages/43/d0/f3cd75b62e1b90f48dbf091261b2fc7ceec14a700e308c50f6a69c83d337/scipy-1.11.4-cp311-cp311-win_amd64.whl", hash = "sha256:acf8ed278cc03f5aff035e69cb511741e0418681d25fbbb86ca65429c4f4d9cd", size = 44095631, upload-time = "2023-11-18T21:02:52.859Z" }, - { url = "https://files.pythonhosted.org/packages/df/64/8a690570485b636da614acff35fd725fcbc487f8b1fa9bdb12871b77412f/scipy-1.11.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:028eccd22e654b3ea01ee63705681ee79933652b2d8f873e7949898dda6d11b6", size = 37053653, upload-time = "2023-11-18T21:03:00.107Z" }, - { url = "https://files.pythonhosted.org/packages/5e/43/abf331745a7e5f4af51f13d40e2a72f516048db41ecbcf3ac6f86ada54a3/scipy-1.11.4-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2c6ff6ef9cc27f9b3db93a6f8b38f97387e6e0591600369a297a50a8e96e835d", size = 29641601, upload-time = "2023-11-18T21:03:06.708Z" }, - { url = "https://files.pythonhosted.org/packages/47/9b/62d0ec086dd2871009da8769c504bec6e39b80f4c182c6ead0fcebd8b323/scipy-1.11.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b030c6674b9230d37c5c60ab456e2cf12f6784596d15ce8da9365e70896effc4", size = 32272137, upload-time = "2023-11-18T21:03:14.877Z" }, - { url = "https://files.pythonhosted.org/packages/08/77/f90f7306d755ac68bd159c50bb86fffe38400e533e8c609dd8484bd0f172/scipy-1.11.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad669df80528aeca5f557712102538f4f37e503f0c5b9541655016dd0932ca79", size = 35777534, upload-time = "2023-11-18T21:03:21.451Z" }, - { url = "https://files.pythonhosted.org/packages/00/de/b9f6938090c37b5092969ba1c67118e9114e8e6ef9d197251671444e839c/scipy-1.11.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ce7fff2e23ab2cc81ff452a9444c215c28e6305f396b2ba88343a567feec9660", size = 35963721, upload-time = "2023-11-18T21:03:27.85Z" }, - { url = "https://files.pythonhosted.org/packages/c6/a1/357e4cd43af2748e1e0407ae0e9a5ea8aaaa6b702833c81be11670dcbad8/scipy-1.11.4-cp312-cp312-win_amd64.whl", hash = "sha256:36750b7733d960d7994888f0d148d31ea3017ac15eef664194b4ef68d36a4a97", size = 43730653, upload-time = "2023-11-18T21:03:34.758Z" }, + { url = "https://files.pythonhosted.org/packages/34/c6/a32add319475d21f89733c034b99c81b3a7c6c7c19f96f80c7ca3ff1bbd4/scipy-1.11.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc9a714581f561af0848e6b69947fda0614915f072dfd14142ed1bfe1b806710", size = 37293259 }, + { url = "https://files.pythonhosted.org/packages/de/0d/4fa68303568c70fd56fbf40668b6c6807cfee4cad975f07d80bdd26d013e/scipy-1.11.4-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:cf00bd2b1b0211888d4dc75656c0412213a8b25e80d73898083f402b50f47e41", size = 29760656 }, + { url = "https://files.pythonhosted.org/packages/13/e5/8012be7857db6cbbbdbeea8a154dbacdfae845e95e1e19c028e82236d4a0/scipy-1.11.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9999c008ccf00e8fbcce1236f85ade5c569d13144f77a1946bef8863e8f6eb4", size = 32922489 }, + { url = "https://files.pythonhosted.org/packages/e0/9e/80e2205d138960a49caea391f3710600895dd8292b6868dc9aff7aa593f9/scipy-1.11.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:933baf588daa8dc9a92c20a0be32f56d43faf3d1a60ab11b3f08c356430f6e56", size = 36442040 }, + { url = "https://files.pythonhosted.org/packages/69/60/30a9c3fbe5066a3a93eefe3e2d44553df13587e6f792e1bff20dfed3d17e/scipy-1.11.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8fce70f39076a5aa62e92e69a7f62349f9574d8405c0a5de6ed3ef72de07f446", size = 36643257 }, + { url = "https://files.pythonhosted.org/packages/f8/ec/b46756f80e3f4c5f0989f6e4492c2851f156d9c239d554754a3c8cffd4e2/scipy-1.11.4-cp310-cp310-win_amd64.whl", hash = "sha256:6550466fbeec7453d7465e74d4f4b19f905642c89a7525571ee91dd7adabb5a3", size = 44149285 }, + { url = "https://files.pythonhosted.org/packages/b8/f2/1aefbd5e54ebd8c6163ccf7f73e5d17bc8cb38738d312befc524fce84bb4/scipy-1.11.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f313b39a7e94f296025e3cffc2c567618174c0b1dde173960cf23808f9fae4be", size = 37159197 }, + { url = "https://files.pythonhosted.org/packages/4b/48/20e77ddb1f473d4717a7d4d3fc8d15557f406f7708496054c59f635b7734/scipy-1.11.4-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:1b7c3dca977f30a739e0409fb001056484661cb2541a01aba0bb0029f7b68db8", size = 29675057 }, + { url = "https://files.pythonhosted.org/packages/75/2e/a781862190d0e7e76afa74752ef363488a9a9d6ea86e46d5e5506cee8df6/scipy-1.11.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00150c5eae7b610c32589dda259eacc7c4f1665aedf25d921907f4d08a951b1c", size = 32882747 }, + { url = "https://files.pythonhosted.org/packages/6b/d4/d62ce38ba00dc67d7ec4ec5cc19d36958d8ed70e63778715ad626bcbc796/scipy-1.11.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:530f9ad26440e85766509dbf78edcfe13ffd0ab7fec2560ee5c36ff74d6269ff", size = 36402732 }, + { url = "https://files.pythonhosted.org/packages/88/86/827b56aea1ed04adbb044a675672a73c84d81076a350092bbfcfc1ae723b/scipy-1.11.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5e347b14fe01003d3b78e196e84bd3f48ffe4c8a7b8a1afbcb8f5505cb710993", size = 36622138 }, + { url = "https://files.pythonhosted.org/packages/43/d0/f3cd75b62e1b90f48dbf091261b2fc7ceec14a700e308c50f6a69c83d337/scipy-1.11.4-cp311-cp311-win_amd64.whl", hash = "sha256:acf8ed278cc03f5aff035e69cb511741e0418681d25fbbb86ca65429c4f4d9cd", size = 44095631 }, + { url = "https://files.pythonhosted.org/packages/df/64/8a690570485b636da614acff35fd725fcbc487f8b1fa9bdb12871b77412f/scipy-1.11.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:028eccd22e654b3ea01ee63705681ee79933652b2d8f873e7949898dda6d11b6", size = 37053653 }, + { url = "https://files.pythonhosted.org/packages/5e/43/abf331745a7e5f4af51f13d40e2a72f516048db41ecbcf3ac6f86ada54a3/scipy-1.11.4-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2c6ff6ef9cc27f9b3db93a6f8b38f97387e6e0591600369a297a50a8e96e835d", size = 29641601 }, + { url = "https://files.pythonhosted.org/packages/47/9b/62d0ec086dd2871009da8769c504bec6e39b80f4c182c6ead0fcebd8b323/scipy-1.11.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b030c6674b9230d37c5c60ab456e2cf12f6784596d15ce8da9365e70896effc4", size = 32272137 }, + { url = "https://files.pythonhosted.org/packages/08/77/f90f7306d755ac68bd159c50bb86fffe38400e533e8c609dd8484bd0f172/scipy-1.11.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad669df80528aeca5f557712102538f4f37e503f0c5b9541655016dd0932ca79", size = 35777534 }, + { url = "https://files.pythonhosted.org/packages/00/de/b9f6938090c37b5092969ba1c67118e9114e8e6ef9d197251671444e839c/scipy-1.11.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ce7fff2e23ab2cc81ff452a9444c215c28e6305f396b2ba88343a567feec9660", size = 35963721 }, + { url = "https://files.pythonhosted.org/packages/c6/a1/357e4cd43af2748e1e0407ae0e9a5ea8aaaa6b702833c81be11670dcbad8/scipy-1.11.4-cp312-cp312-win_amd64.whl", hash = "sha256:36750b7733d960d7994888f0d148d31ea3017ac15eef664194b4ef68d36a4a97", size = 43730653 }, ] [[package]] @@ -2759,71 +2848,122 @@ resolution-markers = [ dependencies = [ { name = "numpy", marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f5/4a/b927028464795439faec8eaf0b03b011005c487bb2d07409f28bf30879c4/scipy-1.16.1.tar.gz", hash = "sha256:44c76f9e8b6e8e488a586190ab38016e4ed2f8a038af7cd3defa903c0a2238b3", size = 30580861, upload-time = "2025-07-27T16:33:30.834Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/4a/b927028464795439faec8eaf0b03b011005c487bb2d07409f28bf30879c4/scipy-1.16.1.tar.gz", hash = "sha256:44c76f9e8b6e8e488a586190ab38016e4ed2f8a038af7cd3defa903c0a2238b3", size = 30580861 } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/91/812adc6f74409b461e3a5fa97f4f74c769016919203138a3bf6fc24ba4c5/scipy-1.16.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:c033fa32bab91dc98ca59d0cf23bb876454e2bb02cbe592d5023138778f70030", size = 36552519, upload-time = "2025-07-27T16:26:29.658Z" }, - { url = "https://files.pythonhosted.org/packages/47/18/8e355edcf3b71418d9e9f9acd2708cc3a6c27e8f98fde0ac34b8a0b45407/scipy-1.16.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:6e5c2f74e5df33479b5cd4e97a9104c511518fbd979aa9b8f6aec18b2e9ecae7", size = 28638010, upload-time = "2025-07-27T16:26:38.196Z" }, - { url = "https://files.pythonhosted.org/packages/d9/eb/e931853058607bdfbc11b86df19ae7a08686121c203483f62f1ecae5989c/scipy-1.16.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0a55ffe0ba0f59666e90951971a884d1ff6f4ec3275a48f472cfb64175570f77", size = 20909790, upload-time = "2025-07-27T16:26:43.93Z" }, - { url = "https://files.pythonhosted.org/packages/45/0c/be83a271d6e96750cd0be2e000f35ff18880a46f05ce8b5d3465dc0f7a2a/scipy-1.16.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f8a5d6cd147acecc2603fbd382fed6c46f474cccfcf69ea32582e033fb54dcfe", size = 23513352, upload-time = "2025-07-27T16:26:50.017Z" }, - { url = "https://files.pythonhosted.org/packages/7c/bf/fe6eb47e74f762f933cca962db7f2c7183acfdc4483bd1c3813cfe83e538/scipy-1.16.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb18899127278058bcc09e7b9966d41a5a43740b5bb8dcba401bd983f82e885b", size = 33534643, upload-time = "2025-07-27T16:26:57.503Z" }, - { url = "https://files.pythonhosted.org/packages/bb/ba/63f402e74875486b87ec6506a4f93f6d8a0d94d10467280f3d9d7837ce3a/scipy-1.16.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adccd93a2fa937a27aae826d33e3bfa5edf9aa672376a4852d23a7cd67a2e5b7", size = 35376776, upload-time = "2025-07-27T16:27:06.639Z" }, - { url = "https://files.pythonhosted.org/packages/c3/b4/04eb9d39ec26a1b939689102da23d505ea16cdae3dbb18ffc53d1f831044/scipy-1.16.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:18aca1646a29ee9a0625a1be5637fa798d4d81fdf426481f06d69af828f16958", size = 35698906, upload-time = "2025-07-27T16:27:14.943Z" }, - { url = "https://files.pythonhosted.org/packages/04/d6/bb5468da53321baeb001f6e4e0d9049eadd175a4a497709939128556e3ec/scipy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d85495cef541729a70cdddbbf3e6b903421bc1af3e8e3a9a72a06751f33b7c39", size = 38129275, upload-time = "2025-07-27T16:27:23.873Z" }, - { url = "https://files.pythonhosted.org/packages/c4/94/994369978509f227cba7dfb9e623254d0d5559506fe994aef4bea3ed469c/scipy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:226652fca853008119c03a8ce71ffe1b3f6d2844cc1686e8f9806edafae68596", size = 38644572, upload-time = "2025-07-27T16:27:32.637Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d9/ec4864f5896232133f51382b54a08de91a9d1af7a76dfa372894026dfee2/scipy-1.16.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81b433bbeaf35728dad619afc002db9b189e45eebe2cd676effe1fb93fef2b9c", size = 36575194, upload-time = "2025-07-27T16:27:41.321Z" }, - { url = "https://files.pythonhosted.org/packages/5c/6d/40e81ecfb688e9d25d34a847dca361982a6addf8e31f0957b1a54fbfa994/scipy-1.16.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:886cc81fdb4c6903a3bb0464047c25a6d1016fef77bb97949817d0c0d79f9e04", size = 28594590, upload-time = "2025-07-27T16:27:49.204Z" }, - { url = "https://files.pythonhosted.org/packages/0e/37/9f65178edfcc629377ce9a64fc09baebea18c80a9e57ae09a52edf84880b/scipy-1.16.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:15240c3aac087a522b4eaedb09f0ad061753c5eebf1ea430859e5bf8640d5919", size = 20866458, upload-time = "2025-07-27T16:27:54.98Z" }, - { url = "https://files.pythonhosted.org/packages/2c/7b/749a66766871ea4cb1d1ea10f27004db63023074c22abed51f22f09770e0/scipy-1.16.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:65f81a25805f3659b48126b5053d9e823d3215e4a63730b5e1671852a1705921", size = 23539318, upload-time = "2025-07-27T16:28:01.604Z" }, - { url = "https://files.pythonhosted.org/packages/c4/db/8d4afec60eb833a666434d4541a3151eedbf2494ea6d4d468cbe877f00cd/scipy-1.16.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6c62eea7f607f122069b9bad3f99489ddca1a5173bef8a0c75555d7488b6f725", size = 33292899, upload-time = "2025-07-27T16:28:09.147Z" }, - { url = "https://files.pythonhosted.org/packages/51/1e/79023ca3bbb13a015d7d2757ecca3b81293c663694c35d6541b4dca53e98/scipy-1.16.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f965bbf3235b01c776115ab18f092a95aa74c271a52577bcb0563e85738fd618", size = 35162637, upload-time = "2025-07-27T16:28:17.535Z" }, - { url = "https://files.pythonhosted.org/packages/b6/49/0648665f9c29fdaca4c679182eb972935b3b4f5ace41d323c32352f29816/scipy-1.16.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f006e323874ffd0b0b816d8c6a8e7f9a73d55ab3b8c3f72b752b226d0e3ac83d", size = 35490507, upload-time = "2025-07-27T16:28:25.705Z" }, - { url = "https://files.pythonhosted.org/packages/62/8f/66cbb9d6bbb18d8c658f774904f42a92078707a7c71e5347e8bf2f52bb89/scipy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8fd15fc5085ab4cca74cb91fe0a4263b1f32e4420761ddae531ad60934c2119", size = 37923998, upload-time = "2025-07-27T16:28:34.339Z" }, - { url = "https://files.pythonhosted.org/packages/14/c3/61f273ae550fbf1667675701112e380881905e28448c080b23b5a181df7c/scipy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7b8013c6c066609577d910d1a2a077021727af07b6fab0ee22c2f901f22352a", size = 38508060, upload-time = "2025-07-27T16:28:43.242Z" }, - { url = "https://files.pythonhosted.org/packages/93/0b/b5c99382b839854a71ca9482c684e3472badc62620287cbbdab499b75ce6/scipy-1.16.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5451606823a5e73dfa621a89948096c6528e2896e40b39248295d3a0138d594f", size = 36533717, upload-time = "2025-07-27T16:28:51.706Z" }, - { url = "https://files.pythonhosted.org/packages/eb/e5/69ab2771062c91e23e07c12e7d5033a6b9b80b0903ee709c3c36b3eb520c/scipy-1.16.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:89728678c5ca5abd610aee148c199ac1afb16e19844401ca97d43dc548a354eb", size = 28570009, upload-time = "2025-07-27T16:28:57.017Z" }, - { url = "https://files.pythonhosted.org/packages/f4/69/bd75dbfdd3cf524f4d753484d723594aed62cfaac510123e91a6686d520b/scipy-1.16.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e756d688cb03fd07de0fffad475649b03cb89bee696c98ce508b17c11a03f95c", size = 20841942, upload-time = "2025-07-27T16:29:01.152Z" }, - { url = "https://files.pythonhosted.org/packages/ea/74/add181c87663f178ba7d6144b370243a87af8476664d5435e57d599e6874/scipy-1.16.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5aa2687b9935da3ed89c5dbed5234576589dd28d0bf7cd237501ccfbdf1ad608", size = 23498507, upload-time = "2025-07-27T16:29:05.202Z" }, - { url = "https://files.pythonhosted.org/packages/1d/74/ece2e582a0d9550cee33e2e416cc96737dce423a994d12bbe59716f47ff1/scipy-1.16.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0851f6a1e537fe9399f35986897e395a1aa61c574b178c0d456be5b1a0f5ca1f", size = 33286040, upload-time = "2025-07-27T16:29:10.201Z" }, - { url = "https://files.pythonhosted.org/packages/e4/82/08e4076df538fb56caa1d489588d880ec7c52d8273a606bb54d660528f7c/scipy-1.16.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fedc2cbd1baed37474b1924c331b97bdff611d762c196fac1a9b71e67b813b1b", size = 35176096, upload-time = "2025-07-27T16:29:17.091Z" }, - { url = "https://files.pythonhosted.org/packages/fa/79/cd710aab8c921375711a8321c6be696e705a120e3011a643efbbcdeeabcc/scipy-1.16.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2ef500e72f9623a6735769e4b93e9dcb158d40752cdbb077f305487e3e2d1f45", size = 35490328, upload-time = "2025-07-27T16:29:22.928Z" }, - { url = "https://files.pythonhosted.org/packages/71/73/e9cc3d35ee4526d784520d4494a3e1ca969b071fb5ae5910c036a375ceec/scipy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:978d8311674b05a8f7ff2ea6c6bce5d8b45a0cb09d4c5793e0318f448613ea65", size = 37939921, upload-time = "2025-07-27T16:29:29.108Z" }, - { url = "https://files.pythonhosted.org/packages/21/12/c0efd2941f01940119b5305c375ae5c0fcb7ec193f806bd8f158b73a1782/scipy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:81929ed0fa7a5713fcdd8b2e6f73697d3b4c4816d090dd34ff937c20fa90e8ab", size = 38479462, upload-time = "2025-07-27T16:30:24.078Z" }, - { url = "https://files.pythonhosted.org/packages/7a/19/c3d08b675260046a991040e1ea5d65f91f40c7df1045fffff412dcfc6765/scipy-1.16.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:bcc12db731858abda693cecdb3bdc9e6d4bd200213f49d224fe22df82687bdd6", size = 36938832, upload-time = "2025-07-27T16:29:35.057Z" }, - { url = "https://files.pythonhosted.org/packages/81/f2/ce53db652c033a414a5b34598dba6b95f3d38153a2417c5a3883da429029/scipy-1.16.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:744d977daa4becb9fc59135e75c069f8d301a87d64f88f1e602a9ecf51e77b27", size = 29093084, upload-time = "2025-07-27T16:29:40.201Z" }, - { url = "https://files.pythonhosted.org/packages/a9/ae/7a10ff04a7dc15f9057d05b33737ade244e4bd195caa3f7cc04d77b9e214/scipy-1.16.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:dc54f76ac18073bcecffb98d93f03ed6b81a92ef91b5d3b135dcc81d55a724c7", size = 21365098, upload-time = "2025-07-27T16:29:44.295Z" }, - { url = "https://files.pythonhosted.org/packages/36/ac/029ff710959932ad3c2a98721b20b405f05f752f07344622fd61a47c5197/scipy-1.16.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:367d567ee9fc1e9e2047d31f39d9d6a7a04e0710c86e701e053f237d14a9b4f6", size = 23896858, upload-time = "2025-07-27T16:29:48.784Z" }, - { url = "https://files.pythonhosted.org/packages/71/13/d1ef77b6bd7898720e1f0b6b3743cb945f6c3cafa7718eaac8841035ab60/scipy-1.16.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4cf5785e44e19dcd32a0e4807555e1e9a9b8d475c6afff3d21c3c543a6aa84f4", size = 33438311, upload-time = "2025-07-27T16:29:54.164Z" }, - { url = "https://files.pythonhosted.org/packages/2d/e0/e64a6821ffbb00b4c5b05169f1c1fddb4800e9307efe3db3788995a82a2c/scipy-1.16.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3d0b80fb26d3e13a794c71d4b837e2a589d839fd574a6bbb4ee1288c213ad4a3", size = 35279542, upload-time = "2025-07-27T16:30:00.249Z" }, - { url = "https://files.pythonhosted.org/packages/57/59/0dc3c8b43e118f1e4ee2b798dcc96ac21bb20014e5f1f7a8e85cc0653bdb/scipy-1.16.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8503517c44c18d1030d666cb70aaac1cc8913608816e06742498833b128488b7", size = 35667665, upload-time = "2025-07-27T16:30:05.916Z" }, - { url = "https://files.pythonhosted.org/packages/45/5f/844ee26e34e2f3f9f8febb9343748e72daeaec64fe0c70e9bf1ff84ec955/scipy-1.16.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:30cc4bb81c41831ecfd6dc450baf48ffd80ef5aed0f5cf3ea775740e80f16ecc", size = 38045210, upload-time = "2025-07-27T16:30:11.655Z" }, - { url = "https://files.pythonhosted.org/packages/8d/d7/210f2b45290f444f1de64bc7353aa598ece9f0e90c384b4a156f9b1a5063/scipy-1.16.1-cp313-cp313t-win_amd64.whl", hash = "sha256:c24fa02f7ed23ae514460a22c57eca8f530dbfa50b1cfdbf4f37c05b5309cc39", size = 38593661, upload-time = "2025-07-27T16:30:17.825Z" }, - { url = "https://files.pythonhosted.org/packages/81/ea/84d481a5237ed223bd3d32d6e82d7a6a96e34756492666c260cef16011d1/scipy-1.16.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:796a5a9ad36fa3a782375db8f4241ab02a091308eb079746bc0f874c9b998318", size = 36525921, upload-time = "2025-07-27T16:30:30.081Z" }, - { url = "https://files.pythonhosted.org/packages/4e/9f/d9edbdeff9f3a664807ae3aea383e10afaa247e8e6255e6d2aa4515e8863/scipy-1.16.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:3ea0733a2ff73fd6fdc5fecca54ee9b459f4d74f00b99aced7d9a3adb43fb1cc", size = 28564152, upload-time = "2025-07-27T16:30:35.336Z" }, - { url = "https://files.pythonhosted.org/packages/3b/95/8125bcb1fe04bc267d103e76516243e8d5e11229e6b306bda1024a5423d1/scipy-1.16.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:85764fb15a2ad994e708258bb4ed8290d1305c62a4e1ef07c414356a24fcfbf8", size = 20836028, upload-time = "2025-07-27T16:30:39.421Z" }, - { url = "https://files.pythonhosted.org/packages/77/9c/bf92e215701fc70bbcd3d14d86337cf56a9b912a804b9c776a269524a9e9/scipy-1.16.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:ca66d980469cb623b1759bdd6e9fd97d4e33a9fad5b33771ced24d0cb24df67e", size = 23489666, upload-time = "2025-07-27T16:30:43.663Z" }, - { url = "https://files.pythonhosted.org/packages/5e/00/5e941d397d9adac41b02839011594620d54d99488d1be5be755c00cde9ee/scipy-1.16.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e7cc1ffcc230f568549fc56670bcf3df1884c30bd652c5da8138199c8c76dae0", size = 33358318, upload-time = "2025-07-27T16:30:48.982Z" }, - { url = "https://files.pythonhosted.org/packages/0e/87/8db3aa10dde6e3e8e7eb0133f24baa011377d543f5b19c71469cf2648026/scipy-1.16.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ddfb1e8d0b540cb4ee9c53fc3dea3186f97711248fb94b4142a1b27178d8b4b", size = 35185724, upload-time = "2025-07-27T16:30:54.26Z" }, - { url = "https://files.pythonhosted.org/packages/89/b4/6ab9ae443216807622bcff02690262d8184078ea467efee2f8c93288a3b1/scipy-1.16.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4dc0e7be79e95d8ba3435d193e0d8ce372f47f774cffd882f88ea4e1e1ddc731", size = 35554335, upload-time = "2025-07-27T16:30:59.765Z" }, - { url = "https://files.pythonhosted.org/packages/9c/9a/d0e9dc03c5269a1afb60661118296a32ed5d2c24298af61b676c11e05e56/scipy-1.16.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f23634f9e5adb51b2a77766dac217063e764337fbc816aa8ad9aaebcd4397fd3", size = 37960310, upload-time = "2025-07-27T16:31:06.151Z" }, - { url = "https://files.pythonhosted.org/packages/5e/00/c8f3130a50521a7977874817ca89e0599b1b4ee8e938bad8ae798a0e1f0d/scipy-1.16.1-cp314-cp314-win_amd64.whl", hash = "sha256:57d75524cb1c5a374958a2eae3d84e1929bb971204cc9d52213fb8589183fc19", size = 39319239, upload-time = "2025-07-27T16:31:59.942Z" }, - { url = "https://files.pythonhosted.org/packages/f2/f2/1ca3eda54c3a7e4c92f6acef7db7b3a057deb135540d23aa6343ef8ad333/scipy-1.16.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:d8da7c3dd67bcd93f15618938f43ed0995982eb38973023d46d4646c4283ad65", size = 36939460, upload-time = "2025-07-27T16:31:11.865Z" }, - { url = "https://files.pythonhosted.org/packages/80/30/98c2840b293a132400c0940bb9e140171dcb8189588619048f42b2ce7b4f/scipy-1.16.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:cc1d2f2fd48ba1e0620554fe5bc44d3e8f5d4185c8c109c7fbdf5af2792cfad2", size = 29093322, upload-time = "2025-07-27T16:31:17.045Z" }, - { url = "https://files.pythonhosted.org/packages/c1/e6/1e6e006e850622cf2a039b62d1a6ddc4497d4851e58b68008526f04a9a00/scipy-1.16.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:21a611ced9275cb861bacadbada0b8c0623bc00b05b09eb97f23b370fc2ae56d", size = 21365329, upload-time = "2025-07-27T16:31:21.188Z" }, - { url = "https://files.pythonhosted.org/packages/8e/02/72a5aa5b820589dda9a25e329ca752842bfbbaf635e36bc7065a9b42216e/scipy-1.16.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dfbb25dffc4c3dd9371d8ab456ca81beeaf6f9e1c2119f179392f0dc1ab7695", size = 23897544, upload-time = "2025-07-27T16:31:25.408Z" }, - { url = "https://files.pythonhosted.org/packages/2b/dc/7122d806a6f9eb8a33532982234bed91f90272e990f414f2830cfe656e0b/scipy-1.16.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f0ebb7204f063fad87fc0a0e4ff4a2ff40b2a226e4ba1b7e34bf4b79bf97cd86", size = 33442112, upload-time = "2025-07-27T16:31:30.62Z" }, - { url = "https://files.pythonhosted.org/packages/24/39/e383af23564daa1021a5b3afbe0d8d6a68ec639b943661841f44ac92de85/scipy-1.16.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f1b9e5962656f2734c2b285a8745358ecb4e4efbadd00208c80a389227ec61ff", size = 35286594, upload-time = "2025-07-27T16:31:36.112Z" }, - { url = "https://files.pythonhosted.org/packages/95/47/1a0b0aff40c3056d955f38b0df5d178350c3d74734ec54f9c68d23910be5/scipy-1.16.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e1a106f8c023d57a2a903e771228bf5c5b27b5d692088f457acacd3b54511e4", size = 35665080, upload-time = "2025-07-27T16:31:42.025Z" }, - { url = "https://files.pythonhosted.org/packages/64/df/ce88803e9ed6e27fe9b9abefa157cf2c80e4fa527cf17ee14be41f790ad4/scipy-1.16.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:709559a1db68a9abc3b2c8672c4badf1614f3b440b3ab326d86a5c0491eafae3", size = 38050306, upload-time = "2025-07-27T16:31:48.109Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6c/a76329897a7cae4937d403e623aa6aaea616a0bb5b36588f0b9d1c9a3739/scipy-1.16.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c0c804d60492a0aad7f5b2bb1862f4548b990049e27e828391ff2bf6f7199998", size = 39427705, upload-time = "2025-07-27T16:31:53.96Z" }, + { url = "https://files.pythonhosted.org/packages/da/91/812adc6f74409b461e3a5fa97f4f74c769016919203138a3bf6fc24ba4c5/scipy-1.16.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:c033fa32bab91dc98ca59d0cf23bb876454e2bb02cbe592d5023138778f70030", size = 36552519 }, + { url = "https://files.pythonhosted.org/packages/47/18/8e355edcf3b71418d9e9f9acd2708cc3a6c27e8f98fde0ac34b8a0b45407/scipy-1.16.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:6e5c2f74e5df33479b5cd4e97a9104c511518fbd979aa9b8f6aec18b2e9ecae7", size = 28638010 }, + { url = "https://files.pythonhosted.org/packages/d9/eb/e931853058607bdfbc11b86df19ae7a08686121c203483f62f1ecae5989c/scipy-1.16.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0a55ffe0ba0f59666e90951971a884d1ff6f4ec3275a48f472cfb64175570f77", size = 20909790 }, + { url = "https://files.pythonhosted.org/packages/45/0c/be83a271d6e96750cd0be2e000f35ff18880a46f05ce8b5d3465dc0f7a2a/scipy-1.16.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f8a5d6cd147acecc2603fbd382fed6c46f474cccfcf69ea32582e033fb54dcfe", size = 23513352 }, + { url = "https://files.pythonhosted.org/packages/7c/bf/fe6eb47e74f762f933cca962db7f2c7183acfdc4483bd1c3813cfe83e538/scipy-1.16.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb18899127278058bcc09e7b9966d41a5a43740b5bb8dcba401bd983f82e885b", size = 33534643 }, + { url = "https://files.pythonhosted.org/packages/bb/ba/63f402e74875486b87ec6506a4f93f6d8a0d94d10467280f3d9d7837ce3a/scipy-1.16.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adccd93a2fa937a27aae826d33e3bfa5edf9aa672376a4852d23a7cd67a2e5b7", size = 35376776 }, + { url = "https://files.pythonhosted.org/packages/c3/b4/04eb9d39ec26a1b939689102da23d505ea16cdae3dbb18ffc53d1f831044/scipy-1.16.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:18aca1646a29ee9a0625a1be5637fa798d4d81fdf426481f06d69af828f16958", size = 35698906 }, + { url = "https://files.pythonhosted.org/packages/04/d6/bb5468da53321baeb001f6e4e0d9049eadd175a4a497709939128556e3ec/scipy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d85495cef541729a70cdddbbf3e6b903421bc1af3e8e3a9a72a06751f33b7c39", size = 38129275 }, + { url = "https://files.pythonhosted.org/packages/c4/94/994369978509f227cba7dfb9e623254d0d5559506fe994aef4bea3ed469c/scipy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:226652fca853008119c03a8ce71ffe1b3f6d2844cc1686e8f9806edafae68596", size = 38644572 }, + { url = "https://files.pythonhosted.org/packages/f8/d9/ec4864f5896232133f51382b54a08de91a9d1af7a76dfa372894026dfee2/scipy-1.16.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81b433bbeaf35728dad619afc002db9b189e45eebe2cd676effe1fb93fef2b9c", size = 36575194 }, + { url = "https://files.pythonhosted.org/packages/5c/6d/40e81ecfb688e9d25d34a847dca361982a6addf8e31f0957b1a54fbfa994/scipy-1.16.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:886cc81fdb4c6903a3bb0464047c25a6d1016fef77bb97949817d0c0d79f9e04", size = 28594590 }, + { url = "https://files.pythonhosted.org/packages/0e/37/9f65178edfcc629377ce9a64fc09baebea18c80a9e57ae09a52edf84880b/scipy-1.16.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:15240c3aac087a522b4eaedb09f0ad061753c5eebf1ea430859e5bf8640d5919", size = 20866458 }, + { url = "https://files.pythonhosted.org/packages/2c/7b/749a66766871ea4cb1d1ea10f27004db63023074c22abed51f22f09770e0/scipy-1.16.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:65f81a25805f3659b48126b5053d9e823d3215e4a63730b5e1671852a1705921", size = 23539318 }, + { url = "https://files.pythonhosted.org/packages/c4/db/8d4afec60eb833a666434d4541a3151eedbf2494ea6d4d468cbe877f00cd/scipy-1.16.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6c62eea7f607f122069b9bad3f99489ddca1a5173bef8a0c75555d7488b6f725", size = 33292899 }, + { url = "https://files.pythonhosted.org/packages/51/1e/79023ca3bbb13a015d7d2757ecca3b81293c663694c35d6541b4dca53e98/scipy-1.16.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f965bbf3235b01c776115ab18f092a95aa74c271a52577bcb0563e85738fd618", size = 35162637 }, + { url = "https://files.pythonhosted.org/packages/b6/49/0648665f9c29fdaca4c679182eb972935b3b4f5ace41d323c32352f29816/scipy-1.16.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f006e323874ffd0b0b816d8c6a8e7f9a73d55ab3b8c3f72b752b226d0e3ac83d", size = 35490507 }, + { url = "https://files.pythonhosted.org/packages/62/8f/66cbb9d6bbb18d8c658f774904f42a92078707a7c71e5347e8bf2f52bb89/scipy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8fd15fc5085ab4cca74cb91fe0a4263b1f32e4420761ddae531ad60934c2119", size = 37923998 }, + { url = "https://files.pythonhosted.org/packages/14/c3/61f273ae550fbf1667675701112e380881905e28448c080b23b5a181df7c/scipy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7b8013c6c066609577d910d1a2a077021727af07b6fab0ee22c2f901f22352a", size = 38508060 }, + { url = "https://files.pythonhosted.org/packages/93/0b/b5c99382b839854a71ca9482c684e3472badc62620287cbbdab499b75ce6/scipy-1.16.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5451606823a5e73dfa621a89948096c6528e2896e40b39248295d3a0138d594f", size = 36533717 }, + { url = "https://files.pythonhosted.org/packages/eb/e5/69ab2771062c91e23e07c12e7d5033a6b9b80b0903ee709c3c36b3eb520c/scipy-1.16.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:89728678c5ca5abd610aee148c199ac1afb16e19844401ca97d43dc548a354eb", size = 28570009 }, + { url = "https://files.pythonhosted.org/packages/f4/69/bd75dbfdd3cf524f4d753484d723594aed62cfaac510123e91a6686d520b/scipy-1.16.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e756d688cb03fd07de0fffad475649b03cb89bee696c98ce508b17c11a03f95c", size = 20841942 }, + { url = "https://files.pythonhosted.org/packages/ea/74/add181c87663f178ba7d6144b370243a87af8476664d5435e57d599e6874/scipy-1.16.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5aa2687b9935da3ed89c5dbed5234576589dd28d0bf7cd237501ccfbdf1ad608", size = 23498507 }, + { url = "https://files.pythonhosted.org/packages/1d/74/ece2e582a0d9550cee33e2e416cc96737dce423a994d12bbe59716f47ff1/scipy-1.16.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0851f6a1e537fe9399f35986897e395a1aa61c574b178c0d456be5b1a0f5ca1f", size = 33286040 }, + { url = "https://files.pythonhosted.org/packages/e4/82/08e4076df538fb56caa1d489588d880ec7c52d8273a606bb54d660528f7c/scipy-1.16.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fedc2cbd1baed37474b1924c331b97bdff611d762c196fac1a9b71e67b813b1b", size = 35176096 }, + { url = "https://files.pythonhosted.org/packages/fa/79/cd710aab8c921375711a8321c6be696e705a120e3011a643efbbcdeeabcc/scipy-1.16.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2ef500e72f9623a6735769e4b93e9dcb158d40752cdbb077f305487e3e2d1f45", size = 35490328 }, + { url = "https://files.pythonhosted.org/packages/71/73/e9cc3d35ee4526d784520d4494a3e1ca969b071fb5ae5910c036a375ceec/scipy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:978d8311674b05a8f7ff2ea6c6bce5d8b45a0cb09d4c5793e0318f448613ea65", size = 37939921 }, + { url = "https://files.pythonhosted.org/packages/21/12/c0efd2941f01940119b5305c375ae5c0fcb7ec193f806bd8f158b73a1782/scipy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:81929ed0fa7a5713fcdd8b2e6f73697d3b4c4816d090dd34ff937c20fa90e8ab", size = 38479462 }, + { url = "https://files.pythonhosted.org/packages/7a/19/c3d08b675260046a991040e1ea5d65f91f40c7df1045fffff412dcfc6765/scipy-1.16.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:bcc12db731858abda693cecdb3bdc9e6d4bd200213f49d224fe22df82687bdd6", size = 36938832 }, + { url = "https://files.pythonhosted.org/packages/81/f2/ce53db652c033a414a5b34598dba6b95f3d38153a2417c5a3883da429029/scipy-1.16.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:744d977daa4becb9fc59135e75c069f8d301a87d64f88f1e602a9ecf51e77b27", size = 29093084 }, + { url = "https://files.pythonhosted.org/packages/a9/ae/7a10ff04a7dc15f9057d05b33737ade244e4bd195caa3f7cc04d77b9e214/scipy-1.16.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:dc54f76ac18073bcecffb98d93f03ed6b81a92ef91b5d3b135dcc81d55a724c7", size = 21365098 }, + { url = "https://files.pythonhosted.org/packages/36/ac/029ff710959932ad3c2a98721b20b405f05f752f07344622fd61a47c5197/scipy-1.16.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:367d567ee9fc1e9e2047d31f39d9d6a7a04e0710c86e701e053f237d14a9b4f6", size = 23896858 }, + { url = "https://files.pythonhosted.org/packages/71/13/d1ef77b6bd7898720e1f0b6b3743cb945f6c3cafa7718eaac8841035ab60/scipy-1.16.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4cf5785e44e19dcd32a0e4807555e1e9a9b8d475c6afff3d21c3c543a6aa84f4", size = 33438311 }, + { url = "https://files.pythonhosted.org/packages/2d/e0/e64a6821ffbb00b4c5b05169f1c1fddb4800e9307efe3db3788995a82a2c/scipy-1.16.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3d0b80fb26d3e13a794c71d4b837e2a589d839fd574a6bbb4ee1288c213ad4a3", size = 35279542 }, + { url = "https://files.pythonhosted.org/packages/57/59/0dc3c8b43e118f1e4ee2b798dcc96ac21bb20014e5f1f7a8e85cc0653bdb/scipy-1.16.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8503517c44c18d1030d666cb70aaac1cc8913608816e06742498833b128488b7", size = 35667665 }, + { url = "https://files.pythonhosted.org/packages/45/5f/844ee26e34e2f3f9f8febb9343748e72daeaec64fe0c70e9bf1ff84ec955/scipy-1.16.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:30cc4bb81c41831ecfd6dc450baf48ffd80ef5aed0f5cf3ea775740e80f16ecc", size = 38045210 }, + { url = "https://files.pythonhosted.org/packages/8d/d7/210f2b45290f444f1de64bc7353aa598ece9f0e90c384b4a156f9b1a5063/scipy-1.16.1-cp313-cp313t-win_amd64.whl", hash = "sha256:c24fa02f7ed23ae514460a22c57eca8f530dbfa50b1cfdbf4f37c05b5309cc39", size = 38593661 }, + { url = "https://files.pythonhosted.org/packages/81/ea/84d481a5237ed223bd3d32d6e82d7a6a96e34756492666c260cef16011d1/scipy-1.16.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:796a5a9ad36fa3a782375db8f4241ab02a091308eb079746bc0f874c9b998318", size = 36525921 }, + { url = "https://files.pythonhosted.org/packages/4e/9f/d9edbdeff9f3a664807ae3aea383e10afaa247e8e6255e6d2aa4515e8863/scipy-1.16.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:3ea0733a2ff73fd6fdc5fecca54ee9b459f4d74f00b99aced7d9a3adb43fb1cc", size = 28564152 }, + { url = "https://files.pythonhosted.org/packages/3b/95/8125bcb1fe04bc267d103e76516243e8d5e11229e6b306bda1024a5423d1/scipy-1.16.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:85764fb15a2ad994e708258bb4ed8290d1305c62a4e1ef07c414356a24fcfbf8", size = 20836028 }, + { url = "https://files.pythonhosted.org/packages/77/9c/bf92e215701fc70bbcd3d14d86337cf56a9b912a804b9c776a269524a9e9/scipy-1.16.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:ca66d980469cb623b1759bdd6e9fd97d4e33a9fad5b33771ced24d0cb24df67e", size = 23489666 }, + { url = "https://files.pythonhosted.org/packages/5e/00/5e941d397d9adac41b02839011594620d54d99488d1be5be755c00cde9ee/scipy-1.16.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e7cc1ffcc230f568549fc56670bcf3df1884c30bd652c5da8138199c8c76dae0", size = 33358318 }, + { url = "https://files.pythonhosted.org/packages/0e/87/8db3aa10dde6e3e8e7eb0133f24baa011377d543f5b19c71469cf2648026/scipy-1.16.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ddfb1e8d0b540cb4ee9c53fc3dea3186f97711248fb94b4142a1b27178d8b4b", size = 35185724 }, + { url = "https://files.pythonhosted.org/packages/89/b4/6ab9ae443216807622bcff02690262d8184078ea467efee2f8c93288a3b1/scipy-1.16.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4dc0e7be79e95d8ba3435d193e0d8ce372f47f774cffd882f88ea4e1e1ddc731", size = 35554335 }, + { url = "https://files.pythonhosted.org/packages/9c/9a/d0e9dc03c5269a1afb60661118296a32ed5d2c24298af61b676c11e05e56/scipy-1.16.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f23634f9e5adb51b2a77766dac217063e764337fbc816aa8ad9aaebcd4397fd3", size = 37960310 }, + { url = "https://files.pythonhosted.org/packages/5e/00/c8f3130a50521a7977874817ca89e0599b1b4ee8e938bad8ae798a0e1f0d/scipy-1.16.1-cp314-cp314-win_amd64.whl", hash = "sha256:57d75524cb1c5a374958a2eae3d84e1929bb971204cc9d52213fb8589183fc19", size = 39319239 }, + { url = "https://files.pythonhosted.org/packages/f2/f2/1ca3eda54c3a7e4c92f6acef7db7b3a057deb135540d23aa6343ef8ad333/scipy-1.16.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:d8da7c3dd67bcd93f15618938f43ed0995982eb38973023d46d4646c4283ad65", size = 36939460 }, + { url = "https://files.pythonhosted.org/packages/80/30/98c2840b293a132400c0940bb9e140171dcb8189588619048f42b2ce7b4f/scipy-1.16.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:cc1d2f2fd48ba1e0620554fe5bc44d3e8f5d4185c8c109c7fbdf5af2792cfad2", size = 29093322 }, + { url = "https://files.pythonhosted.org/packages/c1/e6/1e6e006e850622cf2a039b62d1a6ddc4497d4851e58b68008526f04a9a00/scipy-1.16.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:21a611ced9275cb861bacadbada0b8c0623bc00b05b09eb97f23b370fc2ae56d", size = 21365329 }, + { url = "https://files.pythonhosted.org/packages/8e/02/72a5aa5b820589dda9a25e329ca752842bfbbaf635e36bc7065a9b42216e/scipy-1.16.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dfbb25dffc4c3dd9371d8ab456ca81beeaf6f9e1c2119f179392f0dc1ab7695", size = 23897544 }, + { url = "https://files.pythonhosted.org/packages/2b/dc/7122d806a6f9eb8a33532982234bed91f90272e990f414f2830cfe656e0b/scipy-1.16.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f0ebb7204f063fad87fc0a0e4ff4a2ff40b2a226e4ba1b7e34bf4b79bf97cd86", size = 33442112 }, + { url = "https://files.pythonhosted.org/packages/24/39/e383af23564daa1021a5b3afbe0d8d6a68ec639b943661841f44ac92de85/scipy-1.16.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f1b9e5962656f2734c2b285a8745358ecb4e4efbadd00208c80a389227ec61ff", size = 35286594 }, + { url = "https://files.pythonhosted.org/packages/95/47/1a0b0aff40c3056d955f38b0df5d178350c3d74734ec54f9c68d23910be5/scipy-1.16.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e1a106f8c023d57a2a903e771228bf5c5b27b5d692088f457acacd3b54511e4", size = 35665080 }, + { url = "https://files.pythonhosted.org/packages/64/df/ce88803e9ed6e27fe9b9abefa157cf2c80e4fa527cf17ee14be41f790ad4/scipy-1.16.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:709559a1db68a9abc3b2c8672c4badf1614f3b440b3ab326d86a5c0491eafae3", size = 38050306 }, + { url = "https://files.pythonhosted.org/packages/6e/6c/a76329897a7cae4937d403e623aa6aaea616a0bb5b36588f0b9d1c9a3739/scipy-1.16.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c0c804d60492a0aad7f5b2bb1862f4548b990049e27e828391ff2bf6f7199998", size = 39427705 }, ] [[package]] name = "setuptools" -version = "70.3.0" +version = "80.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/d8/10a70e86f6c28ae59f101a9de6d77bf70f147180fbf40c3af0f64080adc3/setuptools-70.3.0.tar.gz", hash = "sha256:f171bab1dfbc86b132997f26a119f6056a57950d058587841a0082e8830f9dc5", size = 2333112, upload-time = "2024-07-09T16:08:06.251Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/15/88e46eb9387e905704b69849618e699dc2f54407d8953cc4ec4b8b46528d/setuptools-70.3.0-py3-none-any.whl", hash = "sha256:fe384da74336c398e0d956d1cae0669bc02eed936cdb1d49b57de1990dc11ffc", size = 931070, upload-time = "2024-07-09T16:07:58.829Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486 }, +] + +[[package]] +name = "shapely" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ca/3c/2da625233f4e605155926566c0e7ea8dda361877f48e8b1655e53456f252/shapely-2.1.1.tar.gz", hash = "sha256:500621967f2ffe9642454808009044c21e5b35db89ce69f8a2042c2ffd0e2772", size = 315422 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/fa/f18025c95b86116dd8f1ec58cab078bd59ab51456b448136ca27463be533/shapely-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d8ccc872a632acb7bdcb69e5e78df27213f7efd195882668ffba5405497337c6", size = 1825117 }, + { url = "https://files.pythonhosted.org/packages/c7/65/46b519555ee9fb851234288be7c78be11e6260995281071d13abf2c313d0/shapely-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f24f2ecda1e6c091da64bcbef8dd121380948074875bd1b247b3d17e99407099", size = 1628541 }, + { url = "https://files.pythonhosted.org/packages/29/51/0b158a261df94e33505eadfe737db9531f346dfa60850945ad25fd4162f1/shapely-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45112a5be0b745b49e50f8829ce490eb67fefb0cea8d4f8ac5764bfedaa83d2d", size = 2948453 }, + { url = "https://files.pythonhosted.org/packages/a9/4f/6c9bb4bd7b1a14d7051641b9b479ad2a643d5cbc382bcf5bd52fd0896974/shapely-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c10ce6f11904d65e9bbb3e41e774903c944e20b3f0b282559885302f52f224a", size = 3057029 }, + { url = "https://files.pythonhosted.org/packages/89/0b/ad1b0af491d753a83ea93138eee12a4597f763ae12727968d05934fe7c78/shapely-2.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:61168010dfe4e45f956ffbbaf080c88afce199ea81eb1f0ac43230065df320bd", size = 3894342 }, + { url = "https://files.pythonhosted.org/packages/7d/96/73232c5de0b9fdf0ec7ddfc95c43aaf928740e87d9f168bff0e928d78c6d/shapely-2.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cacf067cdff741cd5c56a21c52f54ece4e4dad9d311130493a791997da4a886b", size = 4056766 }, + { url = "https://files.pythonhosted.org/packages/43/cc/eec3c01f754f5b3e0c47574b198f9deb70465579ad0dad0e1cef2ce9e103/shapely-2.1.1-cp310-cp310-win32.whl", hash = "sha256:23b8772c3b815e7790fb2eab75a0b3951f435bc0fce7bb146cb064f17d35ab4f", size = 1523744 }, + { url = "https://files.pythonhosted.org/packages/50/fc/a7187e6dadb10b91e66a9e715d28105cde6489e1017cce476876185a43da/shapely-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:2c7b2b6143abf4fa77851cef8ef690e03feade9a0d48acd6dc41d9e0e78d7ca6", size = 1703061 }, + { url = "https://files.pythonhosted.org/packages/19/97/2df985b1e03f90c503796ad5ecd3d9ed305123b64d4ccb54616b30295b29/shapely-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:587a1aa72bc858fab9b8c20427b5f6027b7cbc92743b8e2c73b9de55aa71c7a7", size = 1819368 }, + { url = "https://files.pythonhosted.org/packages/56/17/504518860370f0a28908b18864f43d72f03581e2b6680540ca668f07aa42/shapely-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9fa5c53b0791a4b998f9ad84aad456c988600757a96b0a05e14bba10cebaaaea", size = 1625362 }, + { url = "https://files.pythonhosted.org/packages/36/a1/9677337d729b79fce1ef3296aac6b8ef4743419086f669e8a8070eff8f40/shapely-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aabecd038841ab5310d23495253f01c2a82a3aedae5ab9ca489be214aa458aa7", size = 2999005 }, + { url = "https://files.pythonhosted.org/packages/a2/17/e09357274699c6e012bbb5a8ea14765a4d5860bb658df1931c9f90d53bd3/shapely-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586f6aee1edec04e16227517a866df3e9a2e43c1f635efc32978bb3dc9c63753", size = 3108489 }, + { url = "https://files.pythonhosted.org/packages/17/5d/93a6c37c4b4e9955ad40834f42b17260ca74ecf36df2e81bb14d12221b90/shapely-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b9878b9e37ad26c72aada8de0c9cfe418d9e2ff36992a1693b7f65a075b28647", size = 3945727 }, + { url = "https://files.pythonhosted.org/packages/a3/1a/ad696648f16fd82dd6bfcca0b3b8fbafa7aacc13431c7fc4c9b49e481681/shapely-2.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9a531c48f289ba355e37b134e98e28c557ff13965d4653a5228d0f42a09aed0", size = 4109311 }, + { url = "https://files.pythonhosted.org/packages/d4/38/150dd245beab179ec0d4472bf6799bf18f21b1efbef59ac87de3377dbf1c/shapely-2.1.1-cp311-cp311-win32.whl", hash = "sha256:4866de2673a971820c75c0167b1f1cd8fb76f2d641101c23d3ca021ad0449bab", size = 1522982 }, + { url = "https://files.pythonhosted.org/packages/93/5b/842022c00fbb051083c1c85430f3bb55565b7fd2d775f4f398c0ba8052ce/shapely-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:20a9d79958b3d6c70d8a886b250047ea32ff40489d7abb47d01498c704557a93", size = 1703872 }, + { url = "https://files.pythonhosted.org/packages/fb/64/9544dc07dfe80a2d489060791300827c941c451e2910f7364b19607ea352/shapely-2.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2827365b58bf98efb60affc94a8e01c56dd1995a80aabe4b701465d86dcbba43", size = 1833021 }, + { url = "https://files.pythonhosted.org/packages/07/aa/fb5f545e72e89b6a0f04a0effda144f5be956c9c312c7d4e00dfddbddbcf/shapely-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9c551f7fa7f1e917af2347fe983f21f212863f1d04f08eece01e9c275903fad", size = 1643018 }, + { url = "https://files.pythonhosted.org/packages/03/46/61e03edba81de729f09d880ce7ae5c1af873a0814206bbfb4402ab5c3388/shapely-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78dec4d4fbe7b1db8dc36de3031767e7ece5911fb7782bc9e95c5cdec58fb1e9", size = 2986417 }, + { url = "https://files.pythonhosted.org/packages/1f/1e/83ec268ab8254a446b4178b45616ab5822d7b9d2b7eb6e27cf0b82f45601/shapely-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:872d3c0a7b8b37da0e23d80496ec5973c4692920b90de9f502b5beb994bbaaef", size = 3098224 }, + { url = "https://files.pythonhosted.org/packages/f1/44/0c21e7717c243e067c9ef8fa9126de24239f8345a5bba9280f7bb9935959/shapely-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2e2b9125ebfbc28ecf5353511de62f75a8515ae9470521c9a693e4bb9fbe0cf1", size = 3925982 }, + { url = "https://files.pythonhosted.org/packages/15/50/d3b4e15fefc103a0eb13d83bad5f65cd6e07a5d8b2ae920e767932a247d1/shapely-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4b96cea171b3d7f6786976a0520f178c42792897653ecca0c5422fb1e6946e6d", size = 4089122 }, + { url = "https://files.pythonhosted.org/packages/bd/05/9a68f27fc6110baeedeeebc14fd86e73fa38738c5b741302408fb6355577/shapely-2.1.1-cp312-cp312-win32.whl", hash = "sha256:39dca52201e02996df02e447f729da97cfb6ff41a03cb50f5547f19d02905af8", size = 1522437 }, + { url = "https://files.pythonhosted.org/packages/bc/e9/a4560e12b9338842a1f82c9016d2543eaa084fce30a1ca11991143086b57/shapely-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:13d643256f81d55a50013eff6321142781cf777eb6a9e207c2c9e6315ba6044a", size = 1703479 }, + { url = "https://files.pythonhosted.org/packages/71/8e/2bc836437f4b84d62efc1faddce0d4e023a5d990bbddd3c78b2004ebc246/shapely-2.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3004a644d9e89e26c20286d5fdc10f41b1744c48ce910bd1867fdff963fe6c48", size = 1832107 }, + { url = "https://files.pythonhosted.org/packages/12/a2/12c7cae5b62d5d851c2db836eadd0986f63918a91976495861f7c492f4a9/shapely-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1415146fa12d80a47d13cfad5310b3c8b9c2aa8c14a0c845c9d3d75e77cb54f6", size = 1642355 }, + { url = "https://files.pythonhosted.org/packages/5b/7e/6d28b43d53fea56de69c744e34c2b999ed4042f7a811dc1bceb876071c95/shapely-2.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21fcab88b7520820ec16d09d6bea68652ca13993c84dffc6129dc3607c95594c", size = 2968871 }, + { url = "https://files.pythonhosted.org/packages/dd/87/1017c31e52370b2b79e4d29e07cbb590ab9e5e58cf7e2bdfe363765d6251/shapely-2.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5ce6a5cc52c974b291237a96c08c5592e50f066871704fb5b12be2639d9026a", size = 3080830 }, + { url = "https://files.pythonhosted.org/packages/1d/fe/f4a03d81abd96a6ce31c49cd8aaba970eaaa98e191bd1e4d43041e57ae5a/shapely-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:04e4c12a45a1d70aeb266618d8cf81a2de9c4df511b63e105b90bfdfb52146de", size = 3908961 }, + { url = "https://files.pythonhosted.org/packages/ef/59/7605289a95a6844056a2017ab36d9b0cb9d6a3c3b5317c1f968c193031c9/shapely-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6ca74d851ca5264aae16c2b47e96735579686cb69fa93c4078070a0ec845b8d8", size = 4079623 }, + { url = "https://files.pythonhosted.org/packages/bc/4d/9fea036eff2ef4059d30247128b2d67aaa5f0b25e9fc27e1d15cc1b84704/shapely-2.1.1-cp313-cp313-win32.whl", hash = "sha256:fd9130501bf42ffb7e0695b9ea17a27ae8ce68d50b56b6941c7f9b3d3453bc52", size = 1521916 }, + { url = "https://files.pythonhosted.org/packages/12/d9/6d13b8957a17c95794f0c4dfb65ecd0957e6c7131a56ce18d135c1107a52/shapely-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:ab8d878687b438a2f4c138ed1a80941c6ab0029e0f4c785ecfe114413b498a97", size = 1702746 }, + { url = "https://files.pythonhosted.org/packages/60/36/b1452e3e7f35f5f6454d96f3be6e2bb87082720ff6c9437ecc215fa79be0/shapely-2.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0c062384316a47f776305ed2fa22182717508ffdeb4a56d0ff4087a77b2a0f6d", size = 1833482 }, + { url = "https://files.pythonhosted.org/packages/ce/ca/8e6f59be0718893eb3e478141285796a923636dc8f086f83e5b0ec0036d0/shapely-2.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4ecf6c196b896e8f1360cc219ed4eee1c1e5f5883e505d449f263bd053fb8c05", size = 1642256 }, + { url = "https://files.pythonhosted.org/packages/ab/78/0053aea449bb1d4503999525fec6232f049abcdc8df60d290416110de943/shapely-2.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb00070b4c4860f6743c600285109c273cca5241e970ad56bb87bef0be1ea3a0", size = 3016614 }, + { url = "https://files.pythonhosted.org/packages/ee/53/36f1b1de1dfafd1b457dcbafa785b298ce1b8a3e7026b79619e708a245d5/shapely-2.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d14a9afa5fa980fbe7bf63706fdfb8ff588f638f145a1d9dbc18374b5b7de913", size = 3093542 }, + { url = "https://files.pythonhosted.org/packages/b9/bf/0619f37ceec6b924d84427c88835b61f27f43560239936ff88915c37da19/shapely-2.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b640e390dabde790e3fb947198b466e63223e0a9ccd787da5f07bcb14756c28d", size = 3945961 }, + { url = "https://files.pythonhosted.org/packages/93/c9/20ca4afeb572763b07a7997f00854cb9499df6af85929e93012b189d8917/shapely-2.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:69e08bf9697c1b73ec6aa70437db922bafcea7baca131c90c26d59491a9760f9", size = 4089514 }, + { url = "https://files.pythonhosted.org/packages/33/6a/27036a5a560b80012a544366bceafd491e8abb94a8db14047b5346b5a749/shapely-2.1.1-cp313-cp313t-win32.whl", hash = "sha256:ef2d09d5a964cc90c2c18b03566cf918a61c248596998a0301d5b632beadb9db", size = 1540607 }, + { url = "https://files.pythonhosted.org/packages/ea/f1/5e9b3ba5c7aa7ebfaf269657e728067d16a7c99401c7973ddf5f0cf121bd/shapely-2.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8cb8f17c377260452e9d7720eeaf59082c5f8ea48cf104524d953e5d36d4bdb7", size = 1723061 }, ] [[package]] @@ -2833,27 +2973,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "wsproto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b0/d4/bfa032f961103eba93de583b161f0e6a5b63cebb8f2c7d0c6e6efe1e3d2e/simple_websocket-1.1.0.tar.gz", hash = "sha256:7939234e7aa067c534abdab3a9ed933ec9ce4691b0713c78acb195560aa52ae4", size = 17300, upload-time = "2024-10-10T22:39:31.412Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/d4/bfa032f961103eba93de583b161f0e6a5b63cebb8f2c7d0c6e6efe1e3d2e/simple_websocket-1.1.0.tar.gz", hash = "sha256:7939234e7aa067c534abdab3a9ed933ec9ce4691b0713c78acb195560aa52ae4", size = 17300 } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl", hash = "sha256:4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c", size = 13842, upload-time = "2024-10-10T22:39:29.645Z" }, + { url = "https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl", hash = "sha256:4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c", size = 13842 }, ] [[package]] name = "six" version = "1.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041, upload-time = "2021-05-05T14:18:18.379Z" } +sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053, upload-time = "2021-05-05T14:18:17.237Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 }, ] [[package]] name = "sniffio" version = "1.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cd/50/d49c388cae4ec10e8109b1b833fd265511840706808576df3ada99ecb0ac/sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101", size = 17103, upload-time = "2022-09-01T12:31:36.968Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/50/d49c388cae4ec10e8109b1b833fd265511840706808576df3ada99ecb0ac/sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101", size = 17103 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/a0/5dba8ed157b0136607c7f2151db695885606968d1fae123dc3391e0cfdbf/sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384", size = 10165, upload-time = "2022-09-01T12:31:34.186Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a0/5dba8ed157b0136607c7f2151db695885606968d1fae123dc3391e0cfdbf/sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384", size = 10165 }, ] [[package]] @@ -2863,9 +3003,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3e/da/1fb4bdb72ae12b834becd7e1e7e47001d32f91ec0ce8d7bc1b618d9f0bd9/starlette-0.41.2.tar.gz", hash = "sha256:9834fd799d1a87fd346deb76158668cfa0b0d56f85caefe8268e2d97c3468b62", size = 2573867, upload-time = "2024-10-27T08:20:02.818Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/da/1fb4bdb72ae12b834becd7e1e7e47001d32f91ec0ce8d7bc1b618d9f0bd9/starlette-0.41.2.tar.gz", hash = "sha256:9834fd799d1a87fd346deb76158668cfa0b0d56f85caefe8268e2d97c3468b62", size = 2573867 } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/43/f185bfd0ca1d213beb4293bed51d92254df23d8ceaf6c0e17146d508a776/starlette-0.41.2-py3-none-any.whl", hash = "sha256:fbc189474b4731cf30fcef52f18a8d070e3f3b46c6a04c97579e85e6ffca942d", size = 73259, upload-time = "2024-10-27T08:20:00.052Z" }, + { url = "https://files.pythonhosted.org/packages/54/43/f185bfd0ca1d213beb4293bed51d92254df23d8ceaf6c0e17146d508a776/starlette-0.41.2-py3-none-any.whl", hash = "sha256:fbc189474b4731cf30fcef52f18a8d070e3f3b46c6a04c97579e85e6ffca942d", size = 73259 }, ] [[package]] @@ -2875,18 +3015,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mpmath" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e5/57/3485a1a3dff51bfd691962768b14310dae452431754bfc091250be50dd29/sympy-1.12.tar.gz", hash = "sha256:ebf595c8dac3e0fdc4152c51878b498396ec7f30e7a914d6071e674d49420fb8", size = 6722203, upload-time = "2023-05-10T18:23:00.378Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/57/3485a1a3dff51bfd691962768b14310dae452431754bfc091250be50dd29/sympy-1.12.tar.gz", hash = "sha256:ebf595c8dac3e0fdc4152c51878b498396ec7f30e7a914d6071e674d49420fb8", size = 6722203 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/05/e6600db80270777c4a64238a98d442f0fd07cc8915be2a1c16da7f2b9e74/sympy-1.12-py3-none-any.whl", hash = "sha256:c3588cd4295d0c0f603d0f2ae780587e64e2efeedb3521e46b9bb1d08d184fa5", size = 5742435, upload-time = "2023-05-10T18:22:14.76Z" }, + { url = "https://files.pythonhosted.org/packages/d2/05/e6600db80270777c4a64238a98d442f0fd07cc8915be2a1c16da7f2b9e74/sympy-1.12-py3-none-any.whl", hash = "sha256:c3588cd4295d0c0f603d0f2ae780587e64e2efeedb3521e46b9bb1d08d184fa5", size = 5742435 }, ] [[package]] name = "threadpoolctl" version = "3.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/8a/c05f7831beb32aff70f808766224f11c650f7edfd49b27a8fc6666107006/threadpoolctl-3.2.0.tar.gz", hash = "sha256:c96a0ba3bdddeaca37dc4cc7344aafad41cdb8c313f74fdfe387a867bba93355", size = 36266, upload-time = "2023-07-13T14:53:53.299Z" } +sdist = { url = "https://files.pythonhosted.org/packages/47/8a/c05f7831beb32aff70f808766224f11c650f7edfd49b27a8fc6666107006/threadpoolctl-3.2.0.tar.gz", hash = "sha256:c96a0ba3bdddeaca37dc4cc7344aafad41cdb8c313f74fdfe387a867bba93355", size = 36266 } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/12/fd4dea011af9d69e1cad05c75f3f7202cdcbeac9b712eea58ca779a72865/threadpoolctl-3.2.0-py3-none-any.whl", hash = "sha256:2b7818516e423bdaebb97c723f86a7c6b0a83d3f3b0970328d66f4d9104dc032", size = 15539, upload-time = "2023-07-13T14:53:39.336Z" }, + { url = "https://files.pythonhosted.org/packages/81/12/fd4dea011af9d69e1cad05c75f3f7202cdcbeac9b712eea58ca779a72865/threadpoolctl-3.2.0-py3-none-any.whl", hash = "sha256:2b7818516e423bdaebb97c723f86a7c6b0a83d3f3b0970328d66f4d9104dc032", size = 15539 }, ] [[package]] @@ -2896,43 +3036,43 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f8/a4/6c0eadea1ccfcda27e6cce400c366098b5b082138a073f4252fe399f4148/tifffile-2023.12.9.tar.gz", hash = "sha256:9dd1da91180a6453018a241ff219e1905f169384355cd89c9ef4034c1b46cdb8", size = 353467, upload-time = "2023-12-09T20:46:29.203Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/a4/6c0eadea1ccfcda27e6cce400c366098b5b082138a073f4252fe399f4148/tifffile-2023.12.9.tar.gz", hash = "sha256:9dd1da91180a6453018a241ff219e1905f169384355cd89c9ef4034c1b46cdb8", size = 353467 } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/a4/569fc717831969cf48bced350bdaf070cdeab06918d179429899e144358d/tifffile-2023.12.9-py3-none-any.whl", hash = "sha256:9b066e4b1a900891ea42ffd33dab8ba34c537935618b9893ddef42d7d422692f", size = 223627, upload-time = "2023-12-09T20:46:26.569Z" }, + { url = "https://files.pythonhosted.org/packages/54/a4/569fc717831969cf48bced350bdaf070cdeab06918d179429899e144358d/tifffile-2023.12.9-py3-none-any.whl", hash = "sha256:9b066e4b1a900891ea42ffd33dab8ba34c537935618b9893ddef42d7d422692f", size = 223627 }, ] [[package]] name = "tokenizers" -version = "0.22.0" +version = "0.21.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "huggingface-hub" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/b4/c1ce3699e81977da2ace8b16d2badfd42b060e7d33d75c4ccdbf9dc920fa/tokenizers-0.22.0.tar.gz", hash = "sha256:2e33b98525be8453f355927f3cab312c36cd3e44f4d7e9e97da2fa94d0a49dcb", size = 362771, upload-time = "2025-08-29T10:25:33.914Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/2f/402986d0823f8d7ca139d969af2917fefaa9b947d1fb32f6168c509f2492/tokenizers-0.21.4.tar.gz", hash = "sha256:fa23f85fbc9a02ec5c6978da172cdcbac23498c3ca9f3645c5c68740ac007880", size = 351253 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/b1/18c13648edabbe66baa85fe266a478a7931ddc0cd1ba618802eb7b8d9865/tokenizers-0.22.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:eaa9620122a3fb99b943f864af95ed14c8dfc0f47afa3b404ac8c16b3f2bb484", size = 3081954, upload-time = "2025-08-29T10:25:24.993Z" }, - { url = "https://files.pythonhosted.org/packages/c2/02/c3c454b641bd7c4f79e4464accfae9e7dfc913a777d2e561e168ae060362/tokenizers-0.22.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:71784b9ab5bf0ff3075bceeb198149d2c5e068549c0d18fe32d06ba0deb63f79", size = 2945644, upload-time = "2025-08-29T10:25:23.405Z" }, - { url = "https://files.pythonhosted.org/packages/55/02/d10185ba2fd8c2d111e124c9d92de398aee0264b35ce433f79fb8472f5d0/tokenizers-0.22.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec5b71f668a8076802b0241a42387d48289f25435b86b769ae1837cad4172a17", size = 3254764, upload-time = "2025-08-29T10:25:12.445Z" }, - { url = "https://files.pythonhosted.org/packages/13/89/17514bd7ef4bf5bfff58e2b131cec0f8d5cea2b1c8ffe1050a2c8de88dbb/tokenizers-0.22.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ea8562fa7498850d02a16178105b58803ea825b50dc9094d60549a7ed63654bb", size = 3161654, upload-time = "2025-08-29T10:25:15.493Z" }, - { url = "https://files.pythonhosted.org/packages/5a/d8/bac9f3a7ef6dcceec206e3857c3b61bb16c6b702ed7ae49585f5bd85c0ef/tokenizers-0.22.0-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4136e1558a9ef2e2f1de1555dcd573e1cbc4a320c1a06c4107a3d46dc8ac6e4b", size = 3511484, upload-time = "2025-08-29T10:25:20.477Z" }, - { url = "https://files.pythonhosted.org/packages/aa/27/9c9800eb6763683010a4851db4d1802d8cab9cec114c17056eccb4d4a6e0/tokenizers-0.22.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf5954de3962a5fd9781dc12048d24a1a6f1f5df038c6e95db328cd22964206", size = 3712829, upload-time = "2025-08-29T10:25:17.154Z" }, - { url = "https://files.pythonhosted.org/packages/10/e3/b1726dbc1f03f757260fa21752e1921445b5bc350389a8314dd3338836db/tokenizers-0.22.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8337ca75d0731fc4860e6204cc24bb36a67d9736142aa06ed320943b50b1e7ed", size = 3408934, upload-time = "2025-08-29T10:25:18.76Z" }, - { url = "https://files.pythonhosted.org/packages/d4/61/aeab3402c26874b74bb67a7f2c4b569dde29b51032c5384db592e7b216f4/tokenizers-0.22.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a89264e26f63c449d8cded9061adea7b5de53ba2346fc7e87311f7e4117c1cc8", size = 3345585, upload-time = "2025-08-29T10:25:22.08Z" }, - { url = "https://files.pythonhosted.org/packages/bc/d3/498b4a8a8764cce0900af1add0f176ff24f475d4413d55b760b8cdf00893/tokenizers-0.22.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:790bad50a1b59d4c21592f9c3cf5e5cf9c3c7ce7e1a23a739f13e01fb1be377a", size = 9322986, upload-time = "2025-08-29T10:25:26.607Z" }, - { url = "https://files.pythonhosted.org/packages/a2/62/92378eb1c2c565837ca3cb5f9569860d132ab9d195d7950c1ea2681dffd0/tokenizers-0.22.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:76cf6757c73a10ef10bf06fa937c0ec7393d90432f543f49adc8cab3fb6f26cb", size = 9276630, upload-time = "2025-08-29T10:25:28.349Z" }, - { url = "https://files.pythonhosted.org/packages/eb/f0/342d80457aa1cda7654327460f69db0d69405af1e4c453f4dc6ca7c4a76e/tokenizers-0.22.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:1626cb186e143720c62c6c6b5371e62bbc10af60481388c0da89bc903f37ea0c", size = 9547175, upload-time = "2025-08-29T10:25:29.989Z" }, - { url = "https://files.pythonhosted.org/packages/14/84/8aa9b4adfc4fbd09381e20a5bc6aa27040c9c09caa89988c01544e008d18/tokenizers-0.22.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:da589a61cbfea18ae267723d6b029b84598dc8ca78db9951d8f5beff72d8507c", size = 9692735, upload-time = "2025-08-29T10:25:32.089Z" }, - { url = "https://files.pythonhosted.org/packages/bf/24/83ee2b1dc76bfe05c3142e7d0ccdfe69f0ad2f1ebf6c726cea7f0874c0d0/tokenizers-0.22.0-cp39-abi3-win32.whl", hash = "sha256:dbf9d6851bddae3e046fedfb166f47743c1c7bd11c640f0691dd35ef0bcad3be", size = 2471915, upload-time = "2025-08-29T10:25:36.411Z" }, - { url = "https://files.pythonhosted.org/packages/d1/9b/0e0bf82214ee20231845b127aa4a8015936ad5a46779f30865d10e404167/tokenizers-0.22.0-cp39-abi3-win_amd64.whl", hash = "sha256:c78174859eeaee96021f248a56c801e36bfb6bd5b067f2e95aa82445ca324f00", size = 2680494, upload-time = "2025-08-29T10:25:35.14Z" }, + { url = "https://files.pythonhosted.org/packages/98/c6/fdb6f72bf6454f52eb4a2510be7fb0f614e541a2554d6210e370d85efff4/tokenizers-0.21.4-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:2ccc10a7c3bcefe0f242867dc914fc1226ee44321eb618cfe3019b5df3400133", size = 2863987 }, + { url = "https://files.pythonhosted.org/packages/8d/a6/28975479e35ddc751dc1ddc97b9b69bf7fcf074db31548aab37f8116674c/tokenizers-0.21.4-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:5e2f601a8e0cd5be5cc7506b20a79112370b9b3e9cb5f13f68ab11acd6ca7d60", size = 2732457 }, + { url = "https://files.pythonhosted.org/packages/aa/8f/24f39d7b5c726b7b0be95dca04f344df278a3fe3a4deb15a975d194cbb32/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b376f5a1aee67b4d29032ee85511bbd1b99007ec735f7f35c8a2eb104eade5", size = 3012624 }, + { url = "https://files.pythonhosted.org/packages/58/47/26358925717687a58cb74d7a508de96649544fad5778f0cd9827398dc499/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2107ad649e2cda4488d41dfd031469e9da3fcbfd6183e74e4958fa729ffbf9c6", size = 2939681 }, + { url = "https://files.pythonhosted.org/packages/99/6f/cc300fea5db2ab5ddc2c8aea5757a27b89c84469899710c3aeddc1d39801/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c73012da95afafdf235ba80047699df4384fdc481527448a078ffd00e45a7d9", size = 3247445 }, + { url = "https://files.pythonhosted.org/packages/be/bf/98cb4b9c3c4afd8be89cfa6423704337dc20b73eb4180397a6e0d456c334/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f23186c40395fc390d27f519679a58023f368a0aad234af145e0f39ad1212732", size = 3428014 }, + { url = "https://files.pythonhosted.org/packages/75/c7/96c1cc780e6ca7f01a57c13235dd05b7bc1c0f3588512ebe9d1331b5f5ae/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc88bb34e23a54cc42713d6d98af5f1bf79c07653d24fe984d2d695ba2c922a2", size = 3193197 }, + { url = "https://files.pythonhosted.org/packages/f2/90/273b6c7ec78af547694eddeea9e05de771278bd20476525ab930cecaf7d8/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51b7eabb104f46c1c50b486520555715457ae833d5aee9ff6ae853d1130506ff", size = 3115426 }, + { url = "https://files.pythonhosted.org/packages/91/43/c640d5a07e95f1cf9d2c92501f20a25f179ac53a4f71e1489a3dcfcc67ee/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:714b05b2e1af1288bd1bc56ce496c4cebb64a20d158ee802887757791191e6e2", size = 9089127 }, + { url = "https://files.pythonhosted.org/packages/44/a1/dd23edd6271d4dca788e5200a807b49ec3e6987815cd9d0a07ad9c96c7c2/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:1340ff877ceedfa937544b7d79f5b7becf33a4cfb58f89b3b49927004ef66f78", size = 9055243 }, + { url = "https://files.pythonhosted.org/packages/21/2b/b410d6e9021c4b7ddb57248304dc817c4d4970b73b6ee343674914701197/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:3c1f4317576e465ac9ef0d165b247825a2a4078bcd01cba6b54b867bdf9fdd8b", size = 9298237 }, + { url = "https://files.pythonhosted.org/packages/b7/0a/42348c995c67e2e6e5c89ffb9cfd68507cbaeb84ff39c49ee6e0a6dd0fd2/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:c212aa4e45ec0bb5274b16b6f31dd3f1c41944025c2358faaa5782c754e84c24", size = 9461980 }, + { url = "https://files.pythonhosted.org/packages/3d/d3/dacccd834404cd71b5c334882f3ba40331ad2120e69ded32cf5fda9a7436/tokenizers-0.21.4-cp39-abi3-win32.whl", hash = "sha256:6c42a930bc5f4c47f4ea775c91de47d27910881902b0f20e4990ebe045a415d0", size = 2329871 }, + { url = "https://files.pythonhosted.org/packages/41/f2/fd673d979185f5dcbac4be7d09461cbb99751554ffb6718d0013af8604cb/tokenizers-0.21.4-cp39-abi3-win_amd64.whl", hash = "sha256:475d807a5c3eb72c59ad9b5fcdb254f6e17f53dfcbb9903233b0dfa9c943b597", size = 2507568 }, ] [[package]] name = "tomli" version = "2.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f", size = 15164, upload-time = "2022-02-08T10:54:04.006Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f", size = 15164 } wheels = [ - { url = "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", size = 12757, upload-time = "2022-02-08T10:54:02.017Z" }, + { url = "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", size = 12757 }, ] [[package]] @@ -2942,18 +3082,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/03/00/6a9b3aedb0b60a80995ade30f718f1a9902612f22a1aaf531c85a02987f7/tqdm-4.66.3.tar.gz", hash = "sha256:23097a41eba115ba99ecae40d06444c15d1c0c698d527a01c6c8bd1c5d0647e5", size = 169551, upload-time = "2024-05-02T21:44:05.084Z" } +sdist = { url = "https://files.pythonhosted.org/packages/03/00/6a9b3aedb0b60a80995ade30f718f1a9902612f22a1aaf531c85a02987f7/tqdm-4.66.3.tar.gz", hash = "sha256:23097a41eba115ba99ecae40d06444c15d1c0c698d527a01c6c8bd1c5d0647e5", size = 169551 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/ad/7d47bbf2cae78ff79f29db0bed5016ec9c56b212a93fca624bb88b551a7c/tqdm-4.66.3-py3-none-any.whl", hash = "sha256:4f41d54107ff9a223dca80b53efe4fb654c67efaba7f47bada3ee9d50e05bd53", size = 78374, upload-time = "2024-05-02T21:44:01.541Z" }, + { url = "https://files.pythonhosted.org/packages/d1/ad/7d47bbf2cae78ff79f29db0bed5016ec9c56b212a93fca624bb88b551a7c/tqdm-4.66.3-py3-none-any.whl", hash = "sha256:4f41d54107ff9a223dca80b53efe4fb654c67efaba7f47bada3ee9d50e05bd53", size = 78374 }, ] [[package]] name = "types-pyyaml" version = "6.0.12.20250822" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/49/85/90a442e538359ab5c9e30de415006fb22567aa4301c908c09f19e42975c2/types_pyyaml-6.0.12.20250822.tar.gz", hash = "sha256:259f1d93079d335730a9db7cff2bcaf65d7e04b4a56b5927d49a612199b59413", size = 17481, upload-time = "2025-08-22T03:02:16.209Z" } +sdist = { url = "https://files.pythonhosted.org/packages/49/85/90a442e538359ab5c9e30de415006fb22567aa4301c908c09f19e42975c2/types_pyyaml-6.0.12.20250822.tar.gz", hash = "sha256:259f1d93079d335730a9db7cff2bcaf65d7e04b4a56b5927d49a612199b59413", size = 17481 } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/8e/8f0aca667c97c0d76024b37cffa39e76e2ce39ca54a38f285a64e6ae33ba/types_pyyaml-6.0.12.20250822-py3-none-any.whl", hash = "sha256:1fe1a5e146aa315483592d292b72a172b65b946a6d98aa6ddd8e4aa838ab7098", size = 20314, upload-time = "2025-08-22T03:02:15.002Z" }, + { url = "https://files.pythonhosted.org/packages/32/8e/8f0aca667c97c0d76024b37cffa39e76e2ce39ca54a38f285a64e6ae33ba/types_pyyaml-6.0.12.20250822-py3-none-any.whl", hash = "sha256:1fe1a5e146aa315483592d292b72a172b65b946a6d98aa6ddd8e4aa838ab7098", size = 20314 }, ] [[package]] @@ -2963,45 +3103,45 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ed/b0/9355adb86ec84d057fea765e4c49cce592aaf3d5117ce5609a95a7fc3dac/types_requests-2.32.4.20250809.tar.gz", hash = "sha256:d8060de1c8ee599311f56ff58010fb4902f462a1470802cf9f6ed27bc46c4df3", size = 23027, upload-time = "2025-08-09T03:17:10.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/b0/9355adb86ec84d057fea765e4c49cce592aaf3d5117ce5609a95a7fc3dac/types_requests-2.32.4.20250809.tar.gz", hash = "sha256:d8060de1c8ee599311f56ff58010fb4902f462a1470802cf9f6ed27bc46c4df3", size = 23027 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/6f/ec0012be842b1d888d46884ac5558fd62aeae1f0ec4f7a581433d890d4b5/types_requests-2.32.4.20250809-py3-none-any.whl", hash = "sha256:f73d1832fb519ece02c85b1f09d5f0dd3108938e7d47e7f94bbfa18a6782b163", size = 20644, upload-time = "2025-08-09T03:17:09.716Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6f/ec0012be842b1d888d46884ac5558fd62aeae1f0ec4f7a581433d890d4b5/types_requests-2.32.4.20250809-py3-none-any.whl", hash = "sha256:f73d1832fb519ece02c85b1f09d5f0dd3108938e7d47e7f94bbfa18a6782b163", size = 20644 }, ] [[package]] name = "types-setuptools" version = "80.9.0.20250822" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/bd/1e5f949b7cb740c9f0feaac430e301b8f1c5f11a81e26324299ea671a237/types_setuptools-80.9.0.20250822.tar.gz", hash = "sha256:070ea7716968ec67a84c7f7768d9952ff24d28b65b6594797a464f1b3066f965", size = 41296, upload-time = "2025-08-22T03:02:08.771Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/bd/1e5f949b7cb740c9f0feaac430e301b8f1c5f11a81e26324299ea671a237/types_setuptools-80.9.0.20250822.tar.gz", hash = "sha256:070ea7716968ec67a84c7f7768d9952ff24d28b65b6594797a464f1b3066f965", size = 41296 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/2d/475bf15c1cdc172e7a0d665b6e373ebfb1e9bf734d3f2f543d668b07a142/types_setuptools-80.9.0.20250822-py3-none-any.whl", hash = "sha256:53bf881cb9d7e46ed12c76ef76c0aaf28cfe6211d3fab12e0b83620b1a8642c3", size = 63179, upload-time = "2025-08-22T03:02:07.643Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2d/475bf15c1cdc172e7a0d665b6e373ebfb1e9bf734d3f2f543d668b07a142/types_setuptools-80.9.0.20250822-py3-none-any.whl", hash = "sha256:53bf881cb9d7e46ed12c76ef76c0aaf28cfe6211d3fab12e0b83620b1a8642c3", size = 63179 }, ] [[package]] name = "types-simplejson" version = "3.20.0.20250822" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/6b/96d43a90cd202bd552cdd871858a11c138fe5ef11aeb4ed8e8dc51389257/types_simplejson-3.20.0.20250822.tar.gz", hash = "sha256:2b0bfd57a6beed3b932fd2c3c7f8e2f48a7df3978c9bba43023a32b3741a95b0", size = 10608, upload-time = "2025-08-22T03:03:35.36Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/6b/96d43a90cd202bd552cdd871858a11c138fe5ef11aeb4ed8e8dc51389257/types_simplejson-3.20.0.20250822.tar.gz", hash = "sha256:2b0bfd57a6beed3b932fd2c3c7f8e2f48a7df3978c9bba43023a32b3741a95b0", size = 10608 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/9f/8e2c9e6aee9a2ff34f2ffce6ccd9c26edeef6dfd366fde611dc2e2c00ab9/types_simplejson-3.20.0.20250822-py3-none-any.whl", hash = "sha256:b5e63ae220ac7a1b0bb9af43b9cb8652237c947981b2708b0c776d3b5d8fa169", size = 10417, upload-time = "2025-08-22T03:03:34.485Z" }, + { url = "https://files.pythonhosted.org/packages/3c/9f/8e2c9e6aee9a2ff34f2ffce6ccd9c26edeef6dfd366fde611dc2e2c00ab9/types_simplejson-3.20.0.20250822-py3-none-any.whl", hash = "sha256:b5e63ae220ac7a1b0bb9af43b9cb8652237c947981b2708b0c776d3b5d8fa169", size = 10417 }, ] [[package]] name = "types-ujson" version = "5.10.0.20250822" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5c/bd/d372d44534f84864a96c19a7059d9b4d29db8541828b8b9dc3040f7a46d0/types_ujson-5.10.0.20250822.tar.gz", hash = "sha256:0a795558e1f78532373cf3f03f35b1f08bc60d52d924187b97995ee3597ba006", size = 8437, upload-time = "2025-08-22T03:02:19.433Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/bd/d372d44534f84864a96c19a7059d9b4d29db8541828b8b9dc3040f7a46d0/types_ujson-5.10.0.20250822.tar.gz", hash = "sha256:0a795558e1f78532373cf3f03f35b1f08bc60d52d924187b97995ee3597ba006", size = 8437 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/f2/d812543c350674d8b3f6e17c8922248ee3bb752c2a76f64beb8c538b40cf/types_ujson-5.10.0.20250822-py3-none-any.whl", hash = "sha256:3e9e73a6dc62ccc03449d9ac2c580cd1b7a8e4873220db498f7dd056754be080", size = 7657, upload-time = "2025-08-22T03:02:18.699Z" }, + { url = "https://files.pythonhosted.org/packages/d7/f2/d812543c350674d8b3f6e17c8922248ee3bb752c2a76f64beb8c538b40cf/types_ujson-5.10.0.20250822-py3-none-any.whl", hash = "sha256:3e9e73a6dc62ccc03449d9ac2c580cd1b7a8e4873220db498f7dd056754be080", size = 7657 }, ] [[package]] name = "typing-extensions" version = "4.12.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321, upload-time = "2024-06-07T18:52:15.995Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } wheels = [ - { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438, upload-time = "2024-06-07T18:52:13.582Z" }, + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, ] [[package]] @@ -3011,18 +3151,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222, upload-time = "2025-02-25T17:27:59.638Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125, upload-time = "2025-02-25T17:27:57.754Z" }, + { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 }, ] [[package]] name = "urllib3" version = "2.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/36/dd/a6b232f449e1bc71802a5b7950dc3675d32c6dbc2a1bd6d71f065551adb6/urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54", size = 263900, upload-time = "2023-11-13T12:29:45.049Z" } +sdist = { url = "https://files.pythonhosted.org/packages/36/dd/a6b232f449e1bc71802a5b7950dc3675d32c6dbc2a1bd6d71f065551adb6/urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54", size = 263900 } wheels = [ - { url = "https://files.pythonhosted.org/packages/96/94/c31f58c7a7f470d5665935262ebd7455c7e4c7782eb525658d3dbf4b9403/urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3", size = 104579, upload-time = "2023-11-13T12:29:42.719Z" }, + { url = "https://files.pythonhosted.org/packages/96/94/c31f58c7a7f470d5665935262ebd7455c7e4c7782eb525658d3dbf4b9403/urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3", size = 104579 }, ] [[package]] @@ -3034,9 +3174,9 @@ dependencies = [ { name = "h11" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473, upload-time = "2025-06-28T16:15:46.058Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, + { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406 }, ] [package.optional-dependencies] @@ -3054,26 +3194,26 @@ standard = [ name = "uvloop" version = "0.19.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9c/16/728cc5dde368e6eddb299c5aec4d10eaf25335a5af04e8c0abd68e2e9d32/uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd", size = 2318492, upload-time = "2023-10-22T22:03:57.665Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/16/728cc5dde368e6eddb299c5aec4d10eaf25335a5af04e8c0abd68e2e9d32/uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd", size = 2318492 } wheels = [ - { url = "https://files.pythonhosted.org/packages/36/c2/27bf858a576b1fa35b5c2c2029c8cec424a8789e87545ed2f25466d1f21d/uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e", size = 1443484, upload-time = "2023-10-22T22:02:54.169Z" }, - { url = "https://files.pythonhosted.org/packages/4e/35/05b6064b93f4113412d1fd92bdcb6018607e78ae94d1712e63e533f9b2fa/uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428", size = 793850, upload-time = "2023-10-22T22:02:56.311Z" }, - { url = "https://files.pythonhosted.org/packages/aa/56/b62ab4e10458ce96bb30c98d327c127f989d3bb4ef899e4c410c739f7ef6/uvloop-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8", size = 3418601, upload-time = "2023-10-22T22:02:58.717Z" }, - { url = "https://files.pythonhosted.org/packages/ab/ed/12729fba5e3b7e02ee70b3ea230b88e60a50375cf63300db22607694d2f0/uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849", size = 3416731, upload-time = "2023-10-22T22:03:01.043Z" }, - { url = "https://files.pythonhosted.org/packages/a2/23/80381a2d728d2a0c36e2eef202f5b77428990004d8fbdd3865558ff49fa5/uvloop-0.19.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957", size = 4128572, upload-time = "2023-10-22T22:03:02.874Z" }, - { url = "https://files.pythonhosted.org/packages/6b/23/1ee41a15e1ad15182e2bd12cbfd37bcb6802f01d6bbcaddf6ca136cbb308/uvloop-0.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd", size = 4129235, upload-time = "2023-10-22T22:03:05.361Z" }, - { url = "https://files.pythonhosted.org/packages/41/2a/608ad69f27f51280098abee440c33e921d3ad203e2c86f7262e241e49c99/uvloop-0.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef", size = 1357681, upload-time = "2023-10-22T22:03:07.158Z" }, - { url = "https://files.pythonhosted.org/packages/13/00/d0923d66d80c8717983493a4d7af747ce47f1c2147d82df057a846ba6bff/uvloop-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2", size = 746421, upload-time = "2023-10-22T22:03:09.4Z" }, - { url = "https://files.pythonhosted.org/packages/1f/c7/e494c367b0c6e6453f9bed5a78548f5b2ff49add36302cd915a91d347d88/uvloop-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1", size = 3481000, upload-time = "2023-10-22T22:03:11.755Z" }, - { url = "https://files.pythonhosted.org/packages/86/cc/1829b3f740e4cb1baefff8240a1c6fc8db9e3caac7b93169aec7d4386069/uvloop-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24", size = 3476361, upload-time = "2023-10-22T22:03:13.841Z" }, - { url = "https://files.pythonhosted.org/packages/7a/4c/ca87e8f5a30629ffa2038c20907c8ab455c5859ff10e810227b76e60d927/uvloop-0.19.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533", size = 4169571, upload-time = "2023-10-22T22:03:15.618Z" }, - { url = "https://files.pythonhosted.org/packages/d2/a9/f947a00c47b1c87c937cac2423243a41ba08f0fb76d04eb0d1d170606e0a/uvloop-0.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12", size = 4170459, upload-time = "2023-10-22T22:03:17.988Z" }, - { url = "https://files.pythonhosted.org/packages/85/57/6736733bb0e86a4b5380d04082463b289c0baecaa205934ba81e8a1d5ea4/uvloop-0.19.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650", size = 1355376, upload-time = "2023-10-22T22:03:20.075Z" }, - { url = "https://files.pythonhosted.org/packages/eb/0c/51339463da912ed34b48d470538d98a91660749b2db56902f23db9b42fdd/uvloop-0.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec", size = 745031, upload-time = "2023-10-22T22:03:21.404Z" }, - { url = "https://files.pythonhosted.org/packages/e6/fc/f0daaf19f5b2116a2d26eb9f98c4a45084aea87bf03c33bcca7aa1ff36e5/uvloop-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc", size = 4077630, upload-time = "2023-10-22T22:03:23.568Z" }, - { url = "https://files.pythonhosted.org/packages/fd/96/fdc318ffe82ae567592b213ec2fcd8ecedd927b5da068cf84d56b28c51a4/uvloop-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6", size = 4159957, upload-time = "2023-10-22T22:03:25.278Z" }, - { url = "https://files.pythonhosted.org/packages/71/bc/092068ae7fc16dcf20f3e389126ba7800cee75ffba83f78bf1d167aee3cd/uvloop-0.19.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593", size = 4014951, upload-time = "2023-10-22T22:03:27.055Z" }, - { url = "https://files.pythonhosted.org/packages/a6/f2/6ce1e73933eb038c89f929e26042e64b2cb8d4453410153eed14918ca9a8/uvloop-0.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3", size = 4100911, upload-time = "2023-10-22T22:03:29.39Z" }, + { url = "https://files.pythonhosted.org/packages/36/c2/27bf858a576b1fa35b5c2c2029c8cec424a8789e87545ed2f25466d1f21d/uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e", size = 1443484 }, + { url = "https://files.pythonhosted.org/packages/4e/35/05b6064b93f4113412d1fd92bdcb6018607e78ae94d1712e63e533f9b2fa/uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428", size = 793850 }, + { url = "https://files.pythonhosted.org/packages/aa/56/b62ab4e10458ce96bb30c98d327c127f989d3bb4ef899e4c410c739f7ef6/uvloop-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8", size = 3418601 }, + { url = "https://files.pythonhosted.org/packages/ab/ed/12729fba5e3b7e02ee70b3ea230b88e60a50375cf63300db22607694d2f0/uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849", size = 3416731 }, + { url = "https://files.pythonhosted.org/packages/a2/23/80381a2d728d2a0c36e2eef202f5b77428990004d8fbdd3865558ff49fa5/uvloop-0.19.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957", size = 4128572 }, + { url = "https://files.pythonhosted.org/packages/6b/23/1ee41a15e1ad15182e2bd12cbfd37bcb6802f01d6bbcaddf6ca136cbb308/uvloop-0.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd", size = 4129235 }, + { url = "https://files.pythonhosted.org/packages/41/2a/608ad69f27f51280098abee440c33e921d3ad203e2c86f7262e241e49c99/uvloop-0.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef", size = 1357681 }, + { url = "https://files.pythonhosted.org/packages/13/00/d0923d66d80c8717983493a4d7af747ce47f1c2147d82df057a846ba6bff/uvloop-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2", size = 746421 }, + { url = "https://files.pythonhosted.org/packages/1f/c7/e494c367b0c6e6453f9bed5a78548f5b2ff49add36302cd915a91d347d88/uvloop-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1", size = 3481000 }, + { url = "https://files.pythonhosted.org/packages/86/cc/1829b3f740e4cb1baefff8240a1c6fc8db9e3caac7b93169aec7d4386069/uvloop-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24", size = 3476361 }, + { url = "https://files.pythonhosted.org/packages/7a/4c/ca87e8f5a30629ffa2038c20907c8ab455c5859ff10e810227b76e60d927/uvloop-0.19.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533", size = 4169571 }, + { url = "https://files.pythonhosted.org/packages/d2/a9/f947a00c47b1c87c937cac2423243a41ba08f0fb76d04eb0d1d170606e0a/uvloop-0.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12", size = 4170459 }, + { url = "https://files.pythonhosted.org/packages/85/57/6736733bb0e86a4b5380d04082463b289c0baecaa205934ba81e8a1d5ea4/uvloop-0.19.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650", size = 1355376 }, + { url = "https://files.pythonhosted.org/packages/eb/0c/51339463da912ed34b48d470538d98a91660749b2db56902f23db9b42fdd/uvloop-0.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec", size = 745031 }, + { url = "https://files.pythonhosted.org/packages/e6/fc/f0daaf19f5b2116a2d26eb9f98c4a45084aea87bf03c33bcca7aa1ff36e5/uvloop-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc", size = 4077630 }, + { url = "https://files.pythonhosted.org/packages/fd/96/fdc318ffe82ae567592b213ec2fcd8ecedd927b5da068cf84d56b28c51a4/uvloop-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6", size = 4159957 }, + { url = "https://files.pythonhosted.org/packages/71/bc/092068ae7fc16dcf20f3e389126ba7800cee75ffba83f78bf1d167aee3cd/uvloop-0.19.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593", size = 4014951 }, + { url = "https://files.pythonhosted.org/packages/a6/f2/6ce1e73933eb038c89f929e26042e64b2cb8d4453410153eed14918ca9a8/uvloop-0.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3", size = 4100911 }, ] [[package]] @@ -3083,115 +3223,115 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/79/0ee412e1228aaf6f9568aa180b43cb482472de52560fbd7c283c786534af/watchfiles-0.21.0.tar.gz", hash = "sha256:c76c635fabf542bb78524905718c39f736a98e5ab25b23ec6d4abede1a85a6a3", size = 37098, upload-time = "2023-10-13T13:06:39.809Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/79/0ee412e1228aaf6f9568aa180b43cb482472de52560fbd7c283c786534af/watchfiles-0.21.0.tar.gz", hash = "sha256:c76c635fabf542bb78524905718c39f736a98e5ab25b23ec6d4abede1a85a6a3", size = 37098 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/85/ea2a035b7d86bf0a29ee1c32bc2df8ad4da77e6602806e679d9735ff28cb/watchfiles-0.21.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:27b4035013f1ea49c6c0b42d983133b136637a527e48c132d368eb19bf1ac6aa", size = 428182, upload-time = "2023-10-13T13:04:34.803Z" }, - { url = "https://files.pythonhosted.org/packages/b5/e5/240e5eb3ff0ee3da3b028ac5be2019c407bdd0f3fdb02bd75fdf3bd10aff/watchfiles-0.21.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c81818595eff6e92535ff32825f31c116f867f64ff8cdf6562cd1d6b2e1e8f3e", size = 418275, upload-time = "2023-10-13T13:04:36.632Z" }, - { url = "https://files.pythonhosted.org/packages/5b/79/ecd0dfb04443a1900cd3952d7ea6493bf655c2db9a0d3736a5d98a15da39/watchfiles-0.21.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6c107ea3cf2bd07199d66f156e3ea756d1b84dfd43b542b2d870b77868c98c03", size = 1379785, upload-time = "2023-10-13T13:04:38.641Z" }, - { url = "https://files.pythonhosted.org/packages/41/0e/3333b986b1889bb71f0e44b3fac0591824a679619b8b8ddd70ff8858edc4/watchfiles-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d9ac347653ebd95839a7c607608703b20bc07e577e870d824fa4801bc1cb124", size = 1349374, upload-time = "2023-10-13T13:04:41.711Z" }, - { url = "https://files.pythonhosted.org/packages/18/c4/ad5ad16cad900a29aaa792e0ed121ff70d76f74062b051661090d88c6dfd/watchfiles-0.21.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5eb86c6acb498208e7663ca22dbe68ca2cf42ab5bf1c776670a50919a56e64ab", size = 1348033, upload-time = "2023-10-13T13:04:43.324Z" }, - { url = "https://files.pythonhosted.org/packages/4e/d2/769254ff04ba88ceb179a6e892606ac4da17338eb010e85ca7a9c3339234/watchfiles-0.21.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f564bf68404144ea6b87a78a3f910cc8de216c6b12a4cf0b27718bf4ec38d303", size = 1464393, upload-time = "2023-10-13T13:04:44.818Z" }, - { url = "https://files.pythonhosted.org/packages/14/d0/662800e778ca20e7664dd5df57751aa79ef18b6abb92224b03c8c2e852a6/watchfiles-0.21.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d0f32ebfaa9c6011f8454994f86108c2eb9c79b8b7de00b36d558cadcedaa3d", size = 1542953, upload-time = "2023-10-13T13:04:46.714Z" }, - { url = "https://files.pythonhosted.org/packages/f7/4b/b90dcdc3bbaf3bb2db733e1beea2d01566b601c15fcf8e71dfcc8686c097/watchfiles-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d45d9b699ecbac6c7bd8e0a2609767491540403610962968d258fd6405c17c", size = 1346961, upload-time = "2023-10-13T13:04:48.072Z" }, - { url = "https://files.pythonhosted.org/packages/92/ff/75cc1b30c5abcad13a2a72e75625ec619c7a393028a111d7d24dba578d5e/watchfiles-0.21.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:aff06b2cac3ef4616e26ba17a9c250c1fe9dd8a5d907d0193f84c499b1b6e6a9", size = 1464393, upload-time = "2023-10-13T13:04:49.638Z" }, - { url = "https://files.pythonhosted.org/packages/9a/65/12cbeb363bf220482a559c48107edfd87f09248f55e1ac315a36c2098a0f/watchfiles-0.21.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d9792dff410f266051025ecfaa927078b94cc7478954b06796a9756ccc7e14a9", size = 1463409, upload-time = "2023-10-13T13:04:51.762Z" }, - { url = "https://files.pythonhosted.org/packages/f2/08/92e28867c66f0d9638bb131feca739057efc48dbcd391fd7f0a55507e470/watchfiles-0.21.0-cp310-none-win32.whl", hash = "sha256:214cee7f9e09150d4fb42e24919a1e74d8c9b8a9306ed1474ecaddcd5479c293", size = 268101, upload-time = "2023-10-13T13:04:53.78Z" }, - { url = "https://files.pythonhosted.org/packages/4b/ea/80527adf1ad51488a96fc201715730af5879f4dfeccb5e2069ff82d890d4/watchfiles-0.21.0-cp310-none-win_amd64.whl", hash = "sha256:1ad7247d79f9f55bb25ab1778fd47f32d70cf36053941f07de0b7c4e96b5d235", size = 279675, upload-time = "2023-10-13T13:04:55.113Z" }, - { url = "https://files.pythonhosted.org/packages/57/b9/2667286003dd305b81d3a3aa824d3dfc63dacbf2a96faae09e72d953c430/watchfiles-0.21.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:668c265d90de8ae914f860d3eeb164534ba2e836811f91fecc7050416ee70aa7", size = 428210, upload-time = "2023-10-13T13:04:56.894Z" }, - { url = "https://files.pythonhosted.org/packages/a3/87/6793ac60d2e20c9c1883aec7431c2e7b501ee44a839f6da1b747c13baa23/watchfiles-0.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a23092a992e61c3a6a70f350a56db7197242f3490da9c87b500f389b2d01eef", size = 418196, upload-time = "2023-10-13T13:04:58.19Z" }, - { url = "https://files.pythonhosted.org/packages/5d/12/e1d1d220c5b99196eea38c9a878964f30a2b55ec9d72fd713191725b35e8/watchfiles-0.21.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e7941bbcfdded9c26b0bf720cb7e6fd803d95a55d2c14b4bd1f6a2772230c586", size = 1380287, upload-time = "2023-10-13T13:04:59.923Z" }, - { url = "https://files.pythonhosted.org/packages/0e/cf/126f0a8683f326d190c3539a769e45e747a80a5fcbf797de82e738c946ae/watchfiles-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11cd0c3100e2233e9c53106265da31d574355c288e15259c0d40a4405cbae317", size = 1349653, upload-time = "2023-10-13T13:05:01.622Z" }, - { url = "https://files.pythonhosted.org/packages/20/6e/6cffd795ec65dbc82f15d95b73d3042c1ddaffc4dd25f6c8240bfcf0640f/watchfiles-0.21.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78f30cbe8b2ce770160d3c08cff01b2ae9306fe66ce899b73f0409dc1846c1b", size = 1348844, upload-time = "2023-10-13T13:05:03.805Z" }, - { url = "https://files.pythonhosted.org/packages/d5/2a/f9633279d8937ad84c532997405dd106fa6100e8d2b83e364f1c87561f96/watchfiles-0.21.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6674b00b9756b0af620aa2a3346b01f8e2a3dc729d25617e1b89cf6af4a54eb1", size = 1464343, upload-time = "2023-10-13T13:05:05.248Z" }, - { url = "https://files.pythonhosted.org/packages/d7/49/9b2199bbf3c89e7c8dd795fced9dac29f201be8a28a5df0c8ff625737df6/watchfiles-0.21.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd7ac678b92b29ba630d8c842d8ad6c555abda1b9ef044d6cc092dacbfc9719d", size = 1542858, upload-time = "2023-10-13T13:05:06.791Z" }, - { url = "https://files.pythonhosted.org/packages/35/e0/e8a9c1fe30e98c5b3507ad381abc4d9ee2c3b9c0ae62ffe9c164a5838186/watchfiles-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c873345680c1b87f1e09e0eaf8cf6c891b9851d8b4d3645e7efe2ec20a20cc7", size = 1347464, upload-time = "2023-10-13T13:05:08.622Z" }, - { url = "https://files.pythonhosted.org/packages/ba/66/873739dd7defdfaee4b880114de9463fae18ba13ae2ddd784806b0ee33b6/watchfiles-0.21.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:49f56e6ecc2503e7dbe233fa328b2be1a7797d31548e7a193237dcdf1ad0eee0", size = 1464343, upload-time = "2023-10-13T13:05:10.584Z" }, - { url = "https://files.pythonhosted.org/packages/bd/51/d7539aa258d8f0e5d7b870af8b9b8964b4f88a1e4517eeb8a2efb838e9b3/watchfiles-0.21.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:02d91cbac553a3ad141db016e3350b03184deaafeba09b9d6439826ee594b365", size = 1463338, upload-time = "2023-10-13T13:05:12.671Z" }, - { url = "https://files.pythonhosted.org/packages/ee/92/219c539a2a93b6870fa7b84eace946983126b20a7e15c6c034d8d0472682/watchfiles-0.21.0-cp311-none-win32.whl", hash = "sha256:ebe684d7d26239e23d102a2bad2a358dedf18e462e8808778703427d1f584400", size = 267658, upload-time = "2023-10-13T13:05:13.972Z" }, - { url = "https://files.pythonhosted.org/packages/f3/dc/2a8a447b783f5059c4bf7a6bad8fe59375a5a9ce872774763b25c21c2860/watchfiles-0.21.0-cp311-none-win_amd64.whl", hash = "sha256:4566006aa44cb0d21b8ab53baf4b9c667a0ed23efe4aaad8c227bfba0bf15cbe", size = 280113, upload-time = "2023-10-13T13:05:15.289Z" }, - { url = "https://files.pythonhosted.org/packages/22/15/e4085181cf0210a6ec6eb29fee0c6088de867ee33d81555076a4a2726e8b/watchfiles-0.21.0-cp311-none-win_arm64.whl", hash = "sha256:c550a56bf209a3d987d5a975cdf2063b3389a5d16caf29db4bdddeae49f22078", size = 268688, upload-time = "2023-10-13T13:05:17.144Z" }, - { url = "https://files.pythonhosted.org/packages/a1/fd/2f009eb17809afd32a143b442856628585c9ce3a9c6d5c1841e44e35a16c/watchfiles-0.21.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:51ddac60b96a42c15d24fbdc7a4bfcd02b5a29c047b7f8bf63d3f6f5a860949a", size = 426902, upload-time = "2023-10-13T13:05:18.828Z" }, - { url = "https://files.pythonhosted.org/packages/e0/62/a2605f212a136e06f2d056ee7491ede9935ba0f1d5ceafd1f7da2a0c8625/watchfiles-0.21.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:511f0b034120cd1989932bf1e9081aa9fb00f1f949fbd2d9cab6264916ae89b1", size = 417300, upload-time = "2023-10-13T13:05:20.116Z" }, - { url = "https://files.pythonhosted.org/packages/69/0e/29f158fa22eb2cc1f188b5ec20fb5c0a64eb801e3901ad5b7ad546cbaed0/watchfiles-0.21.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cfb92d49dbb95ec7a07511bc9efb0faff8fe24ef3805662b8d6808ba8409a71a", size = 1378126, upload-time = "2023-10-13T13:05:21.508Z" }, - { url = "https://files.pythonhosted.org/packages/e8/f3/c67865cb5a174201c52d34e870cc7956b8408ee83ce9a02909d6a2a93a14/watchfiles-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f92944efc564867bbf841c823c8b71bb0be75e06b8ce45c084b46411475a915", size = 1348275, upload-time = "2023-10-13T13:05:22.995Z" }, - { url = "https://files.pythonhosted.org/packages/d7/eb/b6f1184d1c7b9670f5bd1e184e4c221ecf25fd817cf2fcac6adc387882b5/watchfiles-0.21.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:642d66b75eda909fd1112d35c53816d59789a4b38c141a96d62f50a3ef9b3360", size = 1347255, upload-time = "2023-10-13T13:05:24.618Z" }, - { url = "https://files.pythonhosted.org/packages/c8/27/e534e4b3fe739f4bf8bd5dc4c26cbc5d3baa427125d8ef78a6556acd6ff4/watchfiles-0.21.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d23bcd6c8eaa6324fe109d8cac01b41fe9a54b8c498af9ce464c1aeeb99903d6", size = 1462845, upload-time = "2023-10-13T13:05:26.531Z" }, - { url = "https://files.pythonhosted.org/packages/b0/ba/a0d1c1c55f75e7e47c8f79f2314f7ec670b5177596f6d27764aecc7048cd/watchfiles-0.21.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18d5b4da8cf3e41895b34e8c37d13c9ed294954907929aacd95153508d5d89d7", size = 1528957, upload-time = "2023-10-13T13:05:28.365Z" }, - { url = "https://files.pythonhosted.org/packages/1c/3a/4e38518c4dff58090c01fc8cc051fa08ac9ae00b361c855075809b0058ce/watchfiles-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b8d1eae0f65441963d805f766c7e9cd092f91e0c600c820c764a4ff71a0764c", size = 1345542, upload-time = "2023-10-13T13:05:29.862Z" }, - { url = "https://files.pythonhosted.org/packages/9f/b7/783097f8137a710d5cd9ccbfcd92e4b453d38dab05cfcb5dbd2c587752e5/watchfiles-0.21.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1fd9a5205139f3c6bb60d11f6072e0552f0a20b712c85f43d42342d162be1235", size = 1462238, upload-time = "2023-10-13T13:05:32.245Z" }, - { url = "https://files.pythonhosted.org/packages/6b/4c/b741eb38f2c408ae9c5a25235f6506b1dda43486ae0fdb4c462ef75bce11/watchfiles-0.21.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a1e3014a625bcf107fbf38eece0e47fa0190e52e45dc6eee5a8265ddc6dc5ea7", size = 1462406, upload-time = "2023-10-13T13:05:34.339Z" }, - { url = "https://files.pythonhosted.org/packages/77/e4/8d2b3c67364671b0e1c0ce383895a5415f45ecb3e8586982deff4a8e85c9/watchfiles-0.21.0-cp312-none-win32.whl", hash = "sha256:9d09869f2c5a6f2d9df50ce3064b3391d3ecb6dced708ad64467b9e4f2c9bef3", size = 266789, upload-time = "2023-10-13T13:05:35.606Z" }, - { url = "https://files.pythonhosted.org/packages/da/f2/6b1de38aeb21eb9dac1ae6a1ee4521566e79690117032036c737cfab52fa/watchfiles-0.21.0-cp312-none-win_amd64.whl", hash = "sha256:18722b50783b5e30a18a8a5db3006bab146d2b705c92eb9a94f78c72beb94094", size = 280292, upload-time = "2023-10-13T13:05:37.357Z" }, - { url = "https://files.pythonhosted.org/packages/5a/a5/7aba9435beb863c2490bae3173a45f42044ac7a48155d3dd42ab49cfae45/watchfiles-0.21.0-cp312-none-win_arm64.whl", hash = "sha256:a3b9bec9579a15fb3ca2d9878deae789df72f2b0fdaf90ad49ee389cad5edab6", size = 268026, upload-time = "2023-10-13T13:05:38.591Z" }, - { url = "https://files.pythonhosted.org/packages/62/66/7463ceb43eabc6deaa795c7969ff4d4fd938de54e655035483dfd1e97c84/watchfiles-0.21.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:ab03a90b305d2588e8352168e8c5a1520b721d2d367f31e9332c4235b30b8994", size = 429092, upload-time = "2023-10-13T13:06:21.419Z" }, - { url = "https://files.pythonhosted.org/packages/fe/a3/42686af3a089f34aba35c39abac852869661938dae7025c1a0580dfe0fbf/watchfiles-0.21.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:927c589500f9f41e370b0125c12ac9e7d3a2fd166b89e9ee2828b3dda20bfe6f", size = 419188, upload-time = "2023-10-13T13:06:22.934Z" }, - { url = "https://files.pythonhosted.org/packages/37/17/4825999346f15d650f4c69093efa64fb040fbff4f706a20e8c4745f64070/watchfiles-0.21.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bd467213195e76f838caf2c28cd65e58302d0254e636e7c0fca81efa4a2e62c", size = 1350366, upload-time = "2023-10-13T13:06:24.254Z" }, - { url = "https://files.pythonhosted.org/packages/70/76/8d124e14cf51af4d6bba926c7473f253c6efd1539ba62577f079a2d71537/watchfiles-0.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02b73130687bc3f6bb79d8a170959042eb56eb3a42df3671c79b428cd73f17cc", size = 1346270, upload-time = "2023-10-13T13:06:25.742Z" }, + { url = "https://files.pythonhosted.org/packages/6e/85/ea2a035b7d86bf0a29ee1c32bc2df8ad4da77e6602806e679d9735ff28cb/watchfiles-0.21.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:27b4035013f1ea49c6c0b42d983133b136637a527e48c132d368eb19bf1ac6aa", size = 428182 }, + { url = "https://files.pythonhosted.org/packages/b5/e5/240e5eb3ff0ee3da3b028ac5be2019c407bdd0f3fdb02bd75fdf3bd10aff/watchfiles-0.21.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c81818595eff6e92535ff32825f31c116f867f64ff8cdf6562cd1d6b2e1e8f3e", size = 418275 }, + { url = "https://files.pythonhosted.org/packages/5b/79/ecd0dfb04443a1900cd3952d7ea6493bf655c2db9a0d3736a5d98a15da39/watchfiles-0.21.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6c107ea3cf2bd07199d66f156e3ea756d1b84dfd43b542b2d870b77868c98c03", size = 1379785 }, + { url = "https://files.pythonhosted.org/packages/41/0e/3333b986b1889bb71f0e44b3fac0591824a679619b8b8ddd70ff8858edc4/watchfiles-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d9ac347653ebd95839a7c607608703b20bc07e577e870d824fa4801bc1cb124", size = 1349374 }, + { url = "https://files.pythonhosted.org/packages/18/c4/ad5ad16cad900a29aaa792e0ed121ff70d76f74062b051661090d88c6dfd/watchfiles-0.21.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5eb86c6acb498208e7663ca22dbe68ca2cf42ab5bf1c776670a50919a56e64ab", size = 1348033 }, + { url = "https://files.pythonhosted.org/packages/4e/d2/769254ff04ba88ceb179a6e892606ac4da17338eb010e85ca7a9c3339234/watchfiles-0.21.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f564bf68404144ea6b87a78a3f910cc8de216c6b12a4cf0b27718bf4ec38d303", size = 1464393 }, + { url = "https://files.pythonhosted.org/packages/14/d0/662800e778ca20e7664dd5df57751aa79ef18b6abb92224b03c8c2e852a6/watchfiles-0.21.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d0f32ebfaa9c6011f8454994f86108c2eb9c79b8b7de00b36d558cadcedaa3d", size = 1542953 }, + { url = "https://files.pythonhosted.org/packages/f7/4b/b90dcdc3bbaf3bb2db733e1beea2d01566b601c15fcf8e71dfcc8686c097/watchfiles-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d45d9b699ecbac6c7bd8e0a2609767491540403610962968d258fd6405c17c", size = 1346961 }, + { url = "https://files.pythonhosted.org/packages/92/ff/75cc1b30c5abcad13a2a72e75625ec619c7a393028a111d7d24dba578d5e/watchfiles-0.21.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:aff06b2cac3ef4616e26ba17a9c250c1fe9dd8a5d907d0193f84c499b1b6e6a9", size = 1464393 }, + { url = "https://files.pythonhosted.org/packages/9a/65/12cbeb363bf220482a559c48107edfd87f09248f55e1ac315a36c2098a0f/watchfiles-0.21.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d9792dff410f266051025ecfaa927078b94cc7478954b06796a9756ccc7e14a9", size = 1463409 }, + { url = "https://files.pythonhosted.org/packages/f2/08/92e28867c66f0d9638bb131feca739057efc48dbcd391fd7f0a55507e470/watchfiles-0.21.0-cp310-none-win32.whl", hash = "sha256:214cee7f9e09150d4fb42e24919a1e74d8c9b8a9306ed1474ecaddcd5479c293", size = 268101 }, + { url = "https://files.pythonhosted.org/packages/4b/ea/80527adf1ad51488a96fc201715730af5879f4dfeccb5e2069ff82d890d4/watchfiles-0.21.0-cp310-none-win_amd64.whl", hash = "sha256:1ad7247d79f9f55bb25ab1778fd47f32d70cf36053941f07de0b7c4e96b5d235", size = 279675 }, + { url = "https://files.pythonhosted.org/packages/57/b9/2667286003dd305b81d3a3aa824d3dfc63dacbf2a96faae09e72d953c430/watchfiles-0.21.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:668c265d90de8ae914f860d3eeb164534ba2e836811f91fecc7050416ee70aa7", size = 428210 }, + { url = "https://files.pythonhosted.org/packages/a3/87/6793ac60d2e20c9c1883aec7431c2e7b501ee44a839f6da1b747c13baa23/watchfiles-0.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a23092a992e61c3a6a70f350a56db7197242f3490da9c87b500f389b2d01eef", size = 418196 }, + { url = "https://files.pythonhosted.org/packages/5d/12/e1d1d220c5b99196eea38c9a878964f30a2b55ec9d72fd713191725b35e8/watchfiles-0.21.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e7941bbcfdded9c26b0bf720cb7e6fd803d95a55d2c14b4bd1f6a2772230c586", size = 1380287 }, + { url = "https://files.pythonhosted.org/packages/0e/cf/126f0a8683f326d190c3539a769e45e747a80a5fcbf797de82e738c946ae/watchfiles-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11cd0c3100e2233e9c53106265da31d574355c288e15259c0d40a4405cbae317", size = 1349653 }, + { url = "https://files.pythonhosted.org/packages/20/6e/6cffd795ec65dbc82f15d95b73d3042c1ddaffc4dd25f6c8240bfcf0640f/watchfiles-0.21.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78f30cbe8b2ce770160d3c08cff01b2ae9306fe66ce899b73f0409dc1846c1b", size = 1348844 }, + { url = "https://files.pythonhosted.org/packages/d5/2a/f9633279d8937ad84c532997405dd106fa6100e8d2b83e364f1c87561f96/watchfiles-0.21.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6674b00b9756b0af620aa2a3346b01f8e2a3dc729d25617e1b89cf6af4a54eb1", size = 1464343 }, + { url = "https://files.pythonhosted.org/packages/d7/49/9b2199bbf3c89e7c8dd795fced9dac29f201be8a28a5df0c8ff625737df6/watchfiles-0.21.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd7ac678b92b29ba630d8c842d8ad6c555abda1b9ef044d6cc092dacbfc9719d", size = 1542858 }, + { url = "https://files.pythonhosted.org/packages/35/e0/e8a9c1fe30e98c5b3507ad381abc4d9ee2c3b9c0ae62ffe9c164a5838186/watchfiles-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c873345680c1b87f1e09e0eaf8cf6c891b9851d8b4d3645e7efe2ec20a20cc7", size = 1347464 }, + { url = "https://files.pythonhosted.org/packages/ba/66/873739dd7defdfaee4b880114de9463fae18ba13ae2ddd784806b0ee33b6/watchfiles-0.21.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:49f56e6ecc2503e7dbe233fa328b2be1a7797d31548e7a193237dcdf1ad0eee0", size = 1464343 }, + { url = "https://files.pythonhosted.org/packages/bd/51/d7539aa258d8f0e5d7b870af8b9b8964b4f88a1e4517eeb8a2efb838e9b3/watchfiles-0.21.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:02d91cbac553a3ad141db016e3350b03184deaafeba09b9d6439826ee594b365", size = 1463338 }, + { url = "https://files.pythonhosted.org/packages/ee/92/219c539a2a93b6870fa7b84eace946983126b20a7e15c6c034d8d0472682/watchfiles-0.21.0-cp311-none-win32.whl", hash = "sha256:ebe684d7d26239e23d102a2bad2a358dedf18e462e8808778703427d1f584400", size = 267658 }, + { url = "https://files.pythonhosted.org/packages/f3/dc/2a8a447b783f5059c4bf7a6bad8fe59375a5a9ce872774763b25c21c2860/watchfiles-0.21.0-cp311-none-win_amd64.whl", hash = "sha256:4566006aa44cb0d21b8ab53baf4b9c667a0ed23efe4aaad8c227bfba0bf15cbe", size = 280113 }, + { url = "https://files.pythonhosted.org/packages/22/15/e4085181cf0210a6ec6eb29fee0c6088de867ee33d81555076a4a2726e8b/watchfiles-0.21.0-cp311-none-win_arm64.whl", hash = "sha256:c550a56bf209a3d987d5a975cdf2063b3389a5d16caf29db4bdddeae49f22078", size = 268688 }, + { url = "https://files.pythonhosted.org/packages/a1/fd/2f009eb17809afd32a143b442856628585c9ce3a9c6d5c1841e44e35a16c/watchfiles-0.21.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:51ddac60b96a42c15d24fbdc7a4bfcd02b5a29c047b7f8bf63d3f6f5a860949a", size = 426902 }, + { url = "https://files.pythonhosted.org/packages/e0/62/a2605f212a136e06f2d056ee7491ede9935ba0f1d5ceafd1f7da2a0c8625/watchfiles-0.21.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:511f0b034120cd1989932bf1e9081aa9fb00f1f949fbd2d9cab6264916ae89b1", size = 417300 }, + { url = "https://files.pythonhosted.org/packages/69/0e/29f158fa22eb2cc1f188b5ec20fb5c0a64eb801e3901ad5b7ad546cbaed0/watchfiles-0.21.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cfb92d49dbb95ec7a07511bc9efb0faff8fe24ef3805662b8d6808ba8409a71a", size = 1378126 }, + { url = "https://files.pythonhosted.org/packages/e8/f3/c67865cb5a174201c52d34e870cc7956b8408ee83ce9a02909d6a2a93a14/watchfiles-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f92944efc564867bbf841c823c8b71bb0be75e06b8ce45c084b46411475a915", size = 1348275 }, + { url = "https://files.pythonhosted.org/packages/d7/eb/b6f1184d1c7b9670f5bd1e184e4c221ecf25fd817cf2fcac6adc387882b5/watchfiles-0.21.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:642d66b75eda909fd1112d35c53816d59789a4b38c141a96d62f50a3ef9b3360", size = 1347255 }, + { url = "https://files.pythonhosted.org/packages/c8/27/e534e4b3fe739f4bf8bd5dc4c26cbc5d3baa427125d8ef78a6556acd6ff4/watchfiles-0.21.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d23bcd6c8eaa6324fe109d8cac01b41fe9a54b8c498af9ce464c1aeeb99903d6", size = 1462845 }, + { url = "https://files.pythonhosted.org/packages/b0/ba/a0d1c1c55f75e7e47c8f79f2314f7ec670b5177596f6d27764aecc7048cd/watchfiles-0.21.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18d5b4da8cf3e41895b34e8c37d13c9ed294954907929aacd95153508d5d89d7", size = 1528957 }, + { url = "https://files.pythonhosted.org/packages/1c/3a/4e38518c4dff58090c01fc8cc051fa08ac9ae00b361c855075809b0058ce/watchfiles-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b8d1eae0f65441963d805f766c7e9cd092f91e0c600c820c764a4ff71a0764c", size = 1345542 }, + { url = "https://files.pythonhosted.org/packages/9f/b7/783097f8137a710d5cd9ccbfcd92e4b453d38dab05cfcb5dbd2c587752e5/watchfiles-0.21.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1fd9a5205139f3c6bb60d11f6072e0552f0a20b712c85f43d42342d162be1235", size = 1462238 }, + { url = "https://files.pythonhosted.org/packages/6b/4c/b741eb38f2c408ae9c5a25235f6506b1dda43486ae0fdb4c462ef75bce11/watchfiles-0.21.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a1e3014a625bcf107fbf38eece0e47fa0190e52e45dc6eee5a8265ddc6dc5ea7", size = 1462406 }, + { url = "https://files.pythonhosted.org/packages/77/e4/8d2b3c67364671b0e1c0ce383895a5415f45ecb3e8586982deff4a8e85c9/watchfiles-0.21.0-cp312-none-win32.whl", hash = "sha256:9d09869f2c5a6f2d9df50ce3064b3391d3ecb6dced708ad64467b9e4f2c9bef3", size = 266789 }, + { url = "https://files.pythonhosted.org/packages/da/f2/6b1de38aeb21eb9dac1ae6a1ee4521566e79690117032036c737cfab52fa/watchfiles-0.21.0-cp312-none-win_amd64.whl", hash = "sha256:18722b50783b5e30a18a8a5db3006bab146d2b705c92eb9a94f78c72beb94094", size = 280292 }, + { url = "https://files.pythonhosted.org/packages/5a/a5/7aba9435beb863c2490bae3173a45f42044ac7a48155d3dd42ab49cfae45/watchfiles-0.21.0-cp312-none-win_arm64.whl", hash = "sha256:a3b9bec9579a15fb3ca2d9878deae789df72f2b0fdaf90ad49ee389cad5edab6", size = 268026 }, + { url = "https://files.pythonhosted.org/packages/62/66/7463ceb43eabc6deaa795c7969ff4d4fd938de54e655035483dfd1e97c84/watchfiles-0.21.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:ab03a90b305d2588e8352168e8c5a1520b721d2d367f31e9332c4235b30b8994", size = 429092 }, + { url = "https://files.pythonhosted.org/packages/fe/a3/42686af3a089f34aba35c39abac852869661938dae7025c1a0580dfe0fbf/watchfiles-0.21.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:927c589500f9f41e370b0125c12ac9e7d3a2fd166b89e9ee2828b3dda20bfe6f", size = 419188 }, + { url = "https://files.pythonhosted.org/packages/37/17/4825999346f15d650f4c69093efa64fb040fbff4f706a20e8c4745f64070/watchfiles-0.21.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bd467213195e76f838caf2c28cd65e58302d0254e636e7c0fca81efa4a2e62c", size = 1350366 }, + { url = "https://files.pythonhosted.org/packages/70/76/8d124e14cf51af4d6bba926c7473f253c6efd1539ba62577f079a2d71537/watchfiles-0.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02b73130687bc3f6bb79d8a170959042eb56eb3a42df3671c79b428cd73f17cc", size = 1346270 }, ] [[package]] name = "wcwidth" version = "0.2.13" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, ] [[package]] name = "websocket-client" version = "1.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648, upload-time = "2024-04-23T22:16:16.976Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826, upload-time = "2024-04-23T22:16:14.422Z" }, + { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826 }, ] [[package]] name = "websockets" version = "12.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2e/62/7a7874b7285413c954a4cca3c11fd851f11b2fe5b4ae2d9bee4f6d9bdb10/websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b", size = 104994, upload-time = "2023-10-21T14:21:11.88Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/62/7a7874b7285413c954a4cca3c11fd851f11b2fe5b4ae2d9bee4f6d9bdb10/websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b", size = 104994 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/b9/360b86ded0920a93bff0db4e4b0aa31370b0208ca240b2e98d62aad8d082/websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374", size = 124025, upload-time = "2023-10-21T14:19:28.387Z" }, - { url = "https://files.pythonhosted.org/packages/bb/d3/1eca0d8fb6f0665c96f0dc7c0d0ec8aa1a425e8c003e0c18e1451f65d177/websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be", size = 121261, upload-time = "2023-10-21T14:19:30.203Z" }, - { url = "https://files.pythonhosted.org/packages/4e/e1/f6c3ecf7f1bfd9209e13949db027d7fdea2faf090c69b5f2d17d1d796d96/websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547", size = 121328, upload-time = "2023-10-21T14:19:31.765Z" }, - { url = "https://files.pythonhosted.org/packages/74/4d/f88eeceb23cb587c4aeca779e3f356cf54817af2368cb7f2bd41f93c8360/websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2", size = 130925, upload-time = "2023-10-21T14:19:33.36Z" }, - { url = "https://files.pythonhosted.org/packages/16/17/f63d9ee6ffd9afbeea021d5950d6e8db84cd4aead306c6c2ca523805699e/websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558", size = 129930, upload-time = "2023-10-21T14:19:35.109Z" }, - { url = "https://files.pythonhosted.org/packages/9a/12/c7a7504f5bf74d6ee0533f6fc7d30d8f4b79420ab179d1df2484b07602eb/websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480", size = 130245, upload-time = "2023-10-21T14:19:36.761Z" }, - { url = "https://files.pythonhosted.org/packages/e4/6a/3600c7771eb31116d2e77383d7345618b37bb93709d041e328c08e2a8eb3/websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c", size = 134966, upload-time = "2023-10-21T14:19:38.481Z" }, - { url = "https://files.pythonhosted.org/packages/22/26/df77c4b7538caebb78c9b97f43169ef742a4f445e032a5ea1aaef88f8f46/websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8", size = 134196, upload-time = "2023-10-21T14:19:40.264Z" }, - { url = "https://files.pythonhosted.org/packages/e5/18/18ce9a4a08203c8d0d3d561e3ea4f453daf32f099601fc831e60c8a9b0f2/websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603", size = 134822, upload-time = "2023-10-21T14:19:41.836Z" }, - { url = "https://files.pythonhosted.org/packages/45/51/1f823a341fc20a880e67ae62f6c38c4880a24a4b60fbe544a38f516f39a1/websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f", size = 124454, upload-time = "2023-10-21T14:19:43.639Z" }, - { url = "https://files.pythonhosted.org/packages/41/b0/5ec054cfcf23adfc88d39359b85e81d043af8a141e3ac8ce40f45a5ce5f4/websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf", size = 124974, upload-time = "2023-10-21T14:19:44.934Z" }, - { url = "https://files.pythonhosted.org/packages/02/73/9c1e168a2e7fdf26841dc98f5f5502e91dea47428da7690a08101f616169/websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4", size = 124047, upload-time = "2023-10-21T14:19:46.519Z" }, - { url = "https://files.pythonhosted.org/packages/e4/2d/9a683359ad2ed11b2303a7a94800db19c61d33fa3bde271df09e99936022/websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f", size = 121282, upload-time = "2023-10-21T14:19:47.739Z" }, - { url = "https://files.pythonhosted.org/packages/95/aa/75fa3b893142d6d98a48cb461169bd268141f2da8bfca97392d6462a02eb/websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3", size = 121325, upload-time = "2023-10-21T14:19:49.4Z" }, - { url = "https://files.pythonhosted.org/packages/6e/a4/51a25e591d645df71ee0dc3a2c880b28e5514c00ce752f98a40a87abcd1e/websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c", size = 131502, upload-time = "2023-10-21T14:19:50.683Z" }, - { url = "https://files.pythonhosted.org/packages/cd/ea/0ceeea4f5b87398fe2d9f5bcecfa00a1bcd542e2bfcac2f2e5dd612c4e9e/websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45", size = 130491, upload-time = "2023-10-21T14:19:51.835Z" }, - { url = "https://files.pythonhosted.org/packages/e3/05/f52a60b66d9faf07a4f7d71dc056bffafe36a7e98c4eb5b78f04fe6e4e85/websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04", size = 130872, upload-time = "2023-10-21T14:19:53.071Z" }, - { url = "https://files.pythonhosted.org/packages/ac/4e/c7361b2d7b964c40fea924d64881145164961fcd6c90b88b7e3ab2c4f431/websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447", size = 136318, upload-time = "2023-10-21T14:19:54.41Z" }, - { url = "https://files.pythonhosted.org/packages/0a/31/337bf35ae5faeaf364c9cddec66681cdf51dc4414ee7a20f92a18e57880f/websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca", size = 135594, upload-time = "2023-10-21T14:19:55.982Z" }, - { url = "https://files.pythonhosted.org/packages/95/aa/1ac767825c96f9d7e43c4c95683757d4ef28cf11fa47a69aca42428d3e3a/websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53", size = 136191, upload-time = "2023-10-21T14:19:57.349Z" }, - { url = "https://files.pythonhosted.org/packages/28/4b/344ec5cfeb6bc417da097f8253607c3aed11d9a305fb58346f506bf556d8/websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402", size = 124453, upload-time = "2023-10-21T14:19:59.11Z" }, - { url = "https://files.pythonhosted.org/packages/d1/40/6b169cd1957476374f51f4486a3e85003149e62a14e6b78a958c2222337a/websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b", size = 124971, upload-time = "2023-10-21T14:20:00.243Z" }, - { url = "https://files.pythonhosted.org/packages/a9/6d/23cc898647c8a614a0d9ca703695dd04322fb5135096a20c2684b7c852b6/websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df", size = 124061, upload-time = "2023-10-21T14:20:02.221Z" }, - { url = "https://files.pythonhosted.org/packages/39/34/364f30fdf1a375e4002a26ee3061138d1571dfda6421126127d379d13930/websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc", size = 121296, upload-time = "2023-10-21T14:20:03.591Z" }, - { url = "https://files.pythonhosted.org/packages/2e/00/96ae1c9dcb3bc316ef683f2febd8c97dde9f254dc36c3afc65c7645f734c/websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b", size = 121326, upload-time = "2023-10-21T14:20:04.956Z" }, - { url = "https://files.pythonhosted.org/packages/af/f1/bba1e64430685dd456c1a1fd6b0c791ae33104967b928aefeff261761e8d/websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb", size = 131807, upload-time = "2023-10-21T14:20:06.153Z" }, - { url = "https://files.pythonhosted.org/packages/62/3b/98ee269712f37d892b93852ce07b3e6d7653160ca4c0d4f8c8663f8021f8/websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92", size = 130751, upload-time = "2023-10-21T14:20:07.753Z" }, - { url = "https://files.pythonhosted.org/packages/f1/00/d6f01ca2b191f8b0808e4132ccd2e7691f0453cbd7d0f72330eb97453c3a/websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed", size = 131176, upload-time = "2023-10-21T14:20:09.212Z" }, - { url = "https://files.pythonhosted.org/packages/af/9c/703ff3cd8109dcdee6152bae055d852ebaa7750117760ded697ab836cbcf/websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5", size = 136246, upload-time = "2023-10-21T14:20:10.423Z" }, - { url = "https://files.pythonhosted.org/packages/0b/a5/1a38fb85a456b9dc874ec984f3ff34f6550eafd17a3da28753cd3c1628e8/websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2", size = 135466, upload-time = "2023-10-21T14:20:11.826Z" }, - { url = "https://files.pythonhosted.org/packages/3c/98/1261f289dff7e65a38d59d2f591de6ed0a2580b729aebddec033c4d10881/websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113", size = 136083, upload-time = "2023-10-21T14:20:13.451Z" }, - { url = "https://files.pythonhosted.org/packages/a9/1c/f68769fba63ccb9c13fe0a25b616bd5aebeef1c7ddebc2ccc32462fb784d/websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d", size = 124460, upload-time = "2023-10-21T14:20:14.719Z" }, - { url = "https://files.pythonhosted.org/packages/20/52/8915f51f9aaef4e4361c89dd6cf69f72a0159f14e0d25026c81b6ad22525/websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f", size = 124985, upload-time = "2023-10-21T14:20:15.817Z" }, - { url = "https://files.pythonhosted.org/packages/43/8b/554a8a8bb6da9dd1ce04c44125e2192af7b7beebf6e3dbfa5d0e285cc20f/websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd", size = 121110, upload-time = "2023-10-21T14:20:48.335Z" }, - { url = "https://files.pythonhosted.org/packages/b0/8e/58b8812940d746ad74d395fb069497255cb5ef50748dfab1e8b386b1f339/websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870", size = 123216, upload-time = "2023-10-21T14:20:50.083Z" }, - { url = "https://files.pythonhosted.org/packages/81/ee/272cb67ace1786ce6d9f39d47b3c55b335e8b75dd1972a7967aad39178b6/websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077", size = 122821, upload-time = "2023-10-21T14:20:51.237Z" }, - { url = "https://files.pythonhosted.org/packages/a8/03/387fc902b397729df166763e336f4e5cec09fe7b9d60f442542c94a21be1/websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b", size = 122768, upload-time = "2023-10-21T14:20:52.59Z" }, - { url = "https://files.pythonhosted.org/packages/50/f0/5939fbc9bc1979d79a774ce5b7c4b33c0cefe99af22fb70f7462d0919640/websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30", size = 125009, upload-time = "2023-10-21T14:20:54.419Z" }, - { url = "https://files.pythonhosted.org/packages/79/4d/9cc401e7b07e80532ebc8c8e993f42541534da9e9249c59ee0139dcb0352/websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e", size = 118370, upload-time = "2023-10-21T14:21:10.075Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b9/360b86ded0920a93bff0db4e4b0aa31370b0208ca240b2e98d62aad8d082/websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374", size = 124025 }, + { url = "https://files.pythonhosted.org/packages/bb/d3/1eca0d8fb6f0665c96f0dc7c0d0ec8aa1a425e8c003e0c18e1451f65d177/websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be", size = 121261 }, + { url = "https://files.pythonhosted.org/packages/4e/e1/f6c3ecf7f1bfd9209e13949db027d7fdea2faf090c69b5f2d17d1d796d96/websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547", size = 121328 }, + { url = "https://files.pythonhosted.org/packages/74/4d/f88eeceb23cb587c4aeca779e3f356cf54817af2368cb7f2bd41f93c8360/websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2", size = 130925 }, + { url = "https://files.pythonhosted.org/packages/16/17/f63d9ee6ffd9afbeea021d5950d6e8db84cd4aead306c6c2ca523805699e/websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558", size = 129930 }, + { url = "https://files.pythonhosted.org/packages/9a/12/c7a7504f5bf74d6ee0533f6fc7d30d8f4b79420ab179d1df2484b07602eb/websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480", size = 130245 }, + { url = "https://files.pythonhosted.org/packages/e4/6a/3600c7771eb31116d2e77383d7345618b37bb93709d041e328c08e2a8eb3/websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c", size = 134966 }, + { url = "https://files.pythonhosted.org/packages/22/26/df77c4b7538caebb78c9b97f43169ef742a4f445e032a5ea1aaef88f8f46/websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8", size = 134196 }, + { url = "https://files.pythonhosted.org/packages/e5/18/18ce9a4a08203c8d0d3d561e3ea4f453daf32f099601fc831e60c8a9b0f2/websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603", size = 134822 }, + { url = "https://files.pythonhosted.org/packages/45/51/1f823a341fc20a880e67ae62f6c38c4880a24a4b60fbe544a38f516f39a1/websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f", size = 124454 }, + { url = "https://files.pythonhosted.org/packages/41/b0/5ec054cfcf23adfc88d39359b85e81d043af8a141e3ac8ce40f45a5ce5f4/websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf", size = 124974 }, + { url = "https://files.pythonhosted.org/packages/02/73/9c1e168a2e7fdf26841dc98f5f5502e91dea47428da7690a08101f616169/websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4", size = 124047 }, + { url = "https://files.pythonhosted.org/packages/e4/2d/9a683359ad2ed11b2303a7a94800db19c61d33fa3bde271df09e99936022/websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f", size = 121282 }, + { url = "https://files.pythonhosted.org/packages/95/aa/75fa3b893142d6d98a48cb461169bd268141f2da8bfca97392d6462a02eb/websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3", size = 121325 }, + { url = "https://files.pythonhosted.org/packages/6e/a4/51a25e591d645df71ee0dc3a2c880b28e5514c00ce752f98a40a87abcd1e/websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c", size = 131502 }, + { url = "https://files.pythonhosted.org/packages/cd/ea/0ceeea4f5b87398fe2d9f5bcecfa00a1bcd542e2bfcac2f2e5dd612c4e9e/websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45", size = 130491 }, + { url = "https://files.pythonhosted.org/packages/e3/05/f52a60b66d9faf07a4f7d71dc056bffafe36a7e98c4eb5b78f04fe6e4e85/websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04", size = 130872 }, + { url = "https://files.pythonhosted.org/packages/ac/4e/c7361b2d7b964c40fea924d64881145164961fcd6c90b88b7e3ab2c4f431/websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447", size = 136318 }, + { url = "https://files.pythonhosted.org/packages/0a/31/337bf35ae5faeaf364c9cddec66681cdf51dc4414ee7a20f92a18e57880f/websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca", size = 135594 }, + { url = "https://files.pythonhosted.org/packages/95/aa/1ac767825c96f9d7e43c4c95683757d4ef28cf11fa47a69aca42428d3e3a/websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53", size = 136191 }, + { url = "https://files.pythonhosted.org/packages/28/4b/344ec5cfeb6bc417da097f8253607c3aed11d9a305fb58346f506bf556d8/websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402", size = 124453 }, + { url = "https://files.pythonhosted.org/packages/d1/40/6b169cd1957476374f51f4486a3e85003149e62a14e6b78a958c2222337a/websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b", size = 124971 }, + { url = "https://files.pythonhosted.org/packages/a9/6d/23cc898647c8a614a0d9ca703695dd04322fb5135096a20c2684b7c852b6/websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df", size = 124061 }, + { url = "https://files.pythonhosted.org/packages/39/34/364f30fdf1a375e4002a26ee3061138d1571dfda6421126127d379d13930/websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc", size = 121296 }, + { url = "https://files.pythonhosted.org/packages/2e/00/96ae1c9dcb3bc316ef683f2febd8c97dde9f254dc36c3afc65c7645f734c/websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b", size = 121326 }, + { url = "https://files.pythonhosted.org/packages/af/f1/bba1e64430685dd456c1a1fd6b0c791ae33104967b928aefeff261761e8d/websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb", size = 131807 }, + { url = "https://files.pythonhosted.org/packages/62/3b/98ee269712f37d892b93852ce07b3e6d7653160ca4c0d4f8c8663f8021f8/websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92", size = 130751 }, + { url = "https://files.pythonhosted.org/packages/f1/00/d6f01ca2b191f8b0808e4132ccd2e7691f0453cbd7d0f72330eb97453c3a/websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed", size = 131176 }, + { url = "https://files.pythonhosted.org/packages/af/9c/703ff3cd8109dcdee6152bae055d852ebaa7750117760ded697ab836cbcf/websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5", size = 136246 }, + { url = "https://files.pythonhosted.org/packages/0b/a5/1a38fb85a456b9dc874ec984f3ff34f6550eafd17a3da28753cd3c1628e8/websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2", size = 135466 }, + { url = "https://files.pythonhosted.org/packages/3c/98/1261f289dff7e65a38d59d2f591de6ed0a2580b729aebddec033c4d10881/websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113", size = 136083 }, + { url = "https://files.pythonhosted.org/packages/a9/1c/f68769fba63ccb9c13fe0a25b616bd5aebeef1c7ddebc2ccc32462fb784d/websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d", size = 124460 }, + { url = "https://files.pythonhosted.org/packages/20/52/8915f51f9aaef4e4361c89dd6cf69f72a0159f14e0d25026c81b6ad22525/websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f", size = 124985 }, + { url = "https://files.pythonhosted.org/packages/43/8b/554a8a8bb6da9dd1ce04c44125e2192af7b7beebf6e3dbfa5d0e285cc20f/websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd", size = 121110 }, + { url = "https://files.pythonhosted.org/packages/b0/8e/58b8812940d746ad74d395fb069497255cb5ef50748dfab1e8b386b1f339/websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870", size = 123216 }, + { url = "https://files.pythonhosted.org/packages/81/ee/272cb67ace1786ce6d9f39d47b3c55b335e8b75dd1972a7967aad39178b6/websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077", size = 122821 }, + { url = "https://files.pythonhosted.org/packages/a8/03/387fc902b397729df166763e336f4e5cec09fe7b9d60f442542c94a21be1/websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b", size = 122768 }, + { url = "https://files.pythonhosted.org/packages/50/f0/5939fbc9bc1979d79a774ce5b7c4b33c0cefe99af22fb70f7462d0919640/websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30", size = 125009 }, + { url = "https://files.pythonhosted.org/packages/79/4d/9cc401e7b07e80532ebc8c8e993f42541534da9e9249c59ee0139dcb0352/websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e", size = 118370 }, ] [[package]] @@ -3201,9 +3341,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/02/51/2e0fc149e7a810d300422ab543f87f2bcf64d985eb6f1228c4efd6e4f8d4/werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18", size = 803342, upload-time = "2024-05-05T23:10:31.999Z" } +sdist = { url = "https://files.pythonhosted.org/packages/02/51/2e0fc149e7a810d300422ab543f87f2bcf64d985eb6f1228c4efd6e4f8d4/werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18", size = 803342 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/6e/e792999e816d19d7fcbfa94c730936750036d65656a76a5a688b57a656c4/werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8", size = 227274, upload-time = "2024-05-05T23:10:29.567Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6e/e792999e816d19d7fcbfa94c730936750036d65656a76a5a688b57a656c4/werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8", size = 227274 }, ] [[package]] @@ -3213,9 +3353,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/4a/44d3c295350d776427904d73c189e10aeae66d7f555bb2feee16d1e4ba5a/wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", size = 53425, upload-time = "2022-08-23T19:58:21.447Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/4a/44d3c295350d776427904d73c189e10aeae66d7f555bb2feee16d1e4ba5a/wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", size = 53425 } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736", size = 24226, upload-time = "2022-08-23T19:58:19.96Z" }, + { url = "https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736", size = 24226 }, ] [[package]] @@ -3225,9 +3365,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "setuptools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/46/c2/427f1867bb96555d1d34342f1dd97f8c420966ab564d58d18469a1db8736/zope.event-5.0.tar.gz", hash = "sha256:bac440d8d9891b4068e2b5a2c5e2c9765a9df762944bda6955f96bb9b91e67cd", size = 17350, upload-time = "2023-06-23T06:28:35.709Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/c2/427f1867bb96555d1d34342f1dd97f8c420966ab564d58d18469a1db8736/zope.event-5.0.tar.gz", hash = "sha256:bac440d8d9891b4068e2b5a2c5e2c9765a9df762944bda6955f96bb9b91e67cd", size = 17350 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/42/f8dbc2b9ad59e927940325a22d6d3931d630c3644dae7e2369ef5d9ba230/zope.event-5.0-py3-none-any.whl", hash = "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26", size = 6824, upload-time = "2023-06-23T06:28:32.652Z" }, + { url = "https://files.pythonhosted.org/packages/fe/42/f8dbc2b9ad59e927940325a22d6d3931d630c3644dae7e2369ef5d9ba230/zope.event-5.0-py3-none-any.whl", hash = "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26", size = 6824 }, ] [[package]] @@ -3237,24 +3377,24 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "setuptools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/87/03/6b85c1df2dca1b9acca38b423d1e226d8ffdf30ebd78bcb398c511de8b54/zope.interface-6.1.tar.gz", hash = "sha256:2fdc7ccbd6eb6b7df5353012fbed6c3c5d04ceaca0038f75e601060e95345309", size = 293914, upload-time = "2023-10-05T11:24:38.943Z" } +sdist = { url = "https://files.pythonhosted.org/packages/87/03/6b85c1df2dca1b9acca38b423d1e226d8ffdf30ebd78bcb398c511de8b54/zope.interface-6.1.tar.gz", hash = "sha256:2fdc7ccbd6eb6b7df5353012fbed6c3c5d04ceaca0038f75e601060e95345309", size = 293914 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/ec/c1e7ce928dc10bfe02c6da7e964342d941aaf168f96f8084636167ea50d2/zope.interface-6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:43b576c34ef0c1f5a4981163b551a8781896f2a37f71b8655fd20b5af0386abb", size = 202417, upload-time = "2023-10-05T11:24:25.141Z" }, - { url = "https://files.pythonhosted.org/packages/f7/0b/12f269ad049fc40a7a3ab85445d7855b6bc6f1e774c5ca9dd6f5c32becb3/zope.interface-6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:67be3ca75012c6e9b109860820a8b6c9a84bfb036fbd1076246b98e56951ca92", size = 202528, upload-time = "2023-10-05T11:24:27.336Z" }, - { url = "https://files.pythonhosted.org/packages/7f/85/3a35144509eb4a5a2208b48ae8d116a969d67de62cc6513d85602144d9cd/zope.interface-6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b9bc671626281f6045ad61d93a60f52fd5e8209b1610972cf0ef1bbe6d808e3", size = 247532, upload-time = "2023-10-05T11:49:20.587Z" }, - { url = "https://files.pythonhosted.org/packages/50/d6/6176aaa1f6588378f5a5a4a9c6ad50a36824e902b2f844ca8de7f1b0c4a7/zope.interface-6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbe81def9cf3e46f16ce01d9bfd8bea595e06505e51b7baf45115c77352675fd", size = 241703, upload-time = "2023-10-05T11:25:33.542Z" }, - { url = "https://files.pythonhosted.org/packages/4f/20/94d4f221989b4bbdd09004b2afb329958e776b7015b7ea8bc915327e195a/zope.interface-6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dc998f6de015723196a904045e5a2217f3590b62ea31990672e31fbc5370b41", size = 247078, upload-time = "2023-10-05T11:25:48.235Z" }, - { url = "https://files.pythonhosted.org/packages/97/7e/b790b4ab9605010816a91df26a715f163e228d60eb36c947c3118fb65190/zope.interface-6.1-cp310-cp310-win_amd64.whl", hash = "sha256:239a4a08525c080ff833560171d23b249f7f4d17fcbf9316ef4159f44997616f", size = 204155, upload-time = "2023-10-05T11:37:56.715Z" }, - { url = "https://files.pythonhosted.org/packages/4a/0b/1d8817b8a3631384a26ff7faa4c1f3e6726f7e4950c3442721cfef2c95eb/zope.interface-6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9ffdaa5290422ac0f1688cb8adb1b94ca56cee3ad11f29f2ae301df8aecba7d1", size = 202441, upload-time = "2023-10-05T11:24:20.414Z" }, - { url = "https://files.pythonhosted.org/packages/3e/1f/43557bb2b6e8537002a5a26af9b899171e26ddfcdf17a00ff729b00c036b/zope.interface-6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34c15ca9248f2e095ef2e93af2d633358c5f048c49fbfddf5fdfc47d5e263736", size = 202530, upload-time = "2023-10-05T11:24:22.975Z" }, - { url = "https://files.pythonhosted.org/packages/37/a1/5d2b265f4b7371630cad5873d0873965e35ca3de993d11b9336c720f7259/zope.interface-6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b012d023b4fb59183909b45d7f97fb493ef7a46d2838a5e716e3155081894605", size = 249584, upload-time = "2023-10-05T11:49:22.978Z" }, - { url = "https://files.pythonhosted.org/packages/8b/6d/547bfa7465e5b296adba0aff5c7ace1150f2a9e429fbf6c33d6618275162/zope.interface-6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:97806e9ca3651588c1baaebb8d0c5ee3db95430b612db354c199b57378312ee8", size = 243737, upload-time = "2023-10-05T11:25:35.439Z" }, - { url = "https://files.pythonhosted.org/packages/db/5f/46946b588c43eb28efe0e46f4cf455b1ed8b2d1ea62a21b0001c6610662f/zope.interface-6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fddbab55a2473f1d3b8833ec6b7ac31e8211b0aa608df5ab09ce07f3727326de", size = 249104, upload-time = "2023-10-05T11:25:51.355Z" }, - { url = "https://files.pythonhosted.org/packages/6c/9c/9d3c0e7e5362ea59da3c42b3b2b9fc073db433a0fe3bc6cae0809ccec395/zope.interface-6.1-cp311-cp311-win_amd64.whl", hash = "sha256:a0da79117952a9a41253696ed3e8b560a425197d4e41634a23b1507efe3273f1", size = 204155, upload-time = "2023-10-05T11:39:39.139Z" }, - { url = "https://files.pythonhosted.org/packages/3c/91/68a0bbc97c2554f87d39572091954e94d043bcd83897cd6a779ca85cb5cc/zope.interface-6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8bb9c990ca9027b4214fa543fd4025818dc95f8b7abce79d61dc8a2112b561a", size = 202757, upload-time = "2023-10-05T11:25:05.865Z" }, - { url = "https://files.pythonhosted.org/packages/e1/84/850092a8ab7e87a3ea615daf3f822f7196c52592e3e92f264621b4cfe5a2/zope.interface-6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b51b64432eed4c0744241e9ce5c70dcfecac866dff720e746d0a9c82f371dfa7", size = 202654, upload-time = "2023-10-05T11:25:08.347Z" }, - { url = "https://files.pythonhosted.org/packages/57/23/508f7f79619ae4e025f5b264a9283efc3c805ed4c0ad75cb28c091179ced/zope.interface-6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa6fd016e9644406d0a61313e50348c706e911dca29736a3266fc9e28ec4ca6d", size = 254400, upload-time = "2023-10-05T11:49:25.326Z" }, - { url = "https://files.pythonhosted.org/packages/7c/0d/db0ccf0d12767015f23b302aebe98d5eca218aaadc70c2e3908b85fecd2a/zope.interface-6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c8cf55261e15590065039696607f6c9c1aeda700ceee40c70478552d323b3ff", size = 248853, upload-time = "2023-10-05T11:25:37.37Z" }, - { url = "https://files.pythonhosted.org/packages/fd/4f/8e80173ebcdefe0ff4164444c22b171cf8bd72533026befc2adf079f3ac8/zope.interface-6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e30506bcb03de8983f78884807e4fd95d8db6e65b69257eea05d13d519b83ac0", size = 255127, upload-time = "2023-10-05T11:25:53.819Z" }, - { url = "https://files.pythonhosted.org/packages/0f/d5/81f9789311d9773a02ed048af7452fc6cedce059748dba956c1dc040340a/zope.interface-6.1-cp312-cp312-win_amd64.whl", hash = "sha256:e33e86fd65f369f10608b08729c8f1c92ec7e0e485964670b4d2633a4812d36b", size = 204268, upload-time = "2023-10-05T11:41:22.778Z" }, + { url = "https://files.pythonhosted.org/packages/3c/ec/c1e7ce928dc10bfe02c6da7e964342d941aaf168f96f8084636167ea50d2/zope.interface-6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:43b576c34ef0c1f5a4981163b551a8781896f2a37f71b8655fd20b5af0386abb", size = 202417 }, + { url = "https://files.pythonhosted.org/packages/f7/0b/12f269ad049fc40a7a3ab85445d7855b6bc6f1e774c5ca9dd6f5c32becb3/zope.interface-6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:67be3ca75012c6e9b109860820a8b6c9a84bfb036fbd1076246b98e56951ca92", size = 202528 }, + { url = "https://files.pythonhosted.org/packages/7f/85/3a35144509eb4a5a2208b48ae8d116a969d67de62cc6513d85602144d9cd/zope.interface-6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b9bc671626281f6045ad61d93a60f52fd5e8209b1610972cf0ef1bbe6d808e3", size = 247532 }, + { url = "https://files.pythonhosted.org/packages/50/d6/6176aaa1f6588378f5a5a4a9c6ad50a36824e902b2f844ca8de7f1b0c4a7/zope.interface-6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbe81def9cf3e46f16ce01d9bfd8bea595e06505e51b7baf45115c77352675fd", size = 241703 }, + { url = "https://files.pythonhosted.org/packages/4f/20/94d4f221989b4bbdd09004b2afb329958e776b7015b7ea8bc915327e195a/zope.interface-6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dc998f6de015723196a904045e5a2217f3590b62ea31990672e31fbc5370b41", size = 247078 }, + { url = "https://files.pythonhosted.org/packages/97/7e/b790b4ab9605010816a91df26a715f163e228d60eb36c947c3118fb65190/zope.interface-6.1-cp310-cp310-win_amd64.whl", hash = "sha256:239a4a08525c080ff833560171d23b249f7f4d17fcbf9316ef4159f44997616f", size = 204155 }, + { url = "https://files.pythonhosted.org/packages/4a/0b/1d8817b8a3631384a26ff7faa4c1f3e6726f7e4950c3442721cfef2c95eb/zope.interface-6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9ffdaa5290422ac0f1688cb8adb1b94ca56cee3ad11f29f2ae301df8aecba7d1", size = 202441 }, + { url = "https://files.pythonhosted.org/packages/3e/1f/43557bb2b6e8537002a5a26af9b899171e26ddfcdf17a00ff729b00c036b/zope.interface-6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34c15ca9248f2e095ef2e93af2d633358c5f048c49fbfddf5fdfc47d5e263736", size = 202530 }, + { url = "https://files.pythonhosted.org/packages/37/a1/5d2b265f4b7371630cad5873d0873965e35ca3de993d11b9336c720f7259/zope.interface-6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b012d023b4fb59183909b45d7f97fb493ef7a46d2838a5e716e3155081894605", size = 249584 }, + { url = "https://files.pythonhosted.org/packages/8b/6d/547bfa7465e5b296adba0aff5c7ace1150f2a9e429fbf6c33d6618275162/zope.interface-6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:97806e9ca3651588c1baaebb8d0c5ee3db95430b612db354c199b57378312ee8", size = 243737 }, + { url = "https://files.pythonhosted.org/packages/db/5f/46946b588c43eb28efe0e46f4cf455b1ed8b2d1ea62a21b0001c6610662f/zope.interface-6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fddbab55a2473f1d3b8833ec6b7ac31e8211b0aa608df5ab09ce07f3727326de", size = 249104 }, + { url = "https://files.pythonhosted.org/packages/6c/9c/9d3c0e7e5362ea59da3c42b3b2b9fc073db433a0fe3bc6cae0809ccec395/zope.interface-6.1-cp311-cp311-win_amd64.whl", hash = "sha256:a0da79117952a9a41253696ed3e8b560a425197d4e41634a23b1507efe3273f1", size = 204155 }, + { url = "https://files.pythonhosted.org/packages/3c/91/68a0bbc97c2554f87d39572091954e94d043bcd83897cd6a779ca85cb5cc/zope.interface-6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8bb9c990ca9027b4214fa543fd4025818dc95f8b7abce79d61dc8a2112b561a", size = 202757 }, + { url = "https://files.pythonhosted.org/packages/e1/84/850092a8ab7e87a3ea615daf3f822f7196c52592e3e92f264621b4cfe5a2/zope.interface-6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b51b64432eed4c0744241e9ce5c70dcfecac866dff720e746d0a9c82f371dfa7", size = 202654 }, + { url = "https://files.pythonhosted.org/packages/57/23/508f7f79619ae4e025f5b264a9283efc3c805ed4c0ad75cb28c091179ced/zope.interface-6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa6fd016e9644406d0a61313e50348c706e911dca29736a3266fc9e28ec4ca6d", size = 254400 }, + { url = "https://files.pythonhosted.org/packages/7c/0d/db0ccf0d12767015f23b302aebe98d5eca218aaadc70c2e3908b85fecd2a/zope.interface-6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c8cf55261e15590065039696607f6c9c1aeda700ceee40c70478552d323b3ff", size = 248853 }, + { url = "https://files.pythonhosted.org/packages/fd/4f/8e80173ebcdefe0ff4164444c22b171cf8bd72533026befc2adf079f3ac8/zope.interface-6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e30506bcb03de8983f78884807e4fd95d8db6e65b69257eea05d13d519b83ac0", size = 255127 }, + { url = "https://files.pythonhosted.org/packages/0f/d5/81f9789311d9773a02ed048af7452fc6cedce059748dba956c1dc040340a/zope.interface-6.1-cp312-cp312-win_amd64.whl", hash = "sha256:e33e86fd65f369f10608b08729c8f1c92ec7e0e485964670b4d2633a4812d36b", size = 204268 }, ] diff --git a/misc/release/archive-version.js b/misc/release/archive-version.js index 3ef4f58b1e..1a66963dad 100755 --- a/misc/release/archive-version.js +++ b/misc/release/archive-version.js @@ -10,7 +10,7 @@ if (!nextVersion) { const filename = './docs/static/archived-versions.json'; const oldVersions = JSON.parse(readFileSync(filename)); const newVersions = [ - { label: `v${nextVersion}`, url: `https://v${nextVersion}.archive.immich.app` }, + { label: `v${nextVersion}`, url: `https://docs.v${nextVersion}.archive.immich.app` }, ...oldVersions, ]; diff --git a/misc/release/pump-version.sh b/misc/release/pump-version.sh index 35ce9a1f33..2dc772ca91 100755 --- a/misc/release/pump-version.sh +++ b/misc/release/pump-version.sh @@ -3,12 +3,12 @@ # # Pump one or both of the server/mobile versions in appropriate files # -# usage: './scripts/pump-version.sh -s <-m> +# usage: './scripts/pump-version.sh -s <-m> # # examples: -# ./scripts/pump-version.sh -s major # 1.0.0+50 => 2.0.0+50 -# ./scripts/pump-version.sh -s minor -m # 1.0.0+50 => 1.1.0+51 -# ./scripts/pump-version.sh -m # 1.0.0+50 => 1.0.0+51 +# ./scripts/pump-version.sh -s major # 1.0.0+50 => 2.0.0+50 +# ./scripts/pump-version.sh -s minor -m true # 1.0.0+50 => 1.1.0+51 +# ./scripts/pump-version.sh -m true # 1.0.0+50 => 1.0.0+51 # SERVER_PUMP="false" @@ -80,7 +80,7 @@ if [ "$CURRENT_SERVER" != "$NEXT_SERVER" ]; then jq --arg version "$NEXT_SERVER" '.version = $version' e2e/package.json > e2e/package.json.tmp && mv e2e/package.json.tmp e2e/package.json pnpm install --frozen-lockfile --prefix e2e - uvx --from=toml-cli toml set --toml-path=pyproject.toml project.version "$SERVER_PUMP" + uvx --from=toml-cli toml set --toml-path=machine-learning/pyproject.toml project.version "$NEXT_SERVER" fi if [ "$CURRENT_MOBILE" != "$NEXT_MOBILE" ]; then @@ -88,7 +88,6 @@ if [ "$CURRENT_MOBILE" != "$NEXT_MOBILE" ]; then fi sed -i "s/\"android\.injected\.version\.name\" => \"$CURRENT_SERVER\",/\"android\.injected\.version\.name\" => \"$NEXT_SERVER\",/" mobile/android/fastlane/Fastfile -sed -i "s/version_number: \"$CURRENT_SERVER\"$/version_number: \"$NEXT_SERVER\"/" mobile/ios/fastlane/Fastfile sed -i "s/\"android\.injected\.version\.code\" => $CURRENT_MOBILE,/\"android\.injected\.version\.code\" => $NEXT_MOBILE,/" mobile/android/fastlane/Fastfile sed -i "s/^version: $CURRENT_SERVER+$CURRENT_MOBILE$/version: $NEXT_SERVER+$NEXT_MOBILE/" mobile/pubspec.yaml diff --git a/mise.toml b/mise.toml index 51dc3b1606..cf3b86c6cc 100644 --- a/mise.toml +++ b/mise.toml @@ -1,7 +1,11 @@ +experimental_monorepo_root = true + [tools] -node = "22.19.0" -flutter = "3.35.4" -pnpm = "10.15.1" +node = "24.11.0" +flutter = "3.35.7" +pnpm = "10.20.0" +terragrunt = "0.91.2" +opentofu = "1.10.6" [tools."github:CQLabs/homebrew-dcm"] version = "1.30.0" @@ -12,496 +16,21 @@ postinstall = "chmod +x $MISE_TOOL_INSTALL_PATH/dcm" experimental = true pin = true -# .github -[tasks."github:install"] -run = "pnpm install --filter github --frozen-lockfile" - -[tasks."github:format"] -env._.path = "./.github/node_modules/.bin" -dir = ".github" -run = "prettier --check ." - -[tasks."github:format-fix"] -env._.path = "./.github/node_modules/.bin" -dir = ".github" -run = "prettier --write ." - -# @immich/cli -[tasks."cli:install"] -run = "pnpm install --filter @immich/cli --frozen-lockfile" - -[tasks."cli:build"] -env._.path = "./cli/node_modules/.bin" -dir = "cli" -run = "vite build" - -[tasks."cli:test"] -env._.path = "./cli/node_modules/.bin" -dir = "cli" -run = "vite" - -[tasks."cli:lint"] -env._.path = "./cli/node_modules/.bin" -dir = "cli" -run = "eslint \"src/**/*.ts\" --max-warnings 0" - -[tasks."cli:lint-fix"] -run = "mise run cli:lint --fix" - -[tasks."cli:format"] -env._.path = "./cli/node_modules/.bin" -dir = "cli" -run = "prettier --check ." - -[tasks."cli:format-fix"] -env._.path = "./cli/node_modules/.bin" -dir = "cli" -run = "prettier --write ." - -[tasks."cli:check"] -env._.path = "./cli/node_modules/.bin" -dir = "cli" -run = "tsc --noEmit" - -# @immich/sdk +# SDK tasks [tasks."sdk:install"] +dir = "open-api/typescript-sdk" run = "pnpm install --filter @immich/sdk --frozen-lockfile" [tasks."sdk:build"] -env._.path = "./open-api/typescript-sdk/node_modules/.bin" -dir = "./open-api/typescript-sdk" +dir = "open-api/typescript-sdk" +env._.path = "./node_modules/.bin" run = "tsc" -# docs -[tasks."docs:install"] -run = "pnpm install --filter documentation --frozen-lockfile" - -[tasks."docs:start"] -env._.path = "./docs/node_modules/.bin" -dir = "docs" -run = "docusaurus --port 3005" - -[tasks."docs:build"] -env._.path = "./docs/node_modules/.bin" -dir = "docs" -run = [ - "jq -c < ../open-api/immich-openapi-specs.json > ./static/openapi.json || exit 0", - "docusaurus build", -] - - -[tasks."docs:preview"] -env._.path = "./docs/node_modules/.bin" -dir = "docs" -run = "docusaurus serve" - - -[tasks."docs:format"] -env._.path = "./docs/node_modules/.bin" -dir = "docs" -run = "prettier --check ." - -[tasks."docs:format-fix"] -env._.path = "./docs/node_modules/.bin" -dir = "docs" -run = "prettier --write ." - - -# e2e -[tasks."e2e:install"] -run = "pnpm install --filter immich-e2e --frozen-lockfile" - -[tasks."e2e:test"] -env._.path = "./e2e/node_modules/.bin" -dir = "e2e" -run = "vitest --run" - -[tasks."e2e:test-web"] -env._.path = "./e2e/node_modules/.bin" -dir = "e2e" -run = "playwright test" - -[tasks."e2e:format"] -env._.path = "./e2e/node_modules/.bin" -dir = "e2e" -run = "prettier --check ." - -[tasks."e2e:format-fix"] -env._.path = "./e2e/node_modules/.bin" -dir = "e2e" -run = "prettier --write ." - -[tasks."e2e:lint"] -env._.path = "./e2e/node_modules/.bin" -dir = "e2e" -run = "eslint \"src/**/*.ts\" --max-warnings 0" - -[tasks."e2e:lint-fix"] -run = "mise run e2e:lint --fix" - -[tasks."e2e:check"] -env._.path = "./e2e/node_modules/.bin" -dir = "e2e" -run = "tsc --noEmit" - -# i18n +# i18n tasks [tasks."i18n:format"] -run = "mise run i18n:format-fix" +dir = "i18n" +run = { task = ":i18n:format-fix" } [tasks."i18n:format-fix"] -run = "pnpm dlx sort-json ./i18n/*.json" - - -# server -[tasks."server:install"] -run = "pnpm install --filter immich --frozen-lockfile" - -[tasks."server:build"] -env._.path = "./server/node_modules/.bin" -dir = "server" -run = "nest build" - -[tasks."server:test"] -env._.path = "./server/node_modules/.bin" -dir = "server" -run = "vitest --config test/vitest.config.mjs" - -[tasks."server:test-medium"] -env._.path = "./server/node_modules/.bin" -dir = "server" -run = "vitest --config test/vitest.config.medium.mjs" - -[tasks."server:format"] -env._.path = "./server/node_modules/.bin" -dir = "server" -run = "prettier --check ." - -[tasks."server:format-fix"] -env._.path = "./server/node_modules/.bin" -dir = "server" -run = "prettier --write ." - -[tasks."server:lint"] -env._.path = "./server/node_modules/.bin" -dir = "server" -run = "eslint \"src/**/*.ts\" \"test/**/*.ts\" --max-warnings 0" - -[tasks."server:lint-fix"] -run = "mise run server:lint --fix" - -[tasks."server:check"] -env._.path = "./server/node_modules/.bin" -dir = "server" -run = "tsc --noEmit" - -[tasks."server:sql"] -dir = "server" -run = "node ./dist/bin/sync-open-api.js" - -[tasks."server:open-api"] -dir = "server" -run = "node ./dist/bin/sync-open-api.js" - -[tasks."server:migrations"] -dir = "server" -run = "node ./dist/bin/migrations.js" -description = "Run database migration commands (create, generate, run, debug, or query)" - -[tasks."server:schema-drop"] -run = "mise run server:migrations query 'DROP schema public cascade; CREATE schema public;'" - -[tasks."server:schema-reset"] -run = "mise run server:schema-drop && mise run server:migrations run" - -[tasks."server:email-dev"] -env._.path = "./server/node_modules/.bin" -dir = "server" -run = "email dev -p 3050 --dir src/emails" - -[tasks."server:checklist"] -run = [ - "mise run server:install", - "mise run server:format", - "mise run server:lint", - "mise run server:check", - "mise run server:test-medium --run", - "mise run server:test --run", -] - - -# web -[tasks."web:install"] -run = "pnpm install --filter immich-web --frozen-lockfile" - -[tasks."web:svelte-kit-sync"] -env._.path = "./web/node_modules/.bin" -dir = "web" -run = "svelte-kit sync" - -[tasks."web:build"] -env._.path = "./web/node_modules/.bin" -dir = "web" -run = "vite build" - -[tasks."web:build-stats"] -env.BUILD_STATS = "true" -env._.path = "./web/node_modules/.bin" -dir = "web" -run = "vite build" - -[tasks."web:preview"] -env._.path = "./web/node_modules/.bin" -dir = "web" -run = "vite preview" - -[tasks."web:start"] -env._.path = "web/node_modules/.bin" -dir = "web" -run = "vite dev --host 0.0.0.0 --port 3000" - -[tasks."web:test"] -depends = "web:svelte-kit-sync" -env._.path = "web/node_modules/.bin" -dir = "web" -run = "vitest" - -[tasks."web:format"] -env._.path = "web/node_modules/.bin" -dir = "web" -run = "prettier --check ." - -[tasks."web:format-fix"] -env._.path = "web/node_modules/.bin" -dir = "web" -run = "prettier --write ." - -[tasks."web:lint"] -env._.path = "web/node_modules/.bin" -dir = "web" -run = "eslint . --max-warnings 0" - -[tasks."web:lint-p"] -env._.path = "web/node_modules/.bin" -dir = "web" -run = "eslint-p . --max-warnings 0 --concurrency=4" - -[tasks."web:lint-fix"] -run = "mise run web:lint --fix" - -[tasks."web:check"] -depends = "web:svelte-kit-sync" -env._.path = "web/node_modules/.bin" -dir = "web" -run = "tsc --noEmit" - -[tasks."web:check-svelte"] -depends = "web:svelte-kit-sync" -env._.path = "web/node_modules/.bin" -dir = "web" -run = "svelte-check --no-tsconfig --fail-on-warnings" - -[tasks."web:checklist"] -run = [ - "mise run web:install", - "mise run web:format", - "mise run web:check", - "mise run web:test --run", - "mise run web:lint", -] - - -# mobile -[tasks."mobile:codegen:dart"] -alias = "mobile:codegen" -description = "Execute build_runner to auto-generate dart code" -dir = "mobile" -sources = ["pubspec.yaml", "build.yaml", "lib/**/*.dart"] -outputs = { auto = true } -run = "dart run build_runner build --delete-conflicting-outputs" - -[tasks."mobile:codegen:pigeon"] -alias = "mobile:pigeon" -description = "Generate pigeon platform code" -dir = "mobile" -depends = [ - "mobile:pigeon:native-sync", - "mobile:pigeon:thumbnail", - "mobile:pigeon:background-worker", - "mobile:pigeon:background-worker-lock", - "mobile:pigeon:connectivity", -] - -[tasks."mobile:codegen:translation"] -alias = "mobile:translation" -description = "Generate translations from i18n JSONs" -dir = "mobile" -run = [ - { task = "i18n:format-fix" }, - { tasks = [ - "mobile:i18n:loader", - "mobile:i18n:keys", - ] }, -] - -[tasks."mobile:codegen:app-icon"] -description = "Generate app icons" -dir = "mobile" -run = "flutter pub run flutter_launcher_icons:main" - -[tasks."mobile:codegen:splash"] -description = "Generate splash screen" -dir = "mobile" -run = "flutter pub run flutter_native_splash:create" - -[tasks."mobile:test"] -description = "Run mobile tests" -dir = "mobile" -run = "flutter test" - -[tasks."mobile:lint"] -description = "Analyze Dart code" -dir = "mobile" -depends = ["mobile:analyze:dart", "mobile:analyze:dcm"] - -[tasks."mobile:lint-fix"] -description = "Auto-fix Dart code" -dir = "mobile" -depends = ["mobile:analyze:fix:dart", "mobile:analyze:fix:dcm"] - -[tasks."mobile:format"] -description = "Format Dart code" -dir = "mobile" -run = "dart format --set-exit-if-changed $(find lib -name '*.dart' -not \\( -name '*.g.dart' -o -name '*.drift.dart' -o -name '*.gr.dart' \\))" - -[tasks."mobile:build:android"] -description = "Build Android release" -dir = "mobile" -run = "flutter build appbundle" - -[tasks."mobile:drift:migration"] -alias = "mobile:migration" -description = "Generate database migrations" -dir = "mobile" -run = "dart run drift_dev make-migrations" - - -# mobile internal tasks -[tasks."mobile:pigeon:native-sync"] -description = "Generate native sync API pigeon code" -dir = "mobile" -hide = true -sources = ["pigeon/native_sync_api.dart"] -outputs = [ - "lib/platform/native_sync_api.g.dart", - "ios/Runner/Sync/Messages.g.swift", - "android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt", -] -run = [ - "dart run pigeon --input pigeon/native_sync_api.dart", - "dart format lib/platform/native_sync_api.g.dart", -] - -[tasks."mobile:pigeon:thumbnail"] -description = "Generate thumbnail API pigeon code" -dir = "mobile" -hide = true -sources = ["pigeon/thumbnail_api.dart"] -outputs = [ - "lib/platform/thumbnail_api.g.dart", - "ios/Runner/Images/Thumbnails.g.swift", - "android/app/src/main/kotlin/app/alextran/immich/images/Thumbnails.g.kt", -] -run = [ - "dart run pigeon --input pigeon/thumbnail_api.dart", - "dart format lib/platform/thumbnail_api.g.dart", -] - -[tasks."mobile:pigeon:background-worker"] -description = "Generate background worker API pigeon code" -dir = "mobile" -hide = true -sources = ["pigeon/background_worker_api.dart"] -outputs = [ - "lib/platform/background_worker_api.g.dart", - "ios/Runner/Background/BackgroundWorker.g.swift", - "android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.g.kt", -] -run = [ - "dart run pigeon --input pigeon/background_worker_api.dart", - "dart format lib/platform/background_worker_api.g.dart", -] - -[tasks."mobile:pigeon:background-worker-lock"] -description = "Generate background worker lock API pigeon code" -dir = "mobile" -hide = true -sources = ["pigeon/background_worker_lock_api.dart"] -outputs = [ - "lib/platform/background_worker_lock_api.g.dart", - "android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorkerLock.g.kt", -] -run = [ - "dart run pigeon --input pigeon/background_worker_lock_api.dart", - "dart format lib/platform/background_worker_lock_api.g.dart", -] - -[tasks."mobile:pigeon:connectivity"] -description = "Generate connectivity API pigeon code" -dir = "mobile" -hide = true -sources = ["pigeon/connectivity_api.dart"] -outputs = [ - "lib/platform/connectivity_api.g.dart", - "ios/Runner/Connectivity/Connectivity.g.swift", - "android/app/src/main/kotlin/app/alextran/immich/connectivity/Connectivity.g.kt", -] -run = [ - "dart run pigeon --input pigeon/connectivity_api.dart", - "dart format lib/platform/connectivity_api.g.dart", -] - -[tasks."mobile:i18n:loader"] -description = "Generate i18n loader" -dir = "mobile" -hide = true -sources = ["i18n/"] -outputs = "lib/generated/codegen_loader.g.dart" -run = [ - "dart run easy_localization:generate -S ../i18n", - "dart format lib/generated/codegen_loader.g.dart", -] - -[tasks."mobile:i18n:keys"] -description = "Generate i18n keys" -dir = "mobile" -hide = true -sources = ["i18n/en.json"] -outputs = "lib/generated/intl_keys.g.dart" -run = [ - "dart run bin/generate_keys.dart", - "dart format lib/generated/intl_keys.g.dart", -] - -[tasks."mobile:analyze:dart"] -description = "Run Dart analysis" -dir = "mobile" -hide = true -run = "dart analyze --fatal-infos" - -[tasks."mobile:analyze:dcm"] -description = "Run Dart Code Metrics" -dir = "mobile" -hide = true -run = "dcm analyze lib --fatal-style --fatal-warnings" - -[tasks."mobile:analyze:fix:dart"] -description = "Auto-fix Dart analysis" -dir = "mobile" -hide = true -run = "dart fix --apply" - -[tasks."mobile:analyze:fix:dcm"] -description = "Auto-fix Dart Code Metrics" -dir = "mobile" -hide = true -run = "dcm fix lib" +dir = "i18n" +run = "pnpm dlx sort-json *.json" diff --git a/mobile/.fvmrc b/mobile/.fvmrc index a4d5f6d9b7..e8b4151592 100644 --- a/mobile/.fvmrc +++ b/mobile/.fvmrc @@ -1,3 +1,3 @@ { - "flutter": "3.35.4" + "flutter": "3.35.7" } \ No newline at end of file diff --git a/mobile/.vscode/settings.json b/mobile/.vscode/settings.json index 9c6057e582..3092c4565f 100644 --- a/mobile/.vscode/settings.json +++ b/mobile/.vscode/settings.json @@ -1,5 +1,5 @@ { - "dart.flutterSdkPath": ".fvm/versions/3.35.4", + "dart.flutterSdkPath": ".fvm/versions/3.35.7", "dart.lineLength": 120, "[dart]": { "editor.rulers": [120] diff --git a/mobile/README.md b/mobile/README.md index 436b0a4c34..59b2d9340c 100644 --- a/mobile/README.md +++ b/mobile/README.md @@ -84,4 +84,4 @@ Below is how your code needs to be structured: ## Contributing -Please refer to the [architecture](https://immich.app/docs/developer/architecture/) for contributing to the mobile app! +Please refer to the [architecture](https://docs.immich.app/developer/architecture/) for contributing to the mobile app! diff --git a/mobile/analysis_options.yaml b/mobile/analysis_options.yaml index c04e1dafdc..275a38a970 100644 --- a/mobile/analysis_options.yaml +++ b/mobile/analysis_options.yaml @@ -17,7 +17,7 @@ linter: # section below to disable rules from the `package:flutter_lints/flutter.yaml` # included above or to enable additional rules. A list of all available lints # and their documentation is published at - # https://dart-lang.github.io/linter/lints/index.html. + # https://dart.dev/tools/linter-rules # # Instead of disabling a lint rule for the entire project in the # section below, it can also be suppressed for a single line of code @@ -28,6 +28,7 @@ linter: rules: # avoid_print: false # Uncomment to disable the `avoid_print` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + unawaited_futures: true use_build_context_synchronously: false require_trailing_commas: true unrelated_type_equality_checks: true @@ -46,6 +47,8 @@ analyzer: # TODO: Re-enable after upgrading custom_lint # plugins: # - custom_lint + errors: + unawaited_futures: warning custom_lint: debug: true @@ -152,160 +155,6 @@ dart_code_metrics: # - avoid-passing-async-when-sync-expected # - avoid-throw-in-catch-block - avoid-unused-parameters - # - avoid-unnecessary-type-assertions - # - avoid-unnecessary-type-casts - # - avoid-unrelated-type-assertions - # - avoid-unrelated-type-casts - # - no-empty-block - # - no-equal-then-else - # - prefer-correct-test-file-name - prefer-const-border-radius - # - prefer-match-file-name - # - prefer-return-await - # - avoid-self-assignment - # - avoid-self-compare - # - avoid-shadowing - # - prefer-iterable-of - # - no-equal-switch-case - # - no-equal-conditions - # - avoid-equal-expressions - # - avoid-missed-calls - # - avoid-unnecessary-negations - # - avoid-unused-generics - # - function-always-returns-null - # - avoid-throw-objects-without-tostring - # - avoid-unsafe-collection-methods - # - prefer-wildcard-pattern - # - no-equal-switch-expression-cases - # - avoid-future-tostring - # - avoid-unassigned-late-fields - # - avoid-nested-futures - # - avoid-generics-shadowing - # - prefer-parentheses-with-if-null - # - no-equal-nested-conditions - # - avoid-shadowed-extension-methods - # - avoid-unnecessary-conditionals - # - avoid-double-slash-imports - # - avoid-map-keys-contains - # - prefer-correct-json-casts - # - avoid-duplicate-mixins - # - avoid-nullable-interpolation - # - avoid-unused-instances - # - prefer-correct-for-loop-increment - # - prefer-public-exception-classes - # - avoid-uncaught-future-errors - # - always-remove-listener - # - avoid-unnecessary-setstate - # - check-for-equals-in-render-object-setters - # - consistent-update-render-object - # - use-setstate-synchronously - # - avoid-incomplete-copy-with - # - proper-super-calls - # - dispose-fields - # - avoid-empty-setstate - # - avoid-state-constructors - # - avoid-recursive-widget-calls - # - avoid-missing-image-alt - # - avoid-passing-self-as-argument - # - avoid-unnecessary-if - # - avoid-unconditional-break - # - avoid-referencing-discarded-variables - # - avoid-unnecessary-local-late - # - avoid-wildcard-cases-with-enums - # - match-getter-setter-field-names - # - avoid-accessing-collections-by-constant-index - # - prefer-unique-test-names - # - avoid-duplicate-cascades - # - prefer-specific-cases-first - # - avoid-duplicate-switch-case-conditions - # - prefer-explicit-function-type - # - avoid-misused-test-matchers - # - avoid-duplicate-test-assertions - # - prefer-switch-with-enums - # - prefer-any-or-every - # - avoid-duplicate-map-keys - # - avoid-nullable-tostring - # - avoid-undisposed-instances - # - avoid-duplicate-initializers - # - avoid-unassigned-stream-subscriptions - # - avoid-empty-test-groups - # - avoid-not-encodable-in-to-json - # - avoid-contradictory-expressions - # - avoid-excessive-expressions - # - prefer-private-extension-type-field - # - avoid-renaming-representation-getters - # - avoid-empty-spread - # - avoid-unnecessary-gesture-detector - # - avoid-missing-completer-stack-trace - # - avoid-casting-to-extension-type - # - prefer-overriding-parent-equality - # - avoid-missing-controller - # - avoid-unknown-pragma - # - avoid-conditions-with-boolean-literals - # - avoid-multi-assignment - # - avoid-collection-equality-checks - # - avoid-only-rethrow - # - avoid-incorrect-image-opacity - # - avoid-misused-set-literals - # - dispose-class-fields - # - avoid-suspicious-super-overrides - # - avoid-assignments-as-conditions - # - avoid-unused-assignment - # - avoid-unnecessary-overrides - # - avoid-implicitly-nullable-extension-types - # Enable with the next release - # - avoid-late-final-reassignment - # - avoid-duplicate-constant-values - # - function-always-returns-same-value - # - avoid-flexible-outside-flex - # - avoid-unnecessary-patterns - # - use-closest-build-context - # - avoid-commented-out-code - # - avoid-recursive-tostring - # - avoid-enum-values-by-index - # - avoid-constant-assert-conditions - # - avoid-inconsistent-digit-separators - # - pass-existing-future-to-future-builder - # - pass-existing-stream-to-stream-builder - - # Code simplification - # - avoid-redundant-async - # - avoid-redundant-else - # - avoid-unnecessary-nullable-return-type - # - avoid-redundant-pragma-inline - # - avoid-nested-records - # - avoid-redundant-positional-field-name - # - avoid-explicit-pattern-field-name - # - prefer-simpler-patterns-null-check - # - avoid-unnecessary-return - # - avoid-duplicate-patterns - # - avoid-keywords-in-wildcard-pattern - # - avoid-unnecessary-futures - # - avoid-unnecessary-reassignment - # - avoid-unnecessary-call - # - avoid-unnecessary-stateful-widgets - # - prefer-dedicated-media-query-methods - # - avoid-unnecessary-overrides-in-state - # - move-variable-closer-to-its-usage - # - avoid-nullable-parameters-with-default-values - # - prefer-null-aware-spread - # - avoid-inferrable-type-arguments - # - avoid-unnecessary-super - # - avoid-unnecessary-collections - # - avoid-unnecessary-extends - # - avoid-unnecessary-enum-arguments - # - prefer-contains - # Enable with the next release - # - prefer-simpler-boolean-expressions - # - prefer-spacing - # - avoid-unnecessary-continue - # - avoid-unnecessary-compare-to - - # Style - # - prefer-trailing-comma - # - unnecessary-trailing-comma - prefer-declaring-const-constructor - # - prefer-single-widget-per-file - prefer-switch-expression - # - prefer-prefixed-global-constants - # - prefer-correct-callback-field-name diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/BackgroundServicePlugin.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/BackgroundServicePlugin.kt index ae2ec22a71..f62f25558d 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/BackgroundServicePlugin.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/BackgroundServicePlugin.kt @@ -143,7 +143,7 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler, val mediaUrls = call.argument>("mediaUrls") if (mediaUrls != null) { if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) && hasManageMediaPermission()) { - moveToTrash(mediaUrls, result) + moveToTrash(mediaUrls, result) } else { result.error("PERMISSION_DENIED", "Media permission required", null) } @@ -155,15 +155,23 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler, "restoreFromTrash" -> { val fileName = call.argument("fileName") val type = call.argument("type") + val mediaId = call.argument("mediaId") if (fileName != null && type != null) { if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) && hasManageMediaPermission()) { restoreFromTrash(fileName, type, result) } else { result.error("PERMISSION_DENIED", "Media permission required", null) } - } else { - result.error("INVALID_NAME", "The file name is not specified.", null) - } + } else + if (mediaId != null && type != null) { + if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) && hasManageMediaPermission()) { + restoreFromTrashById(mediaId, type, result) + } else { + result.error("PERMISSION_DENIED", "Media permission required", null) + } + } else { + result.error("INVALID_PARAMS", "Required params are not specified.", null) + } } "requestManageMediaPermission" -> { @@ -175,6 +183,17 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler, } } + "hasManageMediaPermission" -> { + if (hasManageMediaPermission()) { + Log.i("Manage storage permission", "Permission already granted") + result.success(true) + } else { + result.success(false) + } + } + + "manageMediaPermission" -> requestManageMediaPermission(result) + else -> result.notImplemented() } } @@ -224,25 +243,47 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler, } @RequiresApi(Build.VERSION_CODES.R) - private fun toggleTrash(contentUris: List, isTrashed: Boolean, result: Result) { - val activity = activityBinding?.activity - val contentResolver = context?.contentResolver - if (activity == null || contentResolver == null) { - result.error("TrashError", "Activity or ContentResolver not available", null) - return - } + private fun restoreFromTrashById(mediaId: String, type: Int, result: Result) { + val id = mediaId.toLongOrNull() + if (id == null) { + result.error("INVALID_ID", "The file id is not a valid number: $mediaId", null) + return + } + if (!isInTrash(id)) { + result.error("TrashNotFound", "Item with id=$id not found in trash", null) + return + } - try { - val pendingIntent = MediaStore.createTrashRequest(contentResolver, contentUris, isTrashed) - pendingResult = result // Store for onActivityResult - activity.startIntentSenderForResult( - pendingIntent.intentSender, - trashRequestCode, - null, 0, 0, 0 - ) - } catch (e: Exception) { - Log.e("TrashError", "Error creating or starting trash request", e) - result.error("TrashError", "Error creating or starting trash request", null) + val uri = ContentUris.withAppendedId(contentUriForType(type), id) + + try { + Log.i(TAG, "restoreFromTrashById: uri=$uri (type=$type,id=$id)") + restoreUris(listOf(uri), result) + } catch (e: Exception) { + Log.w(TAG, "restoreFromTrashById failed", e) + } + } + + @RequiresApi(Build.VERSION_CODES.R) + private fun toggleTrash(contentUris: List, isTrashed: Boolean, result: Result) { + val activity = activityBinding?.activity + val contentResolver = context?.contentResolver + if (activity == null || contentResolver == null) { + result.error("TrashError", "Activity or ContentResolver not available", null) + return + } + + try { + val pendingIntent = MediaStore.createTrashRequest(contentResolver, contentUris, isTrashed) + pendingResult = result // Store for onActivityResult + activity.startIntentSenderForResult( + pendingIntent.intentSender, + trashRequestCode, + null, 0, 0, 0 + ) + } catch (e: Exception) { + Log.e("TrashError", "Error creating or starting trash request", e) + result.error("TrashError", "Error creating or starting trash request", null) } } @@ -264,14 +305,7 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler, contentResolver.query(queryUri, projection, queryArgs, null)?.use { cursor -> if (cursor.moveToFirst()) { val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID)) - // same order as AssetType from dart - val contentUri = when (type) { - 1 -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI - 2 -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI - 3 -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI - else -> queryUri - } - return ContentUris.withAppendedId(contentUri, id) + return ContentUris.withAppendedId(contentUriForType(type), id) } } return null @@ -315,6 +349,40 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler, } return false } + + @RequiresApi(Build.VERSION_CODES.R) + private fun isInTrash(id: Long): Boolean { + val contentResolver = context?.contentResolver ?: return false + val filesUri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL) + val args = Bundle().apply { + putString(ContentResolver.QUERY_ARG_SQL_SELECTION, "${MediaStore.Files.FileColumns._ID}=?") + putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, arrayOf(id.toString())) + putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_ONLY) + putInt(ContentResolver.QUERY_ARG_LIMIT, 1) + } + return contentResolver.query(filesUri, arrayOf(MediaStore.Files.FileColumns._ID), args, null) + ?.use { it.moveToFirst() } == true + } + + @RequiresApi(Build.VERSION_CODES.R) + private fun restoreUris(uris: List, result: Result) { + if (uris.isEmpty()) { + result.error("TrashError", "No URIs to restore", null) + return + } + Log.i(TAG, "restoreUris: count=${uris.size}, first=${uris.first()}") + toggleTrash(uris, false, result) + } + + @RequiresApi(Build.VERSION_CODES.Q) + private fun contentUriForType(type: Int): Uri = + when (type) { + // same order as AssetType from dart + 1 -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI + 2 -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI + 3 -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + else -> MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL) + } } private const val TAG = "BackgroundServicePlugin" diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/ImmichApp.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/ImmichApp.kt index 5a3b0e1f3d..4474c63e09 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/ImmichApp.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/ImmichApp.kt @@ -1,8 +1,11 @@ package app.alextran.immich import android.app.Application +import android.os.Handler +import android.os.Looper import androidx.work.Configuration import androidx.work.WorkManager +import app.alextran.immich.background.BackgroundEngineLock import app.alextran.immich.background.BackgroundWorkerApiImpl class ImmichApp : Application() { @@ -17,6 +20,11 @@ class ImmichApp : Application() { // As a workaround, we also run a backup check when initializing the application ContentObserverWorker.startBackupWorker(context = this, delayMilliseconds = 0) - BackgroundWorkerApiImpl.enqueueBackgroundWorker(this) + Handler(Looper.getMainLooper()).postDelayed({ + // We can only check the engine count and not the status of the lock here, + // as the previous start might have been killed without unlocking. + if (BackgroundEngineLock.connectEngines > 0) return@postDelayed + BackgroundWorkerApiImpl.enqueueBackgroundWorker(this) + }, 5000) } } diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt index 034f5ee72e..4383b3098d 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt @@ -9,6 +9,7 @@ import app.alextran.immich.background.BackgroundWorkerFgHostApi import app.alextran.immich.background.BackgroundWorkerLockApi import app.alextran.immich.connectivity.ConnectivityApi import app.alextran.immich.connectivity.ConnectivityApiImpl +import app.alextran.immich.core.ImmichPlugin import app.alextran.immich.images.ThumbnailApi import app.alextran.immich.images.ThumbnailsImpl import app.alextran.immich.sync.NativeSyncApi @@ -42,6 +43,14 @@ class MainActivity : FlutterFragmentActivity() { flutterEngine.plugins.add(BackgroundServicePlugin()) flutterEngine.plugins.add(HttpSSLOptionsPlugin()) flutterEngine.plugins.add(backgroundEngineLockImpl) + flutterEngine.plugins.add(nativeSyncApiImpl) + } + + fun cancelPlugins(flutterEngine: FlutterEngine) { + val nativeApi = + flutterEngine.plugins.get(NativeSyncApiImpl26::class.java) as ImmichPlugin? + ?: flutterEngine.plugins.get(NativeSyncApiImpl30::class.java) as ImmichPlugin? + nativeApi?.detachFromEngine() } } } diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundEngineLock.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundEngineLock.kt index d8afe32b5c..b11b53bcde 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundEngineLock.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundEngineLock.kt @@ -2,49 +2,55 @@ package app.alextran.immich.background import android.content.Context import android.util.Log +import app.alextran.immich.core.ImmichPlugin import io.flutter.embedding.engine.plugins.FlutterPlugin import java.util.concurrent.atomic.AtomicInteger private const val TAG = "BackgroundEngineLock" -class BackgroundEngineLock(context: Context) : BackgroundWorkerLockApi, FlutterPlugin { - private val ctx: Context = context.applicationContext +class BackgroundEngineLock(context: Context) : BackgroundWorkerLockApi, ImmichPlugin() { + private val ctx: Context = context.applicationContext - companion object { + companion object { - private var engineCount = AtomicInteger(0) + private var engineCount = AtomicInteger(0) - private fun checkAndEnforceBackgroundLock(ctx: Context) { - // work manager task is running while the main app is opened, cancel the worker - if (BackgroundWorkerPreferences(ctx).isLocked() && - engineCount.get() > 1 && - BackgroundWorkerApiImpl.isBackgroundWorkerRunning() - ) { - Log.i(TAG, "Background worker is locked, cancelling the background worker") - BackgroundWorkerApiImpl.cancelBackgroundWorker(ctx) - } - } + val connectEngines: Int + get() = engineCount.get() + + private fun checkAndEnforceBackgroundLock(ctx: Context) { + // work manager task is running while the main app is opened, cancel the worker + if (BackgroundWorkerPreferences(ctx).isLocked() && + connectEngines > 1 && + BackgroundWorkerApiImpl.isBackgroundWorkerRunning() + ) { + Log.i(TAG, "Background worker is locked, cancelling the background worker") + BackgroundWorkerApiImpl.cancelBackgroundWorker(ctx) + } } + } - override fun lock() { - BackgroundWorkerPreferences(ctx).setLocked(true) - checkAndEnforceBackgroundLock(ctx) - Log.i(TAG, "Background worker is locked") - } + override fun lock() { + BackgroundWorkerPreferences(ctx).setLocked(true) + checkAndEnforceBackgroundLock(ctx) + Log.i(TAG, "Background worker is locked") + } - override fun unlock() { - BackgroundWorkerPreferences(ctx).setLocked(false) - Log.i(TAG, "Background worker is unlocked") - } + override fun unlock() { + BackgroundWorkerPreferences(ctx).setLocked(false) + Log.i(TAG, "Background worker is unlocked") + } - override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { - checkAndEnforceBackgroundLock(binding.applicationContext) - engineCount.incrementAndGet() - Log.i(TAG, "Flutter engine attached. Attached Engines count: $engineCount") - } + override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { + super.onAttachedToEngine(binding) + checkAndEnforceBackgroundLock(binding.applicationContext) + engineCount.incrementAndGet() + Log.i(TAG, "Flutter engine attached. Attached Engines count: $engineCount") + } - override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { - engineCount.decrementAndGet() - Log.i(TAG, "Flutter engine detached. Attached Engines count: $engineCount") - } + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + super.onDetachedFromEngine(binding) + engineCount.decrementAndGet() + Log.i(TAG, "Flutter engine detached. Attached Engines count: $engineCount") + } } diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.g.kt index 052395c172..b6b387db03 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.g.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.g.kt @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v26.0.0), do not edit directly. +// Autogenerated from Pigeon (v26.0.2), do not edit directly. // See also: https://pub.dev/packages/pigeon @file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") @@ -136,6 +136,7 @@ private open class BackgroundWorkerPigeonCodec : StandardMessageCodec() { /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ interface BackgroundWorkerFgHostApi { fun enable() + fun saveNotificationMessage(title: String, body: String) fun configure(settings: BackgroundWorkerSettings) fun disable() @@ -164,6 +165,25 @@ interface BackgroundWorkerFgHostApi { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.saveNotificationMessage$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val titleArg = args[0] as String + val bodyArg = args[1] as String + val wrapped: List = try { + api.saveNotificationMessage(titleArg, bodyArg) + listOf(null) + } catch (exception: Throwable) { + BackgroundWorkerPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } run { val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.configure$separatedMessageChannelSuffix", codec) if (api != null) { @@ -204,7 +224,6 @@ interface BackgroundWorkerFgHostApi { /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ interface BackgroundWorkerBgHostApi { fun onInitialized() - fun showNotification(title: String, content: String) fun close() companion object { @@ -232,25 +251,6 @@ interface BackgroundWorkerBgHostApi { channel.setMessageHandler(null) } } - run { - val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.showNotification$separatedMessageChannelSuffix", codec) - if (api != null) { - channel.setMessageHandler { message, reply -> - val args = message as List - val titleArg = args[0] as String - val contentArg = args[1] as String - val wrapped: List = try { - api.showNotification(titleArg, contentArg) - listOf(null) - } catch (exception: Throwable) { - BackgroundWorkerPigeonUtils.wrapError(exception) - } - reply.reply(wrapped) - } - } else { - channel.setMessageHandler(null) - } - } run { val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.close$separatedMessageChannelSuffix", codec) if (api != null) { diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.kt index 71d9f5ffe3..7dce1f6edf 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.kt @@ -73,6 +73,8 @@ class BackgroundWorker(context: Context, params: WorkerParameters) : NotificationManager.IMPORTANCE_LOW ) notificationManager.createNotificationChannel(notificationChannel) + val notificationConfig = BackgroundWorkerPreferences(ctx).getNotificationConfig() + showNotification(notificationConfig.first, notificationConfig.second) loader.ensureInitializationCompleteAsync(ctx, null, Handler(Looper.getMainLooper())) { engine = FlutterEngine(ctx) @@ -109,7 +111,7 @@ class BackgroundWorker(context: Context, params: WorkerParameters) : } // TODO: Move this to a separate NotificationManager class - override fun showNotification(title: String, content: String) { + private fun showNotification(title: String, content: String) { val notification = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID) .setSmallIcon(R.drawable.notification_icon) .setOnlyAlertOnce(true) @@ -188,6 +190,9 @@ class BackgroundWorker(context: Context, params: WorkerParameters) : private fun complete(success: Result) { Log.d(TAG, "About to complete BackupWorker with result: $success") isComplete = true + if (engine != null) { + MainActivity.cancelPlugins(engine!!) + } engine?.destroy() engine = null flutterApi = null diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorkerApiImpl.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorkerApiImpl.kt index 7e0c65eb56..a78db3c5ea 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorkerApiImpl.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorkerApiImpl.kt @@ -20,6 +20,10 @@ class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi { enqueueMediaObserver(ctx) } + override fun saveNotificationMessage(title: String, body: String) { + BackgroundWorkerPreferences(ctx).updateNotificationConfig(title, body) + } + override fun configure(settings: BackgroundWorkerSettings) { BackgroundWorkerPreferences(ctx).updateSettings(settings) enqueueMediaObserver(ctx) @@ -47,7 +51,7 @@ class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi { addContentUriTrigger(MediaStore.Video.Media.INTERNAL_CONTENT_URI, true) addContentUriTrigger(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true) setTriggerContentUpdateDelay(settings.minimumDelaySeconds, TimeUnit.SECONDS) - setTriggerContentMaxDelay(settings.minimumDelaySeconds * 10, TimeUnit.MINUTES) + setTriggerContentMaxDelay(settings.minimumDelaySeconds * 10, TimeUnit.SECONDS) setRequiresCharging(settings.requiresCharging) }.build() diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorkerLock.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorkerLock.g.kt index 3d00bafba2..d7353f0462 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorkerLock.g.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorkerLock.g.kt @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v26.0.0), do not edit directly. +// Autogenerated from Pigeon (v26.0.2), do not edit directly. // See also: https://pub.dev/packages/pigeon @file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorkerPreferences.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorkerPreferences.kt index 964ad558fd..450113e5b0 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorkerPreferences.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorkerPreferences.kt @@ -5,45 +5,65 @@ import android.content.SharedPreferences import androidx.core.content.edit class BackgroundWorkerPreferences(private val ctx: Context) { - companion object { - const val SHARED_PREF_NAME = "Immich::BackgroundWorker" - private const val SHARED_PREF_MIN_DELAY_KEY = "BackgroundWorker::minDelaySeconds" - private const val SHARED_PREF_REQUIRE_CHARGING_KEY = "BackgroundWorker::requireCharging" - private const val SHARED_PREF_LOCK_KEY = "BackgroundWorker::isLocked" + companion object { + const val SHARED_PREF_NAME = "Immich::BackgroundWorker" + private const val SHARED_PREF_MIN_DELAY_KEY = "BackgroundWorker::minDelaySeconds" + private const val SHARED_PREF_REQUIRE_CHARGING_KEY = "BackgroundWorker::requireCharging" + private const val SHARED_PREF_LOCK_KEY = "BackgroundWorker::isLocked" + private const val SHARED_PREF_NOTIF_TITLE_KEY = "BackgroundWorker::notificationTitle" + private const val SHARED_PREF_NOTIF_MSG_KEY = "BackgroundWorker::notificationMessage" - private const val DEFAULT_MIN_DELAY_SECONDS = 30L - private const val DEFAULT_REQUIRE_CHARGING = false - } + private const val DEFAULT_MIN_DELAY_SECONDS = 30L + private const val DEFAULT_REQUIRE_CHARGING = false + private const val DEFAULT_NOTIF_TITLE = "Uploading media" + private const val DEFAULT_NOTIF_MSG = "Checking for new assets…" + } - private val sp: SharedPreferences by lazy { - ctx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) - } + private val sp: SharedPreferences by lazy { + ctx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) + } - fun updateSettings(settings: BackgroundWorkerSettings) { - sp.edit { - putLong(SHARED_PREF_MIN_DELAY_KEY, settings.minimumDelaySeconds) - putBoolean(SHARED_PREF_REQUIRE_CHARGING_KEY, settings.requiresCharging) - } + fun updateSettings(settings: BackgroundWorkerSettings) { + sp.edit { + putLong(SHARED_PREF_MIN_DELAY_KEY, settings.minimumDelaySeconds) + putBoolean(SHARED_PREF_REQUIRE_CHARGING_KEY, settings.requiresCharging) } + } - fun getSettings(): BackgroundWorkerSettings { - return BackgroundWorkerSettings( - minimumDelaySeconds = sp.getLong(SHARED_PREF_MIN_DELAY_KEY, DEFAULT_MIN_DELAY_SECONDS), - requiresCharging = sp.getBoolean( - SHARED_PREF_REQUIRE_CHARGING_KEY, - DEFAULT_REQUIRE_CHARGING - ), - ) - } + fun getSettings(): BackgroundWorkerSettings { + val delaySeconds = sp.getLong(SHARED_PREF_MIN_DELAY_KEY, DEFAULT_MIN_DELAY_SECONDS) - fun setLocked(paused: Boolean) { - sp.edit { - putBoolean(SHARED_PREF_LOCK_KEY, paused) - } - } + return BackgroundWorkerSettings( + minimumDelaySeconds = if (delaySeconds >= 1000) delaySeconds / 1000 else delaySeconds, + requiresCharging = sp.getBoolean( + SHARED_PREF_REQUIRE_CHARGING_KEY, + DEFAULT_REQUIRE_CHARGING + ), + ) + } - fun isLocked(): Boolean { - return sp.getBoolean(SHARED_PREF_LOCK_KEY, true) + fun updateNotificationConfig(title: String, message: String) { + sp.edit { + putString(SHARED_PREF_NOTIF_TITLE_KEY, title) + putString(SHARED_PREF_NOTIF_MSG_KEY, message) } + } + + fun getNotificationConfig(): Pair { + val title = + sp.getString(SHARED_PREF_NOTIF_TITLE_KEY, DEFAULT_NOTIF_TITLE) ?: DEFAULT_NOTIF_TITLE + val message = sp.getString(SHARED_PREF_NOTIF_MSG_KEY, DEFAULT_NOTIF_MSG) ?: DEFAULT_NOTIF_MSG + return Pair(title, message) + } + + fun setLocked(paused: Boolean) { + sp.edit { + putBoolean(SHARED_PREF_LOCK_KEY, paused) + } + } + + fun isLocked(): Boolean { + return sp.getBoolean(SHARED_PREF_LOCK_KEY, true) + } } diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/connectivity/Connectivity.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/connectivity/Connectivity.g.kt index 434ba47ca1..629071382a 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/connectivity/Connectivity.g.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/connectivity/Connectivity.g.kt @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v26.0.0), do not edit directly. +// Autogenerated from Pigeon (v26.0.2), do not edit directly. // See also: https://pub.dev/packages/pigeon @file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/core/ImmichPlugin.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/core/ImmichPlugin.kt new file mode 100644 index 0000000000..4cc131b058 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/core/ImmichPlugin.kt @@ -0,0 +1,29 @@ +package app.alextran.immich.core + +import androidx.annotation.CallSuper +import io.flutter.embedding.engine.plugins.FlutterPlugin + +abstract class ImmichPlugin : FlutterPlugin { + private var detached: Boolean = false; + + @CallSuper + override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { + detached = false; + } + + fun detachFromEngine() { + detached = true + } + + @CallSuper + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + detachFromEngine() + } + + fun completeWhenActive(callback: (T) -> Unit, value: T) { + if (detached) { + return; + } + callback(value); + } +} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/images/Thumbnails.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/images/Thumbnails.g.kt index e9993a3c45..ae2cca4d7b 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/images/Thumbnails.g.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/images/Thumbnails.g.kt @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v26.0.0), do not edit directly. +// Autogenerated from Pigeon (v26.0.2), do not edit directly. // See also: https://pub.dev/packages/pigeon @file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt index 28400c803f..e6cf92f573 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v26.0.0), do not edit directly. +// Autogenerated from Pigeon (v26.0.2), do not edit directly. // See also: https://pub.dev/packages/pigeon @file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") @@ -305,6 +305,7 @@ interface NativeSyncApi { fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List fun hashAssets(assetIds: List, allowNetworkAccess: Boolean, callback: (Result>) -> Unit) fun cancelHashing() + fun getTrashedAssets(): Map> companion object { /** The codec used by NativeSyncApi. */ @@ -483,6 +484,21 @@ interface NativeSyncApi { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets$separatedMessageChannelSuffix", codec, taskQueue) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + listOf(api.getTrashedAssets()) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } } } } diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl26.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl26.kt index 5deacc30db..6d2c35d78f 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl26.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl26.kt @@ -21,4 +21,9 @@ class NativeSyncApiImpl26(context: Context) : NativeSyncApiImplBase(context), Na override fun getMediaChanges(): SyncDelta { throw IllegalStateException("Method not supported on this Android version.") } + + override fun getTrashedAssets(): Map> { + //Method not supported on this Android version. + return emptyMap() + } } diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl30.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl30.kt index 052032e143..ca54c9f823 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl30.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl30.kt @@ -1,7 +1,9 @@ package app.alextran.immich.sync +import android.content.ContentResolver import android.content.Context import android.os.Build +import android.os.Bundle import android.provider.MediaStore import androidx.annotation.RequiresApi import androidx.annotation.RequiresExtension @@ -86,4 +88,29 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na // Unmounted volumes are handled in dart when the album is removed return SyncDelta(hasChanges, changed, deleted, assetAlbums) } + + override fun getTrashedAssets(): Map> { + + val result = LinkedHashMap>() + val volumes = MediaStore.getExternalVolumeNames(ctx) + + for (volume in volumes) { + + val queryArgs = Bundle().apply { + putString(ContentResolver.QUERY_ARG_SQL_SELECTION, MEDIA_SELECTION) + putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, MEDIA_SELECTION_ARGS) + putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_ONLY) + } + + getCursor(volume, queryArgs).use { cursor -> + getAssets(cursor).forEach { res -> + if (res is AssetResult.ValidAsset) { + result.getOrPut(res.albumId) { mutableListOf() }.add(res.asset) + } + } + } + } + + return result.mapValues { it.value.toList() } + } } diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt index 868f3c6cdd..b1e9dd7d44 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt @@ -4,9 +4,12 @@ import android.annotation.SuppressLint import android.content.ContentUris import android.content.Context import android.database.Cursor +import android.net.Uri +import android.os.Bundle import android.provider.MediaStore import android.util.Base64 import androidx.core.database.getStringOrNull +import app.alextran.immich.core.ImmichPlugin import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -27,7 +30,7 @@ sealed class AssetResult { } @SuppressLint("InlinedApi") -open class NativeSyncApiImplBase(context: Context) { +open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() { private val ctx: Context = context.applicationContext private var hashTask: Job? = null @@ -80,6 +83,16 @@ open class NativeSyncApiImplBase(context: Context) { sortOrder, ) + protected fun getCursor( + volume: String, + queryArgs: Bundle + ): Cursor? = ctx.contentResolver.query( + MediaStore.Files.getContentUri(volume), + ASSET_PROJECTION, + queryArgs, + null + ) + protected fun getAssets(cursor: Cursor?): Sequence { return sequence { cursor?.use { c -> @@ -100,9 +113,15 @@ open class NativeSyncApiImplBase(context: Context) { while (c.moveToNext()) { val id = c.getLong(idColumn).toString() + val name = c.getStringOrNull(nameColumn) + val bucketId = c.getStringOrNull(bucketIdColumn) + val path = c.getStringOrNull(dataColumn) - val path = c.getString(dataColumn) - if (path.isNullOrBlank() || !File(path).exists()) { + // Skip assets with invalid metadata + if ( + name.isNullOrBlank() || bucketId.isNullOrBlank() || + path.isNullOrBlank() || !File(path).exists() + ) { yield(AssetResult.InvalidAsset(id)) continue } @@ -112,7 +131,6 @@ open class NativeSyncApiImplBase(context: Context) { MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO -> 2 else -> 0 } - val name = c.getString(nameColumn) // Date taken is milliseconds since epoch, Date added is seconds since epoch val createdAt = (c.getLong(dateTakenColumn).takeIf { it > 0 }?.div(1000)) ?: c.getLong(dateAddedColumn) @@ -123,7 +141,6 @@ open class NativeSyncApiImplBase(context: Context) { // Duration is milliseconds val duration = if (mediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE) 0 else c.getLong(durationColumn) / 1000 - val bucketId = c.getString(bucketIdColumn) val orientation = c.getInt(orientationColumn) val isFavorite = if (favoriteColumn == -1) false else c.getInt(favoriteColumn) != 0 @@ -237,7 +254,7 @@ open class NativeSyncApiImplBase(context: Context) { callback: (Result>) -> Unit ) { if (assetIds.isEmpty()) { - callback(Result.success(emptyList())) + completeWhenActive(callback, Result.success(emptyList())) return } @@ -253,10 +270,10 @@ open class NativeSyncApiImplBase(context: Context) { } }.awaitAll() - callback(Result.success(results)) + completeWhenActive(callback, Result.success(results)) } catch (e: CancellationException) { - callback( - Result.failure( + completeWhenActive( + callback, Result.failure( FlutterError( HASHING_CANCELLED_CODE, "Hashing operation was cancelled", @@ -265,7 +282,7 @@ open class NativeSyncApiImplBase(context: Context) { ) ) } catch (e: Exception) { - callback(Result.failure(e)) + completeWhenActive(callback, Result.failure(e)) } } } diff --git a/mobile/android/build.gradle b/mobile/android/build.gradle index bcf3daa1c8..719c946bd6 100644 --- a/mobile/android/build.gradle +++ b/mobile/android/build.gradle @@ -1,5 +1,5 @@ allprojects { - ext.kotlin_version = '2.0.20' + ext.kotlin_version = '2.2.20' repositories { google() @@ -16,8 +16,8 @@ subprojects { if (project.plugins.hasPlugin("com.android.application") || project.plugins.hasPlugin("com.android.library")) { project.android { - compileSdkVersion 35 - buildToolsVersion "35.0.0" + compileSdkVersion 36 + buildToolsVersion "36.0.0" } } } diff --git a/mobile/android/fastlane/Fastfile b/mobile/android/fastlane/Fastfile index ea034664d7..6b9ce07465 100644 --- a/mobile/android/fastlane/Fastfile +++ b/mobile/android/fastlane/Fastfile @@ -35,8 +35,8 @@ platform :android do task: 'bundle', build_type: 'Release', properties: { - "android.injected.version.code" => 3016, - "android.injected.version.name" => "1.143.0", + "android.injected.version.code" => 3026, + "android.injected.version.name" => "2.2.3", } ) upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab') diff --git a/mobile/android/gradle/wrapper/gradle-wrapper.properties b/mobile/android/gradle/wrapper/gradle-wrapper.properties index dedd5d1e69..ed4c299adb 100644 --- a/mobile/android/gradle/wrapper/gradle-wrapper.properties +++ b/mobile/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/mobile/android/settings.gradle b/mobile/android/settings.gradle index 29c3a7c056..fbed55a3e3 100644 --- a/mobile/android/settings.gradle +++ b/mobile/android/settings.gradle @@ -18,10 +18,10 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version '8.7.2' apply false - id "org.jetbrains.kotlin.android" version "2.0.20" apply false + id "com.android.application" version '8.11.2' apply false + id "org.jetbrains.kotlin.android" version "2.2.20" apply false id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.22' apply false - id 'com.google.devtools.ksp' version '2.0.20-1.0.24' apply false + id 'com.google.devtools.ksp' version '2.2.20-2.0.3' apply false } include ":app" diff --git a/mobile/drift_schemas/main/drift_schema_v12.json b/mobile/drift_schemas/main/drift_schema_v12.json new file mode 100644 index 0000000000..1c100ab37f --- /dev/null +++ b/mobile/drift_schemas/main/drift_schema_v12.json @@ -0,0 +1 @@ +{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":true},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"user_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"email","getter_name":"email","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"has_profile_image","getter_name":"hasProfileImage","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"has_profile_image\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"has_profile_image\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"profile_changed_at","getter_name":"profileChangedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"avatar_color","getter_name":"avatarColor","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AvatarColor.values)","dart_type_name":"AvatarColor"}}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":1,"references":[0],"type":"table","data":{"name":"remote_asset_entity","was_declared_in_moor":false,"columns":[{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"type","getter_name":"type","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AssetType.values)","dart_type_name":"AssetType"}},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"width","getter_name":"width","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"height","getter_name":"height","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"duration_in_seconds","getter_name":"durationInSeconds","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"checksum","getter_name":"checksum","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_favorite","getter_name":"isFavorite","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_favorite\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_favorite\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"local_date_time","getter_name":"localDateTime","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"thumb_hash","getter_name":"thumbHash","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"deleted_at","getter_name":"deletedAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"live_photo_video_id","getter_name":"livePhotoVideoId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"visibility","getter_name":"visibility","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AssetVisibility.values)","dart_type_name":"AssetVisibility"}},{"name":"stack_id","getter_name":"stackId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"library_id","getter_name":"libraryId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":2,"references":[0],"type":"table","data":{"name":"stack_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"primary_asset_id","getter_name":"primaryAssetId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":3,"references":[],"type":"table","data":{"name":"local_asset_entity","was_declared_in_moor":false,"columns":[{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"type","getter_name":"type","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AssetType.values)","dart_type_name":"AssetType"}},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"width","getter_name":"width","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"height","getter_name":"height","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"duration_in_seconds","getter_name":"durationInSeconds","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"checksum","getter_name":"checksum","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_favorite","getter_name":"isFavorite","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_favorite\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_favorite\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"orientation","getter_name":"orientation","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":4,"references":[0,1],"type":"table","data":{"name":"remote_album_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"description","getter_name":"description","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('\\'\\'')","default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"thumbnail_asset_id","getter_name":"thumbnailAssetId","moor_type":"string","nullable":true,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id) ON DELETE SET NULL","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id) ON DELETE SET NULL"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"is_activity_enabled","getter_name":"isActivityEnabled","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_activity_enabled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_activity_enabled\" IN (0, 1))"},"default_dart":"const CustomExpression('1')","default_client_dart":null,"dsl_features":[]},{"name":"order","getter_name":"order","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AlbumAssetOrder.values)","dart_type_name":"AlbumAssetOrder"}}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":5,"references":[4],"type":"table","data":{"name":"local_album_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"backup_selection","getter_name":"backupSelection","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(BackupSelection.values)","dart_type_name":"BackupSelection"}},{"name":"is_ios_shared_album","getter_name":"isIosSharedAlbum","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_ios_shared_album\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_ios_shared_album\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"linked_remote_album_id","getter_name":"linkedRemoteAlbumId","moor_type":"string","nullable":true,"customConstraints":null,"defaultConstraints":"REFERENCES remote_album_entity (id) ON DELETE SET NULL","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_album_entity (id) ON DELETE SET NULL"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"marker","getter_name":"marker_","moor_type":"bool","nullable":true,"customConstraints":null,"defaultConstraints":"CHECK (\"marker\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"marker\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":6,"references":[3,5],"type":"table","data":{"name":"local_album_asset_entity","was_declared_in_moor":false,"columns":[{"name":"asset_id","getter_name":"assetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES local_asset_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES local_asset_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"album_id","getter_name":"albumId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES local_album_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES local_album_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"marker","getter_name":"marker_","moor_type":"bool","nullable":true,"customConstraints":null,"defaultConstraints":"CHECK (\"marker\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"marker\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["asset_id","album_id"]}},{"id":7,"references":[3],"type":"index","data":{"on":3,"name":"idx_local_asset_checksum","sql":"CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)","unique":false,"columns":[]}},{"id":8,"references":[1],"type":"index","data":{"on":1,"name":"idx_remote_asset_owner_checksum","sql":"CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)","unique":false,"columns":[]}},{"id":9,"references":[1],"type":"index","data":{"on":1,"name":"UQ_remote_assets_owner_checksum","sql":"CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum\nON remote_asset_entity (owner_id, checksum)\nWHERE (library_id IS NULL);\n","unique":true,"columns":[]}},{"id":10,"references":[1],"type":"index","data":{"on":1,"name":"UQ_remote_assets_owner_library_checksum","sql":"CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum\nON remote_asset_entity (owner_id, library_id, checksum)\nWHERE (library_id IS NOT NULL);\n","unique":true,"columns":[]}},{"id":11,"references":[1],"type":"index","data":{"on":1,"name":"idx_remote_asset_checksum","sql":"CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)","unique":false,"columns":[]}},{"id":12,"references":[],"type":"table","data":{"name":"auth_user_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"email","getter_name":"email","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_admin","getter_name":"isAdmin","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_admin\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_admin\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"has_profile_image","getter_name":"hasProfileImage","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"has_profile_image\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"has_profile_image\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"profile_changed_at","getter_name":"profileChangedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"avatar_color","getter_name":"avatarColor","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AvatarColor.values)","dart_type_name":"AvatarColor"}},{"name":"quota_size_in_bytes","getter_name":"quotaSizeInBytes","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"quota_usage_in_bytes","getter_name":"quotaUsageInBytes","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"pin_code","getter_name":"pinCode","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":13,"references":[0],"type":"table","data":{"name":"user_metadata_entity","was_declared_in_moor":false,"columns":[{"name":"user_id","getter_name":"userId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"key","getter_name":"key","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(UserMetadataKey.values)","dart_type_name":"UserMetadataKey"}},{"name":"value","getter_name":"value","moor_type":"blob","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"userMetadataConverter","dart_type_name":"Map"}}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["user_id","key"]}},{"id":14,"references":[0],"type":"table","data":{"name":"partner_entity","was_declared_in_moor":false,"columns":[{"name":"shared_by_id","getter_name":"sharedById","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"shared_with_id","getter_name":"sharedWithId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"in_timeline","getter_name":"inTimeline","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"in_timeline\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"in_timeline\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["shared_by_id","shared_with_id"]}},{"id":15,"references":[1],"type":"table","data":{"name":"remote_exif_entity","was_declared_in_moor":false,"columns":[{"name":"asset_id","getter_name":"assetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"city","getter_name":"city","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"state","getter_name":"state","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"country","getter_name":"country","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"date_time_original","getter_name":"dateTimeOriginal","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"description","getter_name":"description","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"height","getter_name":"height","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"width","getter_name":"width","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"exposure_time","getter_name":"exposureTime","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"f_number","getter_name":"fNumber","moor_type":"double","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"file_size","getter_name":"fileSize","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"focal_length","getter_name":"focalLength","moor_type":"double","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"latitude","getter_name":"latitude","moor_type":"double","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"longitude","getter_name":"longitude","moor_type":"double","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"iso","getter_name":"iso","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"make","getter_name":"make","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"model","getter_name":"model","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"lens","getter_name":"lens","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"orientation","getter_name":"orientation","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"time_zone","getter_name":"timeZone","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"rating","getter_name":"rating","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"projection_type","getter_name":"projectionType","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["asset_id"]}},{"id":16,"references":[1,4],"type":"table","data":{"name":"remote_album_asset_entity","was_declared_in_moor":false,"columns":[{"name":"asset_id","getter_name":"assetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"album_id","getter_name":"albumId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_album_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_album_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["asset_id","album_id"]}},{"id":17,"references":[4,0],"type":"table","data":{"name":"remote_album_user_entity","was_declared_in_moor":false,"columns":[{"name":"album_id","getter_name":"albumId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_album_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_album_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"user_id","getter_name":"userId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"role","getter_name":"role","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AlbumUserRole.values)","dart_type_name":"AlbumUserRole"}}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["album_id","user_id"]}},{"id":18,"references":[0],"type":"table","data":{"name":"memory_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"deleted_at","getter_name":"deletedAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"type","getter_name":"type","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(MemoryTypeEnum.values)","dart_type_name":"MemoryTypeEnum"}},{"name":"data","getter_name":"data","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_saved","getter_name":"isSaved","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_saved\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_saved\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"memory_at","getter_name":"memoryAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"seen_at","getter_name":"seenAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"show_at","getter_name":"showAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"hide_at","getter_name":"hideAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":19,"references":[1,18],"type":"table","data":{"name":"memory_asset_entity","was_declared_in_moor":false,"columns":[{"name":"asset_id","getter_name":"assetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"memory_id","getter_name":"memoryId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES memory_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES memory_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["asset_id","memory_id"]}},{"id":20,"references":[0],"type":"table","data":{"name":"person_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"face_asset_id","getter_name":"faceAssetId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_favorite","getter_name":"isFavorite","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_favorite\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_favorite\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_hidden","getter_name":"isHidden","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_hidden\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_hidden\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"color","getter_name":"color","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"birth_date","getter_name":"birthDate","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":21,"references":[1,20],"type":"table","data":{"name":"asset_face_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"asset_id","getter_name":"assetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"person_id","getter_name":"personId","moor_type":"string","nullable":true,"customConstraints":null,"defaultConstraints":"REFERENCES person_entity (id) ON DELETE SET NULL","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES person_entity (id) ON DELETE SET NULL"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"image_width","getter_name":"imageWidth","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"image_height","getter_name":"imageHeight","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"bounding_box_x1","getter_name":"boundingBoxX1","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"bounding_box_y1","getter_name":"boundingBoxY1","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"bounding_box_x2","getter_name":"boundingBoxX2","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"bounding_box_y2","getter_name":"boundingBoxY2","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"source_type","getter_name":"sourceType","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":22,"references":[],"type":"table","data":{"name":"store_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"string_value","getter_name":"stringValue","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"int_value","getter_name":"intValue","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":23,"references":[15],"type":"index","data":{"on":15,"name":"idx_lat_lng","sql":"CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)","unique":false,"columns":[]}}]} \ No newline at end of file diff --git a/mobile/drift_schemas/main/drift_schema_v13.json b/mobile/drift_schemas/main/drift_schema_v13.json new file mode 100644 index 0000000000..e527e8d78a --- /dev/null +++ b/mobile/drift_schemas/main/drift_schema_v13.json @@ -0,0 +1 @@ +{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":true},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"user_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"email","getter_name":"email","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"has_profile_image","getter_name":"hasProfileImage","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"has_profile_image\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"has_profile_image\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"profile_changed_at","getter_name":"profileChangedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"avatar_color","getter_name":"avatarColor","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AvatarColor.values)","dart_type_name":"AvatarColor"}}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":1,"references":[0],"type":"table","data":{"name":"remote_asset_entity","was_declared_in_moor":false,"columns":[{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"type","getter_name":"type","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AssetType.values)","dart_type_name":"AssetType"}},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"width","getter_name":"width","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"height","getter_name":"height","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"duration_in_seconds","getter_name":"durationInSeconds","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"checksum","getter_name":"checksum","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_favorite","getter_name":"isFavorite","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_favorite\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_favorite\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"local_date_time","getter_name":"localDateTime","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"thumb_hash","getter_name":"thumbHash","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"deleted_at","getter_name":"deletedAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"live_photo_video_id","getter_name":"livePhotoVideoId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"visibility","getter_name":"visibility","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AssetVisibility.values)","dart_type_name":"AssetVisibility"}},{"name":"stack_id","getter_name":"stackId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"library_id","getter_name":"libraryId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":2,"references":[0],"type":"table","data":{"name":"stack_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"primary_asset_id","getter_name":"primaryAssetId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":3,"references":[],"type":"table","data":{"name":"local_asset_entity","was_declared_in_moor":false,"columns":[{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"type","getter_name":"type","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AssetType.values)","dart_type_name":"AssetType"}},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"width","getter_name":"width","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"height","getter_name":"height","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"duration_in_seconds","getter_name":"durationInSeconds","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"checksum","getter_name":"checksum","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_favorite","getter_name":"isFavorite","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_favorite\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_favorite\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"orientation","getter_name":"orientation","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":4,"references":[0,1],"type":"table","data":{"name":"remote_album_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"description","getter_name":"description","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('\\'\\'')","default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"thumbnail_asset_id","getter_name":"thumbnailAssetId","moor_type":"string","nullable":true,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id) ON DELETE SET NULL","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id) ON DELETE SET NULL"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"is_activity_enabled","getter_name":"isActivityEnabled","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_activity_enabled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_activity_enabled\" IN (0, 1))"},"default_dart":"const CustomExpression('1')","default_client_dart":null,"dsl_features":[]},{"name":"order","getter_name":"order","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AlbumAssetOrder.values)","dart_type_name":"AlbumAssetOrder"}}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":5,"references":[4],"type":"table","data":{"name":"local_album_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"backup_selection","getter_name":"backupSelection","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(BackupSelection.values)","dart_type_name":"BackupSelection"}},{"name":"is_ios_shared_album","getter_name":"isIosSharedAlbum","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_ios_shared_album\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_ios_shared_album\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"linked_remote_album_id","getter_name":"linkedRemoteAlbumId","moor_type":"string","nullable":true,"customConstraints":null,"defaultConstraints":"REFERENCES remote_album_entity (id) ON DELETE SET NULL","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_album_entity (id) ON DELETE SET NULL"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"marker","getter_name":"marker_","moor_type":"bool","nullable":true,"customConstraints":null,"defaultConstraints":"CHECK (\"marker\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"marker\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":6,"references":[3,5],"type":"table","data":{"name":"local_album_asset_entity","was_declared_in_moor":false,"columns":[{"name":"asset_id","getter_name":"assetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES local_asset_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES local_asset_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"album_id","getter_name":"albumId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES local_album_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES local_album_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"marker","getter_name":"marker_","moor_type":"bool","nullable":true,"customConstraints":null,"defaultConstraints":"CHECK (\"marker\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"marker\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["asset_id","album_id"]}},{"id":7,"references":[3],"type":"index","data":{"on":3,"name":"idx_local_asset_checksum","sql":"CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)","unique":false,"columns":[]}},{"id":8,"references":[1],"type":"index","data":{"on":1,"name":"idx_remote_asset_owner_checksum","sql":"CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)","unique":false,"columns":[]}},{"id":9,"references":[1],"type":"index","data":{"on":1,"name":"UQ_remote_assets_owner_checksum","sql":"CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum\nON remote_asset_entity (owner_id, checksum)\nWHERE (library_id IS NULL);\n","unique":true,"columns":[]}},{"id":10,"references":[1],"type":"index","data":{"on":1,"name":"UQ_remote_assets_owner_library_checksum","sql":"CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum\nON remote_asset_entity (owner_id, library_id, checksum)\nWHERE (library_id IS NOT NULL);\n","unique":true,"columns":[]}},{"id":11,"references":[1],"type":"index","data":{"on":1,"name":"idx_remote_asset_checksum","sql":"CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)","unique":false,"columns":[]}},{"id":12,"references":[],"type":"table","data":{"name":"auth_user_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"email","getter_name":"email","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_admin","getter_name":"isAdmin","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_admin\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_admin\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"has_profile_image","getter_name":"hasProfileImage","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"has_profile_image\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"has_profile_image\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"profile_changed_at","getter_name":"profileChangedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"avatar_color","getter_name":"avatarColor","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AvatarColor.values)","dart_type_name":"AvatarColor"}},{"name":"quota_size_in_bytes","getter_name":"quotaSizeInBytes","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"quota_usage_in_bytes","getter_name":"quotaUsageInBytes","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"pin_code","getter_name":"pinCode","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":13,"references":[0],"type":"table","data":{"name":"user_metadata_entity","was_declared_in_moor":false,"columns":[{"name":"user_id","getter_name":"userId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"key","getter_name":"key","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(UserMetadataKey.values)","dart_type_name":"UserMetadataKey"}},{"name":"value","getter_name":"value","moor_type":"blob","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"userMetadataConverter","dart_type_name":"Map"}}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["user_id","key"]}},{"id":14,"references":[0],"type":"table","data":{"name":"partner_entity","was_declared_in_moor":false,"columns":[{"name":"shared_by_id","getter_name":"sharedById","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"shared_with_id","getter_name":"sharedWithId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"in_timeline","getter_name":"inTimeline","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"in_timeline\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"in_timeline\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["shared_by_id","shared_with_id"]}},{"id":15,"references":[1],"type":"table","data":{"name":"remote_exif_entity","was_declared_in_moor":false,"columns":[{"name":"asset_id","getter_name":"assetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"city","getter_name":"city","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"state","getter_name":"state","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"country","getter_name":"country","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"date_time_original","getter_name":"dateTimeOriginal","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"description","getter_name":"description","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"height","getter_name":"height","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"width","getter_name":"width","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"exposure_time","getter_name":"exposureTime","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"f_number","getter_name":"fNumber","moor_type":"double","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"file_size","getter_name":"fileSize","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"focal_length","getter_name":"focalLength","moor_type":"double","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"latitude","getter_name":"latitude","moor_type":"double","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"longitude","getter_name":"longitude","moor_type":"double","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"iso","getter_name":"iso","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"make","getter_name":"make","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"model","getter_name":"model","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"lens","getter_name":"lens","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"orientation","getter_name":"orientation","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"time_zone","getter_name":"timeZone","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"rating","getter_name":"rating","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"projection_type","getter_name":"projectionType","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["asset_id"]}},{"id":16,"references":[1,4],"type":"table","data":{"name":"remote_album_asset_entity","was_declared_in_moor":false,"columns":[{"name":"asset_id","getter_name":"assetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"album_id","getter_name":"albumId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_album_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_album_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["asset_id","album_id"]}},{"id":17,"references":[4,0],"type":"table","data":{"name":"remote_album_user_entity","was_declared_in_moor":false,"columns":[{"name":"album_id","getter_name":"albumId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_album_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_album_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"user_id","getter_name":"userId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"role","getter_name":"role","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AlbumUserRole.values)","dart_type_name":"AlbumUserRole"}}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["album_id","user_id"]}},{"id":18,"references":[0],"type":"table","data":{"name":"memory_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"deleted_at","getter_name":"deletedAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"type","getter_name":"type","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(MemoryTypeEnum.values)","dart_type_name":"MemoryTypeEnum"}},{"name":"data","getter_name":"data","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_saved","getter_name":"isSaved","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_saved\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_saved\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"memory_at","getter_name":"memoryAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"seen_at","getter_name":"seenAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"show_at","getter_name":"showAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"hide_at","getter_name":"hideAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":19,"references":[1,18],"type":"table","data":{"name":"memory_asset_entity","was_declared_in_moor":false,"columns":[{"name":"asset_id","getter_name":"assetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"memory_id","getter_name":"memoryId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES memory_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES memory_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["asset_id","memory_id"]}},{"id":20,"references":[0],"type":"table","data":{"name":"person_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"owner_id","getter_name":"ownerId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES user_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES user_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"face_asset_id","getter_name":"faceAssetId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_favorite","getter_name":"isFavorite","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_favorite\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_favorite\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_hidden","getter_name":"isHidden","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_hidden\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_hidden\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"color","getter_name":"color","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"birth_date","getter_name":"birthDate","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":21,"references":[1,20],"type":"table","data":{"name":"asset_face_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"asset_id","getter_name":"assetId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES remote_asset_entity (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"person_id","getter_name":"personId","moor_type":"string","nullable":true,"customConstraints":null,"defaultConstraints":"REFERENCES person_entity (id) ON DELETE SET NULL","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES person_entity (id) ON DELETE SET NULL"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"image_width","getter_name":"imageWidth","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"image_height","getter_name":"imageHeight","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"bounding_box_x1","getter_name":"boundingBoxX1","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"bounding_box_y1","getter_name":"boundingBoxY1","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"bounding_box_x2","getter_name":"boundingBoxX2","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"bounding_box_y2","getter_name":"boundingBoxY2","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"source_type","getter_name":"sourceType","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":22,"references":[],"type":"table","data":{"name":"store_entity","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"string_value","getter_name":"stringValue","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"int_value","getter_name":"intValue","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id"]}},{"id":23,"references":[],"type":"table","data":{"name":"trashed_local_asset_entity","was_declared_in_moor":false,"columns":[{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"type","getter_name":"type","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(AssetType.values)","dart_type_name":"AssetType"}},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CURRENT_TIMESTAMP')","default_client_dart":null,"dsl_features":[]},{"name":"width","getter_name":"width","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"height","getter_name":"height","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"duration_in_seconds","getter_name":"durationInSeconds","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"album_id","getter_name":"albumId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"checksum","getter_name":"checksum","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_favorite","getter_name":"isFavorite","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_favorite\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_favorite\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"orientation","getter_name":"orientation","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":true,"constraints":[],"strict":true,"explicit_pk":["id","album_id"]}},{"id":24,"references":[15],"type":"index","data":{"on":15,"name":"idx_lat_lng","sql":"CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)","unique":false,"columns":[]}},{"id":25,"references":[23],"type":"index","data":{"on":23,"name":"idx_trashed_local_asset_checksum","sql":"CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)","unique":false,"columns":[]}},{"id":26,"references":[23],"type":"index","data":{"on":23,"name":"idx_trashed_local_asset_album","sql":"CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)","unique":false,"columns":[]}}]} \ No newline at end of file diff --git a/mobile/ios/.gitignore b/mobile/ios/.gitignore index e32cadbf68..f1a46a2fef 100644 --- a/mobile/ios/.gitignore +++ b/mobile/ios/.gitignore @@ -33,3 +33,4 @@ Runner/GeneratedPluginRegistrant.* !default.perspectivev3 fastlane/report.xml +Gemfile.lock \ No newline at end of file diff --git a/mobile/ios/Gemfile b/mobile/ios/Gemfile index 7a118b49be..3b6771ad35 100644 --- a/mobile/ios/Gemfile +++ b/mobile/ios/Gemfile @@ -1,3 +1,5 @@ source "https://rubygems.org" gem "fastlane" +gem "cocoapods" +gem "abbrev" # Required for Ruby 3.4+ \ No newline at end of file diff --git a/mobile/ios/Gemfile.lock b/mobile/ios/Gemfile.lock deleted file mode 100644 index 218b8c1355..0000000000 --- a/mobile/ios/Gemfile.lock +++ /dev/null @@ -1,218 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - CFPropertyList (3.0.7) - base64 - nkf - rexml - addressable (2.8.6) - public_suffix (>= 2.0.2, < 6.0) - artifactory (3.0.17) - atomos (0.1.3) - aws-eventstream (1.3.0) - aws-partitions (1.932.0) - aws-sdk-core (3.196.1) - aws-eventstream (~> 1, >= 1.3.0) - aws-partitions (~> 1, >= 1.651.0) - aws-sigv4 (~> 1.8) - jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.81.0) - aws-sdk-core (~> 3, >= 3.193.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.151.0) - aws-sdk-core (~> 3, >= 3.194.0) - aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.8) - aws-sigv4 (1.8.0) - aws-eventstream (~> 1, >= 1.0.2) - babosa (1.0.4) - base64 (0.2.0) - claide (1.1.0) - colored (1.2) - colored2 (3.1.2) - commander (4.6.0) - highline (~> 2.0.0) - declarative (0.0.20) - digest-crc (0.6.5) - rake (>= 12.0.0, < 14.0.0) - domain_name (0.6.20240107) - dotenv (2.8.1) - emoji_regex (3.2.3) - excon (0.110.0) - faraday (1.10.3) - faraday-em_http (~> 1.0) - faraday-em_synchrony (~> 1.0) - faraday-excon (~> 1.1) - faraday-httpclient (~> 1.0) - faraday-multipart (~> 1.0) - faraday-net_http (~> 1.0) - faraday-net_http_persistent (~> 1.0) - faraday-patron (~> 1.0) - faraday-rack (~> 1.0) - faraday-retry (~> 1.0) - ruby2_keywords (>= 0.0.4) - faraday-cookie_jar (0.0.7) - faraday (>= 0.8.0) - http-cookie (~> 1.0.0) - faraday-em_http (1.0.0) - faraday-em_synchrony (1.0.0) - faraday-excon (1.1.0) - faraday-httpclient (1.0.1) - faraday-multipart (1.0.4) - multipart-post (~> 2) - faraday-net_http (1.0.1) - faraday-net_http_persistent (1.2.0) - faraday-patron (1.0.0) - faraday-rack (1.0.0) - faraday-retry (1.0.3) - faraday_middleware (1.2.0) - faraday (~> 1.0) - fastimage (2.3.1) - fastlane (2.214.0) - CFPropertyList (>= 2.3, < 4.0.0) - addressable (>= 2.8, < 3.0.0) - artifactory (~> 3.0) - aws-sdk-s3 (~> 1.0) - babosa (>= 1.0.3, < 2.0.0) - bundler (>= 1.12.0, < 3.0.0) - colored - commander (~> 4.6) - dotenv (>= 2.1.1, < 3.0.0) - emoji_regex (>= 0.1, < 4.0) - excon (>= 0.71.0, < 1.0.0) - faraday (~> 1.0) - faraday-cookie_jar (~> 0.0.6) - faraday_middleware (~> 1.0) - fastimage (>= 2.1.0, < 3.0.0) - gh_inspector (>= 1.1.2, < 2.0.0) - google-apis-androidpublisher_v3 (~> 0.3) - google-apis-playcustomapp_v1 (~> 0.1) - google-cloud-storage (~> 1.31) - highline (~> 2.0) - json (< 3.0.0) - jwt (>= 2.1.0, < 3) - mini_magick (>= 4.9.4, < 5.0.0) - multipart-post (>= 2.0.0, < 3.0.0) - naturally (~> 2.2) - optparse (~> 0.1.1) - plist (>= 3.1.0, < 4.0.0) - rubyzip (>= 2.0.0, < 3.0.0) - security (= 0.1.3) - simctl (~> 1.6.3) - terminal-notifier (>= 2.0.0, < 3.0.0) - terminal-table (>= 1.4.5, < 2.0.0) - tty-screen (>= 0.6.3, < 1.0.0) - tty-spinner (>= 0.8.0, < 1.0.0) - word_wrap (~> 1.0.0) - xcodeproj (>= 1.13.0, < 2.0.0) - xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3) - gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.54.0) - google-apis-core (>= 0.11.0, < 2.a) - google-apis-core (0.11.3) - addressable (~> 2.5, >= 2.5.1) - googleauth (>= 0.16.2, < 2.a) - httpclient (>= 2.8.1, < 3.a) - mini_mime (~> 1.0) - representable (~> 3.0) - retriable (>= 2.0, < 4.a) - rexml - google-apis-iamcredentials_v1 (0.17.0) - google-apis-core (>= 0.11.0, < 2.a) - google-apis-playcustomapp_v1 (0.13.0) - google-apis-core (>= 0.11.0, < 2.a) - google-apis-storage_v1 (0.31.0) - google-apis-core (>= 0.11.0, < 2.a) - google-cloud-core (1.7.0) - google-cloud-env (>= 1.0, < 3.a) - google-cloud-errors (~> 1.0) - google-cloud-env (1.6.0) - faraday (>= 0.17.3, < 3.0) - google-cloud-errors (1.4.0) - google-cloud-storage (1.47.0) - addressable (~> 2.8) - digest-crc (~> 0.4) - google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.31.0) - google-cloud-core (~> 1.6) - googleauth (>= 0.16.2, < 2.a) - mini_mime (~> 1.0) - googleauth (1.8.1) - faraday (>= 0.17.3, < 3.a) - jwt (>= 1.4, < 3.0) - multi_json (~> 1.11) - os (>= 0.9, < 2.0) - signet (>= 0.16, < 2.a) - highline (2.0.3) - http-cookie (1.0.5) - domain_name (~> 0.5) - httpclient (2.8.3) - jmespath (1.6.2) - json (2.7.2) - jwt (2.8.1) - base64 - mini_magick (4.12.0) - mini_mime (1.1.5) - multi_json (1.15.0) - multipart-post (2.4.1) - nanaimo (0.3.0) - naturally (2.2.1) - nkf (0.2.0) - optparse (0.1.1) - os (1.1.4) - plist (3.7.1) - public_suffix (4.0.7) - rake (13.2.1) - representable (3.2.0) - declarative (< 0.1.0) - trailblazer-option (>= 0.1.1, < 0.2.0) - uber (< 0.2.0) - retriable (3.1.2) - rexml (3.3.6) - strscan - rouge (2.0.7) - ruby2_keywords (0.0.5) - rubyzip (2.3.2) - security (0.1.3) - signet (0.19.0) - addressable (~> 2.8) - faraday (>= 0.17.5, < 3.a) - jwt (>= 1.5, < 3.0) - multi_json (~> 1.10) - simctl (1.6.10) - CFPropertyList - naturally - strscan (3.1.0) - terminal-notifier (2.0.0) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) - trailblazer-option (0.1.2) - tty-cursor (0.7.1) - tty-screen (0.8.2) - tty-spinner (0.9.3) - tty-cursor (~> 0.7) - uber (0.1.0) - unicode-display_width (1.8.0) - word_wrap (1.0.0) - xcodeproj (1.25.0) - CFPropertyList (>= 2.3.3, < 4.0) - atomos (~> 0.1.3) - claide (>= 1.0.2, < 2.0) - colored2 (~> 3.1) - nanaimo (~> 0.3.0) - rexml (>= 3.3.2, < 4.0) - xcpretty (0.3.0) - rouge (~> 2.0.7) - xcpretty-travis-formatter (1.0.1) - xcpretty (~> 0.2, >= 0.0.7) - -PLATFORMS - x86_64-darwin-21 - x86_64-linux - -DEPENDENCIES - fastlane - -BUNDLED WITH - 2.3.7 diff --git a/mobile/ios/Podfile.lock b/mobile/ios/Podfile.lock index 502fd9008f..d869aa9c08 100644 --- a/mobile/ios/Podfile.lock +++ b/mobile/ios/Podfile.lock @@ -64,7 +64,7 @@ PODS: - Flutter - integration_test (0.0.1): - Flutter - - isar_flutter_libs (1.0.0): + - isar_community_flutter_libs (1.0.0): - Flutter - local_auth_darwin (0.0.1): - Flutter @@ -84,7 +84,7 @@ PODS: - FlutterMacOS - permission_handler_apple (9.3.0): - Flutter - - photo_manager (2.0.0): + - photo_manager (3.7.1): - Flutter - FlutterMacOS - SAMKeychain (1.5.3) @@ -149,7 +149,7 @@ DEPENDENCIES: - home_widget (from `.symlinks/plugins/home_widget/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - integration_test (from `.symlinks/plugins/integration_test/ios`) - - isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`) + - isar_community_flutter_libs (from `.symlinks/plugins/isar_community_flutter_libs/ios`) - local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`) - maplibre_gl (from `.symlinks/plugins/maplibre_gl/ios`) - native_video_player (from `.symlinks/plugins/native_video_player/ios`) @@ -210,8 +210,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/image_picker_ios/ios" integration_test: :path: ".symlinks/plugins/integration_test/ios" - isar_flutter_libs: - :path: ".symlinks/plugins/isar_flutter_libs/ios" + isar_community_flutter_libs: + :path: ".symlinks/plugins/isar_community_flutter_libs/ios" local_auth_darwin: :path: ".symlinks/plugins/local_auth_darwin/darwin" maplibre_gl: @@ -262,18 +262,18 @@ SPEC CHECKSUMS: fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1 geolocator_apple: 1560c3c875af2a412242c7a923e15d0d401966ff home_widget: f169fc41fd807b4d46ab6615dc44d62adbf9f64f - image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a + image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326 integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e - isar_flutter_libs: bc909e72c3d756c2759f14c8776c13b5b0556e26 + isar_community_flutter_libs: bede843185a61a05ff364a05c9b23209523f7e0d local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391 MapLibre: 69e572367f4ef6287e18246cfafc39c80cdcabcd maplibre_gl: 3c924e44725147b03dda33430ad216005b40555f native_video_player: b65c58951ede2f93d103a25366bdebca95081265 network_info_plus: cf61925ab5205dce05a4f0895989afdb6aade5fc package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 - path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d - photo_manager: d2fbcc0f2d82458700ee6256a15018210a81d413 + photo_manager: 1d80ae07a89a67dfbcae95953a1e5a24af7c3e62 SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868 share_handler_ios: e2244e990f826b2c8eaa291ac3831569438ba0fb @@ -285,7 +285,7 @@ SPEC CHECKSUMS: sqlite3_flutter_libs: f8fc13346870e73fe35ebf6dbb997fbcd156b241 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 url_launcher_ios: 694010445543906933d732453a59da0a173ae33d - wakelock_plus: 04623e3f525556020ebd4034310f20fe7fda8b49 + wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556 PODFILE CHECKSUM: 7ce312f2beab01395db96f6969d90a447279cf45 diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index 48826a20f1..599e7990f4 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -32,6 +32,9 @@ FEAFA8732E4D42F4001E47FE /* Thumbhash.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEAFA8722E4D42F4001E47FE /* Thumbhash.swift */; }; FED3B1962E253E9B0030FD97 /* ThumbnailsImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = FED3B1942E253E9B0030FD97 /* ThumbnailsImpl.swift */; }; FED3B1972E253E9B0030FD97 /* Thumbnails.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = FED3B1932E253E9B0030FD97 /* Thumbnails.g.swift */; }; + FEE084F82EC172460045228E /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084F72EC172460045228E /* SQLiteData */; }; + FEE084FB2EC1725A0045228E /* RawStructuredFieldValues in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084FA2EC1725A0045228E /* RawStructuredFieldValues */; }; + FEE084FD2EC1725A0045228E /* StructuredFieldValues in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084FC2EC1725A0045228E /* StructuredFieldValues */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -131,6 +134,13 @@ /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ + B231F52D2E93A44A00BC45D1 /* Core */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + ); + path = Core; + sourceTree = ""; + }; B2CF7F8C2DDE4EBB00744BF6 /* Sync */ = { isa = PBXFileSystemSynchronizedRootGroup; exceptions = ( @@ -146,6 +156,13 @@ path = WidgetExtension; sourceTree = ""; }; + FEE084F22EC172080045228E /* Schemas */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + ); + path = Schemas; + sourceTree = ""; + }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ @@ -153,6 +170,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + FEE084F82EC172460045228E /* SQLiteData in Frameworks */, + FEE084FB2EC1725A0045228E /* RawStructuredFieldValues in Frameworks */, + FEE084FD2EC1725A0045228E /* StructuredFieldValues in Frameworks */, D218389C4A4C4693F141F7D1 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -247,6 +267,8 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + FEE084F22EC172080045228E /* Schemas */, + B231F52D2E93A44A00BC45D1 /* Core */, B25D37792E72CA15008B6CA7 /* Connectivity */, B21E34A62E5AF9760031FDB9 /* Background */, B2CF7F8C2DDE4EBB00744BF6 /* Sync */, @@ -331,7 +353,9 @@ F0B57D482DF764BE00DC5BCC /* PBXTargetDependency */, ); fileSystemSynchronizedGroups = ( + B231F52D2E93A44A00BC45D1 /* Core */, B2CF7F8C2DDE4EBB00744BF6 /* Sync */, + FEE084F22EC172080045228E /* Schemas */, ); name = Runner; productName = Runner; @@ -410,6 +434,10 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + FEE084F62EC172460045228E /* XCRemoteSwiftPackageReference "sqlite-data" */, + FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */, + ); preferredProjectObjectVersion = 77; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; @@ -705,7 +733,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 224; + CURRENT_PROJECT_VERSION = 233; CUSTOM_GROUP_ID = group.app.immich.share; DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_BITCODE = NO; @@ -849,7 +877,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 224; + CURRENT_PROJECT_VERSION = 233; CUSTOM_GROUP_ID = group.app.immich.share; DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_BITCODE = NO; @@ -879,7 +907,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 224; + CURRENT_PROJECT_VERSION = 233; CUSTOM_GROUP_ID = group.app.immich.share; DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_BITCODE = NO; @@ -913,7 +941,7 @@ CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 224; + CURRENT_PROJECT_VERSION = 233; DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -956,7 +984,7 @@ CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 224; + CURRENT_PROJECT_VERSION = 233; DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -996,7 +1024,7 @@ CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 224; + CURRENT_PROJECT_VERSION = 233; DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -1035,7 +1063,7 @@ CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 224; + CURRENT_PROJECT_VERSION = 233; CUSTOM_GROUP_ID = group.app.immich.share; DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -1079,7 +1107,7 @@ CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 224; + CURRENT_PROJECT_VERSION = 233; CUSTOM_GROUP_ID = group.app.immich.share; DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -1120,7 +1148,7 @@ CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 224; + CURRENT_PROJECT_VERSION = 233; CUSTOM_GROUP_ID = group.app.immich.share; DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -1192,6 +1220,43 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + FEE084F62EC172460045228E /* XCRemoteSwiftPackageReference "sqlite-data" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/pointfreeco/sqlite-data"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.3.0; + }; + }; + FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-http-structured-headers.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.5.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + FEE084F72EC172460045228E /* SQLiteData */ = { + isa = XCSwiftPackageProductDependency; + package = FEE084F62EC172460045228E /* XCRemoteSwiftPackageReference "sqlite-data" */; + productName = SQLiteData; + }; + FEE084FA2EC1725A0045228E /* RawStructuredFieldValues */ = { + isa = XCSwiftPackageProductDependency; + package = FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */; + productName = RawStructuredFieldValues; + }; + FEE084FC2EC1725A0045228E /* StructuredFieldValues */ = { + isa = XCSwiftPackageProductDependency; + package = FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */; + productName = StructuredFieldValues; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000000..432e81234d --- /dev/null +++ b/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,177 @@ +{ + "originHash" : "9be33bfaa68721646604aefff3cabbdaf9a193da192aae024c265065671f6c49", + "pins" : [ + { + "identity" : "combine-schedulers", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/combine-schedulers", + "state" : { + "revision" : "fd16d76fd8b9a976d88bfb6cacc05ca8d19c91b6", + "version" : "1.1.0" + } + }, + { + "identity" : "grdb.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/groue/GRDB.swift", + "state" : { + "revision" : "18497b68fdbb3a09528d260a0a0e1e7e61c8c53d", + "version" : "7.8.0" + } + }, + { + "identity" : "opencombine", + "kind" : "remoteSourceControl", + "location" : "https://github.com/OpenCombine/OpenCombine.git", + "state" : { + "revision" : "8576f0d579b27020beccbccc3ea6844f3ddfc2c2", + "version" : "0.14.0" + } + }, + { + "identity" : "sqlite-data", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/sqlite-data", + "state" : { + "revision" : "b66b894b9a5710f1072c8eb6448a7edfc2d743d9", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-case-paths", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-case-paths", + "state" : { + "revision" : "6989976265be3f8d2b5802c722f9ba168e227c71", + "version" : "1.7.2" + } + }, + { + "identity" : "swift-clocks", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-clocks", + "state" : { + "revision" : "cc46202b53476d64e824e0b6612da09d84ffde8e", + "version" : "1.0.6" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections", + "state" : { + "revision" : "7b847a3b7008b2dc2f47ca3110d8c782fb2e5c7e", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-concurrency-extras", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-concurrency-extras", + "state" : { + "revision" : "5a3825302b1a0d744183200915a47b508c828e6f", + "version" : "1.3.2" + } + }, + { + "identity" : "swift-custom-dump", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-custom-dump", + "state" : { + "revision" : "82645ec760917961cfa08c9c0c7104a57a0fa4b1", + "version" : "1.3.3" + } + }, + { + "identity" : "swift-dependencies", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-dependencies", + "state" : { + "revision" : "a10f9feeb214bc72b5337b6ef6d5a029360db4cc", + "version" : "1.10.0" + } + }, + { + "identity" : "swift-http-structured-headers", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-structured-headers.git", + "state" : { + "revision" : "a9f3c352f4d46afd155e00b3c6e85decae6bcbeb", + "version" : "1.5.0" + } + }, + { + "identity" : "swift-identified-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-identified-collections", + "state" : { + "revision" : "322d9ffeeba85c9f7c4984b39422ec7cc3c56597", + "version" : "1.1.1" + } + }, + { + "identity" : "swift-perception", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-perception", + "state" : { + "revision" : "4f47ebafed5f0b0172cf5c661454fa8e28fb2ac4", + "version" : "2.0.9" + } + }, + { + "identity" : "swift-sharing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-sharing", + "state" : { + "revision" : "3bfc408cc2d0bee2287c174da6b1c76768377818", + "version" : "2.7.4" + } + }, + { + "identity" : "swift-snapshot-testing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-snapshot-testing", + "state" : { + "revision" : "a8b7c5e0ed33d8ab8887d1654d9b59f2cbad529b", + "version" : "1.18.7" + } + }, + { + "identity" : "swift-structured-queries", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-structured-queries", + "state" : { + "revision" : "9c84335373bae5f5c9f7b5f0adf3ae10f2cab5b9", + "version" : "0.25.2" + } + }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftlang/swift-syntax", + "state" : { + "revision" : "4799286537280063c85a32f09884cfbca301b1a1", + "version" : "602.0.0" + } + }, + { + "identity" : "swift-tagged", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-tagged", + "state" : { + "revision" : "3907a9438f5b57d317001dc99f3f11b46882272b", + "version" : "0.10.0" + } + }, + { + "identity" : "xctest-dynamic-overlay", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", + "state" : { + "revision" : "4c27acf5394b645b70d8ba19dc249c0472d5f618", + "version" : "1.7.0" + } + } + ], + "version" : 3 +} diff --git a/mobile/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/mobile/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000000..ff8a53ff4b --- /dev/null +++ b/mobile/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,168 @@ +{ + "originHash" : "9be33bfaa68721646604aefff3cabbdaf9a193da192aae024c265065671f6c49", + "pins" : [ + { + "identity" : "combine-schedulers", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/combine-schedulers", + "state" : { + "revision" : "5928286acce13def418ec36d05a001a9641086f2", + "version" : "1.0.3" + } + }, + { + "identity" : "grdb.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/groue/GRDB.swift", + "state" : { + "revision" : "18497b68fdbb3a09528d260a0a0e1e7e61c8c53d", + "version" : "7.8.0" + } + }, + { + "identity" : "sqlite-data", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/sqlite-data", + "state" : { + "revision" : "b66b894b9a5710f1072c8eb6448a7edfc2d743d9", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-case-paths", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-case-paths", + "state" : { + "revision" : "6989976265be3f8d2b5802c722f9ba168e227c71", + "version" : "1.7.2" + } + }, + { + "identity" : "swift-clocks", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-clocks", + "state" : { + "revision" : "cc46202b53476d64e824e0b6612da09d84ffde8e", + "version" : "1.0.6" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections", + "state" : { + "revision" : "7b847a3b7008b2dc2f47ca3110d8c782fb2e5c7e", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-concurrency-extras", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-concurrency-extras", + "state" : { + "revision" : "5a3825302b1a0d744183200915a47b508c828e6f", + "version" : "1.3.2" + } + }, + { + "identity" : "swift-custom-dump", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-custom-dump", + "state" : { + "revision" : "82645ec760917961cfa08c9c0c7104a57a0fa4b1", + "version" : "1.3.3" + } + }, + { + "identity" : "swift-dependencies", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-dependencies", + "state" : { + "revision" : "a10f9feeb214bc72b5337b6ef6d5a029360db4cc", + "version" : "1.10.0" + } + }, + { + "identity" : "swift-http-structured-headers", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-structured-headers.git", + "state" : { + "revision" : "a9f3c352f4d46afd155e00b3c6e85decae6bcbeb", + "version" : "1.5.0" + } + }, + { + "identity" : "swift-identified-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-identified-collections", + "state" : { + "revision" : "322d9ffeeba85c9f7c4984b39422ec7cc3c56597", + "version" : "1.1.1" + } + }, + { + "identity" : "swift-perception", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-perception", + "state" : { + "revision" : "4f47ebafed5f0b0172cf5c661454fa8e28fb2ac4", + "version" : "2.0.9" + } + }, + { + "identity" : "swift-sharing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-sharing", + "state" : { + "revision" : "3bfc408cc2d0bee2287c174da6b1c76768377818", + "version" : "2.7.4" + } + }, + { + "identity" : "swift-snapshot-testing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-snapshot-testing", + "state" : { + "revision" : "a8b7c5e0ed33d8ab8887d1654d9b59f2cbad529b", + "version" : "1.18.7" + } + }, + { + "identity" : "swift-structured-queries", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-structured-queries", + "state" : { + "revision" : "1447ea20550f6f02c4b48cc80931c3ed40a9c756", + "version" : "0.25.0" + } + }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftlang/swift-syntax", + "state" : { + "revision" : "4799286537280063c85a32f09884cfbca301b1a1", + "version" : "602.0.0" + } + }, + { + "identity" : "swift-tagged", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-tagged", + "state" : { + "revision" : "3907a9438f5b57d317001dc99f3f11b46882272b", + "version" : "0.10.0" + } + }, + { + "identity" : "xctest-dynamic-overlay", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", + "state" : { + "revision" : "4c27acf5394b645b70d8ba19dc249c0472d5f618", + "version" : "1.7.0" + } + } + ], + "version" : 3 +} diff --git a/mobile/ios/Runner/AppDelegate.swift b/mobile/ios/Runner/AppDelegate.swift index 3476030923..4e4cb2ed13 100644 --- a/mobile/ios/Runner/AppDelegate.swift +++ b/mobile/ios/Runner/AppDelegate.swift @@ -20,7 +20,7 @@ import UIKit GeneratedPluginRegistrant.register(with: self) let controller: FlutterViewController = window?.rootViewController as! FlutterViewController - AppDelegate.registerPlugins(binaryMessenger: controller.binaryMessenger) + AppDelegate.registerPlugins(with: controller.engine) BackgroundServicePlugin.register(with: self.registrar(forPlugin: "BackgroundServicePlugin")!) BackgroundServicePlugin.registerBackgroundProcessing() @@ -51,9 +51,13 @@ import UIKit return super.application(application, didFinishLaunchingWithOptions: launchOptions) } - public static func registerPlugins(binaryMessenger: FlutterBinaryMessenger) { - NativeSyncApiSetup.setUp(binaryMessenger: binaryMessenger, api: NativeSyncApiImpl()) - ThumbnailApiSetup.setUp(binaryMessenger: binaryMessenger, api: ThumbnailApiImpl()) - BackgroundWorkerFgHostApiSetup.setUp(binaryMessenger: binaryMessenger, api: BackgroundWorkerApiImpl()) + public static func registerPlugins(with engine: FlutterEngine) { + NativeSyncApiImpl.register(with: engine.registrar(forPlugin: NativeSyncApiImpl.name)!) + ThumbnailApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: ThumbnailApiImpl()) + BackgroundWorkerFgHostApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: BackgroundWorkerApiImpl()) + } + + public static func cancelPlugins(with engine: FlutterEngine) { + (engine.valuePublished(byPlugin: NativeSyncApiImpl.name) as? NativeSyncApiImpl)?.detachFromEngine() } } diff --git a/mobile/ios/Runner/Background/BackgroundWorker.g.swift b/mobile/ios/Runner/Background/BackgroundWorker.g.swift index ece5cd5f64..8c9391e8d2 100644 --- a/mobile/ios/Runner/Background/BackgroundWorker.g.swift +++ b/mobile/ios/Runner/Background/BackgroundWorker.g.swift @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v26.0.0), do not edit directly. +// Autogenerated from Pigeon (v26.0.2), do not edit directly. // See also: https://pub.dev/packages/pigeon import Foundation @@ -182,6 +182,7 @@ class BackgroundWorkerPigeonCodec: FlutterStandardMessageCodec, @unchecked Senda /// Generated protocol from Pigeon that represents a handler of messages from Flutter. protocol BackgroundWorkerFgHostApi { func enable() throws + func saveNotificationMessage(title: String, body: String) throws func configure(settings: BackgroundWorkerSettings) throws func disable() throws } @@ -205,6 +206,22 @@ class BackgroundWorkerFgHostApiSetup { } else { enableChannel.setMessageHandler(nil) } + let saveNotificationMessageChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.saveNotificationMessage\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + saveNotificationMessageChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let titleArg = args[0] as! String + let bodyArg = args[1] as! String + do { + try api.saveNotificationMessage(title: titleArg, body: bodyArg) + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + saveNotificationMessageChannel.setMessageHandler(nil) + } let configureChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.configure\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) if let api = api { configureChannel.setMessageHandler { message, reply in @@ -238,7 +255,6 @@ class BackgroundWorkerFgHostApiSetup { /// Generated protocol from Pigeon that represents a handler of messages from Flutter. protocol BackgroundWorkerBgHostApi { func onInitialized() throws - func showNotification(title: String, content: String) throws func close() throws } @@ -261,22 +277,6 @@ class BackgroundWorkerBgHostApiSetup { } else { onInitializedChannel.setMessageHandler(nil) } - let showNotificationChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.showNotification\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) - if let api = api { - showNotificationChannel.setMessageHandler { message, reply in - let args = message as! [Any?] - let titleArg = args[0] as! String - let contentArg = args[1] as! String - do { - try api.showNotification(title: titleArg, content: contentArg) - reply(wrapResult(nil)) - } catch { - reply(wrapError(error)) - } - } - } else { - showNotificationChannel.setMessageHandler(nil) - } let closeChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.close\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) if let api = api { closeChannel.setMessageHandler { _, reply in diff --git a/mobile/ios/Runner/Background/BackgroundWorker.swift b/mobile/ios/Runner/Background/BackgroundWorker.swift index c3268b4a2b..7dc450d76e 100644 --- a/mobile/ios/Runner/Background/BackgroundWorker.swift +++ b/mobile/ios/Runner/Background/BackgroundWorker.swift @@ -95,7 +95,7 @@ class BackgroundWorker: BackgroundWorkerBgHostApi { // Register plugins in the new engine GeneratedPluginRegistrant.register(with: engine) // Register custom plugins - AppDelegate.registerPlugins(binaryMessenger: engine.binaryMessenger) + AppDelegate.registerPlugins(with: engine) flutterApi = BackgroundWorkerFlutterApi(binaryMessenger: engine.binaryMessenger) BackgroundWorkerBgHostApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: self) @@ -119,10 +119,6 @@ class BackgroundWorker: BackgroundWorkerBgHostApi { }) } - func showNotification(title: String, content: String) throws { - // No-op on iOS for the time being - } - /** * Cancels the currently running background task, either due to timeout or external request. * Sends a cancel signal to the Flutter side and sets up a fallback timer to ensure @@ -172,6 +168,7 @@ class BackgroundWorker: BackgroundWorkerBgHostApi { } isComplete = true + AppDelegate.cancelPlugins(with: engine) engine.destroyContext() flutterApi = nil completionHandler(success) diff --git a/mobile/ios/Runner/Background/BackgroundWorkerApiImpl.swift b/mobile/ios/Runner/Background/BackgroundWorkerApiImpl.swift index f7f8f69989..a7bbc31ceb 100644 --- a/mobile/ios/Runner/Background/BackgroundWorkerApiImpl.swift +++ b/mobile/ios/Runner/Background/BackgroundWorkerApiImpl.swift @@ -12,6 +12,10 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi { // Android only } + func saveNotificationMessage(title: String, body: String) throws { + // Android only + } + func disable() throws { BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: BackgroundWorkerApiImpl.refreshTaskID); BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: BackgroundWorkerApiImpl.processingTaskID); diff --git a/mobile/ios/Runner/Connectivity/Connectivity.g.swift b/mobile/ios/Runner/Connectivity/Connectivity.g.swift index 45333f03d8..f8d85a2edf 100644 --- a/mobile/ios/Runner/Connectivity/Connectivity.g.swift +++ b/mobile/ios/Runner/Connectivity/Connectivity.g.swift @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v26.0.0), do not edit directly. +// Autogenerated from Pigeon (v26.0.2), do not edit directly. // See also: https://pub.dev/packages/pigeon import Foundation diff --git a/mobile/ios/Runner/Core/ImmichPlugin.swift b/mobile/ios/Runner/Core/ImmichPlugin.swift new file mode 100644 index 0000000000..db10b7a75d --- /dev/null +++ b/mobile/ios/Runner/Core/ImmichPlugin.swift @@ -0,0 +1,17 @@ +class ImmichPlugin: NSObject { + var detached: Bool + + override init() { + detached = false + super.init() + } + + func detachFromEngine() { + self.detached = true + } + + func completeWhenActive(for completion: @escaping (T) -> Void, with value: T) { + guard !self.detached else { return } + completion(value) + } +} diff --git a/mobile/ios/Runner/Images/Thumbnails.g.swift b/mobile/ios/Runner/Images/Thumbnails.g.swift index be40a18b41..fbaef294d3 100644 --- a/mobile/ios/Runner/Images/Thumbnails.g.swift +++ b/mobile/ios/Runner/Images/Thumbnails.g.swift @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v26.0.0), do not edit directly. +// Autogenerated from Pigeon (v26.0.2), do not edit directly. // See also: https://pub.dev/packages/pigeon import Foundation diff --git a/mobile/ios/Runner/Info.plist b/mobile/ios/Runner/Info.plist index c1e6ec212d..7a3a9261ae 100644 --- a/mobile/ios/Runner/Info.plist +++ b/mobile/ios/Runner/Info.plist @@ -80,7 +80,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.142.1 + 2.2.1 CFBundleSignature ???? CFBundleURLTypes @@ -107,7 +107,7 @@ CFBundleVersion - 224 + 233 FLTEnableImpeller ITSAppUsesNonExemptEncryption diff --git a/mobile/ios/Runner/Schemas/Constants.swift b/mobile/ios/Runner/Schemas/Constants.swift new file mode 100644 index 0000000000..a4b0f701a1 --- /dev/null +++ b/mobile/ios/Runner/Schemas/Constants.swift @@ -0,0 +1,177 @@ +import SQLiteData + +struct Endpoint: Codable { + let url: URL + let status: Status + + enum Status: String, Codable { + case loading, valid, error, unknown + } +} + +enum StoreKey: Int, CaseIterable, QueryBindable { + // MARK: - Int + case _version = 0 + static let version = Typed(rawValue: ._version) + case _deviceIdHash = 3 + static let deviceIdHash = Typed(rawValue: ._deviceIdHash) + case _backupTriggerDelay = 8 + static let backupTriggerDelay = Typed(rawValue: ._backupTriggerDelay) + case _tilesPerRow = 103 + static let tilesPerRow = Typed(rawValue: ._tilesPerRow) + case _groupAssetsBy = 105 + static let groupAssetsBy = Typed(rawValue: ._groupAssetsBy) + case _uploadErrorNotificationGracePeriod = 106 + static let uploadErrorNotificationGracePeriod = Typed(rawValue: ._uploadErrorNotificationGracePeriod) + case _thumbnailCacheSize = 110 + static let thumbnailCacheSize = Typed(rawValue: ._thumbnailCacheSize) + case _imageCacheSize = 111 + static let imageCacheSize = Typed(rawValue: ._imageCacheSize) + case _albumThumbnailCacheSize = 112 + static let albumThumbnailCacheSize = Typed(rawValue: ._albumThumbnailCacheSize) + case _selectedAlbumSortOrder = 113 + static let selectedAlbumSortOrder = Typed(rawValue: ._selectedAlbumSortOrder) + case _logLevel = 115 + static let logLevel = Typed(rawValue: ._logLevel) + case _mapRelativeDate = 119 + static let mapRelativeDate = Typed(rawValue: ._mapRelativeDate) + case _mapThemeMode = 124 + static let mapThemeMode = Typed(rawValue: ._mapThemeMode) + + // MARK: - String + case _assetETag = 1 + static let assetETag = Typed(rawValue: ._assetETag) + case _currentUser = 2 + static let currentUser = Typed(rawValue: ._currentUser) + case _deviceId = 4 + static let deviceId = Typed(rawValue: ._deviceId) + case _accessToken = 11 + static let accessToken = Typed(rawValue: ._accessToken) + case _serverEndpoint = 12 + static let serverEndpoint = Typed(rawValue: ._serverEndpoint) + case _sslClientCertData = 15 + static let sslClientCertData = Typed(rawValue: ._sslClientCertData) + case _sslClientPasswd = 16 + static let sslClientPasswd = Typed(rawValue: ._sslClientPasswd) + case _themeMode = 102 + static let themeMode = Typed(rawValue: ._themeMode) + case _customHeaders = 127 + static let customHeaders = Typed<[String: String]>(rawValue: ._customHeaders) + case _primaryColor = 128 + static let primaryColor = Typed(rawValue: ._primaryColor) + case _preferredWifiName = 133 + static let preferredWifiName = Typed(rawValue: ._preferredWifiName) + + // MARK: - Endpoint + case _externalEndpointList = 135 + static let externalEndpointList = Typed<[Endpoint]>(rawValue: ._externalEndpointList) + + // MARK: - URL + case _localEndpoint = 134 + static let localEndpoint = Typed(rawValue: ._localEndpoint) + case _serverUrl = 10 + static let serverUrl = Typed(rawValue: ._serverUrl) + + // MARK: - Date + case _backupFailedSince = 5 + static let backupFailedSince = Typed(rawValue: ._backupFailedSince) + + // MARK: - Bool + case _backupRequireWifi = 6 + static let backupRequireWifi = Typed(rawValue: ._backupRequireWifi) + case _backupRequireCharging = 7 + static let backupRequireCharging = Typed(rawValue: ._backupRequireCharging) + case _autoBackup = 13 + static let autoBackup = Typed(rawValue: ._autoBackup) + case _backgroundBackup = 14 + static let backgroundBackup = Typed(rawValue: ._backgroundBackup) + case _loadPreview = 100 + static let loadPreview = Typed(rawValue: ._loadPreview) + case _loadOriginal = 101 + static let loadOriginal = Typed(rawValue: ._loadOriginal) + case _dynamicLayout = 104 + static let dynamicLayout = Typed(rawValue: ._dynamicLayout) + case _backgroundBackupTotalProgress = 107 + static let backgroundBackupTotalProgress = Typed(rawValue: ._backgroundBackupTotalProgress) + case _backgroundBackupSingleProgress = 108 + static let backgroundBackupSingleProgress = Typed(rawValue: ._backgroundBackupSingleProgress) + case _storageIndicator = 109 + static let storageIndicator = Typed(rawValue: ._storageIndicator) + case _advancedTroubleshooting = 114 + static let advancedTroubleshooting = Typed(rawValue: ._advancedTroubleshooting) + case _preferRemoteImage = 116 + static let preferRemoteImage = Typed(rawValue: ._preferRemoteImage) + case _loopVideo = 117 + static let loopVideo = Typed(rawValue: ._loopVideo) + case _mapShowFavoriteOnly = 118 + static let mapShowFavoriteOnly = Typed(rawValue: ._mapShowFavoriteOnly) + case _selfSignedCert = 120 + static let selfSignedCert = Typed(rawValue: ._selfSignedCert) + case _mapIncludeArchived = 121 + static let mapIncludeArchived = Typed(rawValue: ._mapIncludeArchived) + case _ignoreIcloudAssets = 122 + static let ignoreIcloudAssets = Typed(rawValue: ._ignoreIcloudAssets) + case _selectedAlbumSortReverse = 123 + static let selectedAlbumSortReverse = Typed(rawValue: ._selectedAlbumSortReverse) + case _mapwithPartners = 125 + static let mapwithPartners = Typed(rawValue: ._mapwithPartners) + case _enableHapticFeedback = 126 + static let enableHapticFeedback = Typed(rawValue: ._enableHapticFeedback) + case _dynamicTheme = 129 + static let dynamicTheme = Typed(rawValue: ._dynamicTheme) + case _colorfulInterface = 130 + static let colorfulInterface = Typed(rawValue: ._colorfulInterface) + case _syncAlbums = 131 + static let syncAlbums = Typed(rawValue: ._syncAlbums) + case _autoEndpointSwitching = 132 + static let autoEndpointSwitching = Typed(rawValue: ._autoEndpointSwitching) + case _loadOriginalVideo = 136 + static let loadOriginalVideo = Typed(rawValue: ._loadOriginalVideo) + case _manageLocalMediaAndroid = 137 + static let manageLocalMediaAndroid = Typed(rawValue: ._manageLocalMediaAndroid) + case _readonlyModeEnabled = 138 + static let readonlyModeEnabled = Typed(rawValue: ._readonlyModeEnabled) + case _autoPlayVideo = 139 + static let autoPlayVideo = Typed(rawValue: ._autoPlayVideo) + case _photoManagerCustomFilter = 1000 + static let photoManagerCustomFilter = Typed(rawValue: ._photoManagerCustomFilter) + case _betaPromptShown = 1001 + static let betaPromptShown = Typed(rawValue: ._betaPromptShown) + case _betaTimeline = 1002 + static let betaTimeline = Typed(rawValue: ._betaTimeline) + case _enableBackup = 1003 + static let enableBackup = Typed(rawValue: ._enableBackup) + case _useWifiForUploadVideos = 1004 + static let useWifiForUploadVideos = Typed(rawValue: ._useWifiForUploadVideos) + case _useWifiForUploadPhotos = 1005 + static let useWifiForUploadPhotos = Typed(rawValue: ._useWifiForUploadPhotos) + case _needBetaMigration = 1006 + static let needBetaMigration = Typed(rawValue: ._needBetaMigration) + case _shouldResetSync = 1007 + static let shouldResetSync = Typed(rawValue: ._shouldResetSync) + + struct Typed: RawRepresentable { + let rawValue: StoreKey + + @_transparent + init(rawValue value: StoreKey) { + self.rawValue = value + } + } +} + +enum BackupSelection: Int, QueryBindable { + case selected, none, excluded +} + +enum AvatarColor: Int, QueryBindable { + case primary, pink, red, yellow, blue, green, purple, orange, gray, amber +} + +enum AlbumUserRole: Int, QueryBindable { + case editor, viewer +} + +enum MemoryType: Int, QueryBindable { + case onThisDay +} diff --git a/mobile/ios/Runner/Schemas/Store.swift b/mobile/ios/Runner/Schemas/Store.swift new file mode 100644 index 0000000000..ee5280b6c0 --- /dev/null +++ b/mobile/ios/Runner/Schemas/Store.swift @@ -0,0 +1,146 @@ +import SQLiteData + +enum StoreError: Error { + case invalidJSON(String) + case invalidURL(String) + case encodingFailed +} + +protocol StoreConvertible { + associatedtype StorageType + static func fromValue(_ value: StorageType) throws(StoreError) -> Self + static func toValue(_ value: Self) throws(StoreError) -> StorageType +} + +extension Int: StoreConvertible { + static func fromValue(_ value: Int) -> Int { value } + static func toValue(_ value: Int) -> Int { value } +} + +extension Bool: StoreConvertible { + static func fromValue(_ value: Int) -> Bool { value == 1 } + static func toValue(_ value: Bool) -> Int { value ? 1 : 0 } +} + +extension Date: StoreConvertible { + static func fromValue(_ value: Int) -> Date { Date(timeIntervalSince1970: TimeInterval(value) / 1000) } + static func toValue(_ value: Date) -> Int { Int(value.timeIntervalSince1970 * 1000) } +} + +extension String: StoreConvertible { + static func fromValue(_ value: String) -> String { value } + static func toValue(_ value: String) -> String { value } +} + +extension URL: StoreConvertible { + static func fromValue(_ value: String) throws(StoreError) -> URL { + guard let url = URL(string: value) else { + throw StoreError.invalidURL(value) + } + return url + } + static func toValue(_ value: URL) -> String { value.absoluteString } +} + +extension StoreConvertible where Self: Codable, StorageType == String { + static var jsonDecoder: JSONDecoder { JSONDecoder() } + static var jsonEncoder: JSONEncoder { JSONEncoder() } + + static func fromValue(_ value: String) throws(StoreError) -> Self { + do { + return try jsonDecoder.decode(Self.self, from: Data(value.utf8)) + } catch { + throw StoreError.invalidJSON(value) + } + } + + static func toValue(_ value: Self) throws(StoreError) -> String { + let encoded: Data + do { + encoded = try jsonEncoder.encode(value) + } catch { + throw StoreError.encodingFailed + } + + guard let string = String(data: encoded, encoding: .utf8) else { + throw StoreError.encodingFailed + } + return string + } +} + +extension Array: StoreConvertible where Element: Codable { + typealias StorageType = String +} + +extension Dictionary: StoreConvertible where Key == String, Value: Codable { + typealias StorageType = String +} + +class StoreRepository { + private let db: DatabasePool + + init(db: DatabasePool) { + self.db = db + } + + func get(_ key: StoreKey.Typed) throws -> T? where T.StorageType == Int { + let query = Store.select(\.intValue).where { $0.id.eq(key.rawValue) } + if let value = try db.read({ conn in try query.fetchOne(conn) }) ?? nil { + return try T.fromValue(value) + } + return nil + } + + func get(_ key: StoreKey.Typed) throws -> T? where T.StorageType == String { + let query = Store.select(\.stringValue).where { $0.id.eq(key.rawValue) } + if let value = try db.read({ conn in try query.fetchOne(conn) }) ?? nil { + return try T.fromValue(value) + } + return nil + } + + func get(_ key: StoreKey.Typed) async throws -> T? where T.StorageType == Int { + let query = Store.select(\.intValue).where { $0.id.eq(key.rawValue) } + if let value = try await db.read({ conn in try query.fetchOne(conn) }) ?? nil { + return try T.fromValue(value) + } + return nil + } + + func get(_ key: StoreKey.Typed) async throws -> T? where T.StorageType == String { + let query = Store.select(\.stringValue).where { $0.id.eq(key.rawValue) } + if let value = try await db.read({ conn in try query.fetchOne(conn) }) ?? nil { + return try T.fromValue(value) + } + return nil + } + + func set(_ key: StoreKey.Typed, value: T) throws where T.StorageType == Int { + let value = try T.toValue(value) + try db.write { conn in + try Store.upsert { Store(id: key.rawValue, stringValue: nil, intValue: value) }.execute(conn) + } + } + + func set(_ key: StoreKey.Typed, value: T) throws where T.StorageType == String { + let value = try T.toValue(value) + try db.write { conn in + try Store.upsert { Store(id: key.rawValue, stringValue: value, intValue: nil) }.execute(conn) + } + } + + func set(_ key: StoreKey.Typed, value: T) async throws where T.StorageType == Int { + let value = try T.toValue(value) + try await db.write { conn in + try Store.upsert { Store(id: key.rawValue, stringValue: nil, intValue: value) }.execute(conn) + } + } + + func set(_ key: StoreKey.Typed, value: T) async throws where T.StorageType == String { + let value = try T.toValue(value) + try await db.write { conn in + try Store.upsert { Store(id: key.rawValue, stringValue: value, intValue: nil) }.execute(conn) + } + } +} diff --git a/mobile/ios/Runner/Schemas/Tables.swift b/mobile/ios/Runner/Schemas/Tables.swift new file mode 100644 index 0000000000..c256b0d0ed --- /dev/null +++ b/mobile/ios/Runner/Schemas/Tables.swift @@ -0,0 +1,237 @@ +import GRDB +import SQLiteData + +@Table("asset_face_entity") +struct AssetFace { + let id: String + let assetId: String + let personId: String? + let imageWidth: Int + let imageHeight: Int + let boundingBoxX1: Int + let boundingBoxY1: Int + let boundingBoxX2: Int + let boundingBoxY2: Int + let sourceType: String +} + +@Table("auth_user_entity") +struct AuthUser { + let id: String + let name: String + let email: String + let isAdmin: Bool + let hasProfileImage: Bool + let profileChangedAt: Date + let avatarColor: AvatarColor + let quotaSizeInBytes: Int + let quotaUsageInBytes: Int + let pinCode: String? +} + +@Table("local_album_entity") +struct LocalAlbum { + let id: String + let backupSelection: BackupSelection + let linkedRemoteAlbumId: String? + let marker_: Bool? + let name: String + let isIosSharedAlbum: Bool + let updatedAt: Date +} + +@Table("local_album_asset_entity") +struct LocalAlbumAsset { + let id: ID + let marker_: String? + + @Selection + struct ID { + let assetId: String + let albumId: String + } +} + +@Table("local_asset_entity") +struct LocalAsset { + let id: String + let checksum: String? + let createdAt: Date + let durationInSeconds: Int? + let height: Int? + let isFavorite: Bool + let name: String + let orientation: String + let type: Int + let updatedAt: Date + let width: Int? +} + +@Table("memory_asset_entity") +struct MemoryAsset { + let id: ID + + @Selection + struct ID { + let assetId: String + let albumId: String + } +} + +@Table("memory_entity") +struct Memory { + let id: String + let createdAt: Date + let updatedAt: Date + let deletedAt: Date? + let ownerId: String + let type: MemoryType + let data: String + let isSaved: Bool + let memoryAt: Date + let seenAt: Date? + let showAt: Date? + let hideAt: Date? +} + +@Table("partner_entity") +struct Partner { + let id: ID + let inTimeline: Bool + + @Selection + struct ID { + let sharedById: String + let sharedWithId: String + } +} + +@Table("person_entity") +struct Person { + let id: String + let createdAt: Date + let updatedAt: Date + let ownerId: String + let name: String + let faceAssetId: String? + let isFavorite: Bool + let isHidden: Bool + let color: String? + let birthDate: Date? +} + +@Table("remote_album_entity") +struct RemoteAlbum { + let id: String + let createdAt: Date + let description: String? + let isActivityEnabled: Bool + let name: String + let order: Int + let ownerId: String + let thumbnailAssetId: String? + let updatedAt: Date +} + +@Table("remote_album_asset_entity") +struct RemoteAlbumAsset { + let id: ID + + @Selection + struct ID { + let assetId: String + let albumId: String + } +} + +@Table("remote_album_user_entity") +struct RemoteAlbumUser { + let id: ID + let role: AlbumUserRole + + @Selection + struct ID { + let albumId: String + let userId: String + } +} + +@Table("remote_asset_entity") +struct RemoteAsset { + let id: String + let checksum: String? + let deletedAt: Date? + let isFavorite: Int + let libraryId: String? + let livePhotoVideoId: String? + let localDateTime: Date? + let orientation: String + let ownerId: String + let stackId: String? + let visibility: Int +} + +@Table("remote_exif_entity") +struct RemoteExif { + @Column(primaryKey: true) + let assetId: String + let city: String? + let state: String? + let country: String? + let dateTimeOriginal: Date? + let description: String? + let height: Int? + let width: Int? + let exposureTime: String? + let fNumber: Double? + let fileSize: Int? + let focalLength: Double? + let latitude: Double? + let longitude: Double? + let iso: Int? + let make: String? + let model: String? + let lens: String? + let orientation: String? + let timeZone: String? + let rating: Int? + let projectionType: String? +} + +@Table("stack_entity") +struct Stack { + let id: String + let createdAt: Date + let updatedAt: Date + let ownerId: String + let primaryAssetId: String +} + +@Table("store_entity") +struct Store { + let id: StoreKey + let stringValue: String? + let intValue: Int? +} + +@Table("user_entity") +struct User { + let id: String + let name: String + let email: String + let hasProfileImage: Bool + let profileChangedAt: Date + let avatarColor: AvatarColor +} + +@Table("user_metadata_entity") +struct UserMetadata { + let id: ID + let value: Data + + @Selection + struct ID { + let userId: String + let key: Date + } +} diff --git a/mobile/ios/Runner/Sync/Messages.g.swift b/mobile/ios/Runner/Sync/Messages.g.swift index 305aca5266..bbe18e7375 100644 --- a/mobile/ios/Runner/Sync/Messages.g.swift +++ b/mobile/ios/Runner/Sync/Messages.g.swift @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v26.0.0), do not edit directly. +// Autogenerated from Pigeon (v26.0.2), do not edit directly. // See also: https://pub.dev/packages/pigeon import Foundation @@ -364,6 +364,7 @@ protocol NativeSyncApi { func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset] func hashAssets(assetIds: [String], allowNetworkAccess: Bool, completion: @escaping (Result<[HashResult], Error>) -> Void) func cancelHashing() throws + func getTrashedAssets() throws -> [String: [PlatformAsset]] } /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. @@ -532,5 +533,20 @@ class NativeSyncApiSetup { } else { cancelHashingChannel.setMessageHandler(nil) } + let getTrashedAssetsChannel = taskQueue == nil + ? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + : FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue) + if let api = api { + getTrashedAssetsChannel.setMessageHandler { _, reply in + do { + let result = try api.getTrashedAssets() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getTrashedAssetsChannel.setMessageHandler(nil) + } } } diff --git a/mobile/ios/Runner/Sync/MessagesImpl.swift b/mobile/ios/Runner/Sync/MessagesImpl.swift index bb23bae6b6..03493f57ca 100644 --- a/mobile/ios/Runner/Sync/MessagesImpl.swift +++ b/mobile/ios/Runner/Sync/MessagesImpl.swift @@ -3,35 +3,47 @@ import CryptoKit struct AssetWrapper: Hashable, Equatable { let asset: PlatformAsset - + init(with asset: PlatformAsset) { self.asset = asset } - + func hash(into hasher: inout Hasher) { hasher.combine(self.asset.id) } - + static func == (lhs: AssetWrapper, rhs: AssetWrapper) -> Bool { return lhs.asset.id == rhs.asset.id } } -class NativeSyncApiImpl: NativeSyncApi { +class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { + static let name = "NativeSyncApi" + + static func register(with registrar: any FlutterPluginRegistrar) { + let instance = NativeSyncApiImpl() + NativeSyncApiSetup.setUp(binaryMessenger: registrar.messenger(), api: instance) + registrar.publish(instance) + } + + func detachFromEngine(for registrar: any FlutterPluginRegistrar) { + super.detachFromEngine() + } + private let defaults: UserDefaults private let changeTokenKey = "immich:changeToken" private let albumTypes: [PHAssetCollectionType] = [.album, .smartAlbum] private let recoveredAlbumSubType = 1000000219 - - private var hashTask: Task? + + private var hashTask: Task? private static let hashCancelledCode = "HASH_CANCELLED" private static let hashCancelled = Result<[HashResult], Error>.failure(PigeonError(code: hashCancelledCode, message: "Hashing cancelled", details: nil)) - - + + init(with defaults: UserDefaults = .standard) { self.defaults = defaults } - + @available(iOS 16, *) private func getChangeToken() -> PHPersistentChangeToken? { guard let data = defaults.data(forKey: changeTokenKey) else { @@ -39,7 +51,7 @@ class NativeSyncApiImpl: NativeSyncApi { } return try? NSKeyedUnarchiver.unarchivedObject(ofClass: PHPersistentChangeToken.self, from: data) } - + @available(iOS 16, *) private func saveChangeToken(token: PHPersistentChangeToken) -> Void { guard let data = try? NSKeyedArchiver.archivedData(withRootObject: token, requiringSecureCoding: true) else { @@ -47,18 +59,18 @@ class NativeSyncApiImpl: NativeSyncApi { } defaults.set(data, forKey: changeTokenKey) } - + func clearSyncCheckpoint() -> Void { defaults.removeObject(forKey: changeTokenKey) } - + func checkpointSync() { guard #available(iOS 16, *) else { return } saveChangeToken(token: PHPhotoLibrary.shared().currentChangeToken) } - + func shouldFullSync() -> Bool { guard #available(iOS 16, *), PHPhotoLibrary.authorizationStatus(for: .readWrite) == .authorized, @@ -66,34 +78,36 @@ class NativeSyncApiImpl: NativeSyncApi { // When we do not have access to photo library, older iOS version or No token available, fallback to full sync return true } - + guard let _ = try? PHPhotoLibrary.shared().fetchPersistentChanges(since: storedToken) else { // Cannot fetch persistent changes return true } - + return false } - + func getAlbums() throws -> [PlatformAlbum] { var albums: [PlatformAlbum] = [] - + albumTypes.forEach { type in let collections = PHAssetCollection.fetchAssetCollections(with: type, subtype: .any, options: nil) for i in 0.. SyncDelta { guard #available(iOS 16, *) else { throw PigeonError(code: "UNSUPPORTED_OS", message: "This feature requires iOS 16 or later.", details: nil) } - + guard PHPhotoLibrary.authorizationStatus(for: .readWrite) == .authorized else { throw PigeonError(code: "NO_AUTH", message: "No photo library access", details: nil) } - + guard let storedToken = getChangeToken() else { // No token exists, definitely need a full sync print("MediaManager::getMediaChanges: No token found") throw PigeonError(code: "NO_TOKEN", message: "No stored change token", details: nil) } - + let currentToken = PHPhotoLibrary.shared().currentChangeToken if storedToken == currentToken { return SyncDelta(hasChanges: false, updates: [], deletes: [], assetAlbums: [:]) } - + do { let changes = try PHPhotoLibrary.shared().fetchPersistentChanges(since: storedToken) - + var updatedAssets: Set = [] var deletedAssets: Set = [] - + for change in changes { guard let details = try? change.changeDetails(for: PHObjectType.asset) else { continue } - + let updated = details.updatedLocalIdentifiers.union(details.insertedLocalIdentifiers) deletedAssets.formUnion(details.deletedLocalIdentifiers) - + if (updated.isEmpty) { continue } - + let options = PHFetchOptions() options.includeHiddenAssets = false let result = PHAsset.fetchAssets(withLocalIdentifiers: Array(updated), options: options) for i in 0..) -> [String: [String]] { guard !assets.isEmpty else { return [:] } - + var albumAssets: [String: [String]] = [:] - + for type in albumTypes { let collections = PHAssetCollection.fetchAssetCollections(with: type, subtype: .any, options: nil) collections.enumerateObjects { (album, _, _) in let options = PHFetchOptions() options.predicate = NSPredicate(format: "localIdentifier IN %@", assets.map(\.id)) options.includeHiddenAssets = false - let result = PHAsset.fetchAssets(in: album, options: options) + let result = self.getAssetsFromAlbum(in: album, options: options) result.enumerateObjects { (asset, _, _) in albumAssets[asset.localIdentifier, default: []].append(album.localIdentifier) } @@ -197,62 +211,62 @@ class NativeSyncApiImpl: NativeSyncApi { } return albumAssets } - + func getAssetIdsForAlbum(albumId: String) throws -> [String] { let collections = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [albumId], options: nil) guard let album = collections.firstObject else { return [] } - + var ids: [String] = [] let options = PHFetchOptions() options.includeHiddenAssets = false - let assets = PHAsset.fetchAssets(in: album, options: options) + let assets = getAssetsFromAlbum(in: album, options: options) assets.enumerateObjects { (asset, _, _) in ids.append(asset.localIdentifier) } return ids } - + func getAssetsCountSince(albumId: String, timestamp: Int64) throws -> Int64 { let collections = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [albumId], options: nil) guard let album = collections.firstObject else { return 0 } - + let date = NSDate(timeIntervalSince1970: TimeInterval(timestamp)) let options = PHFetchOptions() options.predicate = NSPredicate(format: "creationDate > %@ OR modificationDate > %@", date, date) options.includeHiddenAssets = false - let assets = PHAsset.fetchAssets(in: album, options: options) + let assets = getAssetsFromAlbum(in: album, options: options) return Int64(assets.count) } - + func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset] { let collections = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [albumId], options: nil) guard let album = collections.firstObject else { return [] } - + let options = PHFetchOptions() options.includeHiddenAssets = false if(updatedTimeCond != nil) { let date = NSDate(timeIntervalSince1970: TimeInterval(updatedTimeCond!)) options.predicate = NSPredicate(format: "creationDate > %@ OR modificationDate > %@", date, date) } - - let result = PHAsset.fetchAssets(in: album, options: options) + + let result = getAssetsFromAlbum(in: album, options: options) if(result.count == 0) { return [] } - + var assets: [PlatformAsset] = [] result.enumerateObjects { (asset, _, _) in assets.append(asset.toPlatformAsset()) } return assets } - + func hashAssets(assetIds: [String], allowNetworkAccess: Bool, completion: @escaping (Result<[HashResult], Error>) -> Void) { if let prevTask = hashTask { prevTask.cancel() @@ -270,45 +284,45 @@ class NativeSyncApiImpl: NativeSyncApi { missingAssetIds.remove(asset.localIdentifier) assets.append(asset) } - + if Task.isCancelled { - return completion(Self.hashCancelled) + return self?.completeWhenActive(for: completion, with: Self.hashCancelled) } - + await withTaskGroup(of: HashResult?.self) { taskGroup in var results = [HashResult]() results.reserveCapacity(assets.count) for asset in assets { if Task.isCancelled { - return completion(Self.hashCancelled) + return self?.completeWhenActive(for: completion, with: Self.hashCancelled) } taskGroup.addTask { guard let self = self else { return nil } return await self.hashAsset(asset, allowNetworkAccess: allowNetworkAccess) } } - + for await result in taskGroup { guard let result = result else { - return completion(Self.hashCancelled) + return self?.completeWhenActive(for: completion, with: Self.hashCancelled) } results.append(result) } - + for missing in missingAssetIds { results.append(HashResult(assetId: missing, error: "Asset not found in library", hash: nil)) } - - completion(.success(results)) + + return self?.completeWhenActive(for: completion, with: .success(results)) } } } - + func cancelHashing() { hashTask?.cancel() hashTask = nil } - + private func hashAsset(_ asset: PHAsset, allowNetworkAccess: Bool) async -> HashResult? { class RequestRef { var id: PHAssetResourceDataRequestID? @@ -318,21 +332,21 @@ class NativeSyncApiImpl: NativeSyncApi { if Task.isCancelled { return nil } - + guard let resource = asset.getResource() else { return HashResult(assetId: asset.localIdentifier, error: "Cannot get asset resource", hash: nil) } - + if Task.isCancelled { return nil } - + let options = PHAssetResourceRequestOptions() options.isNetworkAccessAllowed = allowNetworkAccess - + return await withCheckedContinuation { continuation in var hasher = Insecure.SHA1() - + requestRef.id = PHAssetResourceManager.default().requestData( for: resource, options: options, @@ -363,4 +377,17 @@ class NativeSyncApiImpl: NativeSyncApi { PHAssetResourceManager.default().cancelDataRequest(requestId) }) } + + func getTrashedAssets() throws -> [String: [PlatformAsset]] { + throw PigeonError(code: "UNSUPPORTED_OS", message: "This feature not supported on iOS.", details: nil) + } + + private func getAssetsFromAlbum(in album: PHAssetCollection, options: PHFetchOptions) -> PHFetchResult { + // Ensure to actually getting all assets for the Recents album + if (album.assetCollectionSubtype == .smartAlbumUserLibrary) { + return PHAsset.fetchAssets(with: options) + } else { + return PHAsset.fetchAssets(in: album, options: options) + } + } } diff --git a/mobile/ios/WidgetExtension/widgets/MemoryWidget.swift b/mobile/ios/WidgetExtension/widgets/MemoryWidget.swift index d0a3e8c29d..22414fbec4 100644 --- a/mobile/ios/WidgetExtension/widgets/MemoryWidget.swift +++ b/mobile/ios/WidgetExtension/widgets/MemoryWidget.swift @@ -114,7 +114,7 @@ struct ImmichMemoryProvider: TimelineProvider { } } - // If we didnt add any memory images (some failure occured or no images in memory), + // If we didn't add any memory images (some failure occurred or no images in memory), // default to 12 hours of random photos if entries.count == 0 { // this must be a do/catch since we need to diff --git a/mobile/ios/fastlane/Fastfile b/mobile/ios/fastlane/Fastfile index b1071cc988..d167d5fb2d 100644 --- a/mobile/ios/fastlane/Fastfile +++ b/mobile/ios/fastlane/Fastfile @@ -16,23 +16,231 @@ default_platform(:ios) platform :ios do - desc "iOS Release" - lane :release do + # Constants + TEAM_ID = "2F67MQ8R79" + CODE_SIGN_IDENTITY = "Apple Distribution: Hau Tran (#{TEAM_ID})" + BASE_BUNDLE_ID = "app.alextran.immich" + + # Helper method to get App Store Connect API key + def get_api_key + app_store_connect_api_key( + key_id: ENV["APP_STORE_CONNECT_API_KEY_ID"], + issuer_id: ENV["APP_STORE_CONNECT_API_KEY_ISSUER_ID"], + key_filepath: "#{Dir.home}/.appstoreconnect/private_keys/AuthKey_#{ENV['APP_STORE_CONNECT_API_KEY_ID']}.p8", + duration: 1200, + in_house: false + ) + end + + # Helper method to get version from pubspec.yaml +def get_version_from_pubspec + require 'yaml' + + pubspec_path = File.join(Dir.pwd, "../..", "pubspec.yaml") + pubspec = YAML.load_file(pubspec_path) + + version_string = pubspec['version'] + version_string ? version_string.split('+').first : nil +end + + # Helper method to configure code signing for all targets + def configure_code_signing(bundle_id_suffix: "") + bundle_suffix = bundle_id_suffix.empty? ? "" : ".#{bundle_id_suffix}" + + # Runner (main app) + update_code_signing_settings( + use_automatic_signing: false, + path: "./Runner.xcodeproj", + team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID, + code_sign_identity: CODE_SIGN_IDENTITY, + bundle_identifier: "#{BASE_BUNDLE_ID}#{bundle_suffix}", + profile_name: "#{BASE_BUNDLE_ID}#{bundle_suffix} AppStore", + targets: ["Runner"] + ) + + # ShareExtension + update_code_signing_settings( + use_automatic_signing: false, + path: "./Runner.xcodeproj", + team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID, + code_sign_identity: CODE_SIGN_IDENTITY, + bundle_identifier: "#{BASE_BUNDLE_ID}#{bundle_suffix}.ShareExtension", + profile_name: "#{BASE_BUNDLE_ID}#{bundle_suffix}.ShareExtension AppStore", + targets: ["ShareExtension"] + ) + + # WidgetExtension + update_code_signing_settings( + use_automatic_signing: false, + path: "./Runner.xcodeproj", + team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID, + code_sign_identity: CODE_SIGN_IDENTITY, + bundle_identifier: "#{BASE_BUNDLE_ID}#{bundle_suffix}.Widget", + profile_name: "#{BASE_BUNDLE_ID}#{bundle_suffix}.Widget AppStore", + targets: ["WidgetExtension"] + ) + end + + # Helper method to build and upload to TestFlight + def build_and_upload( + api_key:, + bundle_id_suffix: "", + configuration: "Release", + distribute_external: true, + version_number: nil + ) + bundle_suffix = bundle_id_suffix.empty? ? "" : ".#{bundle_id_suffix}" + app_identifier = "#{BASE_BUNDLE_ID}#{bundle_suffix}" + + # Set version number if provided + if version_number + increment_version_number(version_number: version_number) + end + + # Increment build number + increment_build_number( + build_number: latest_testflight_build_number( + api_key: api_key, + app_identifier: app_identifier + ) + 1, + xcodeproj: "./Runner.xcodeproj" + ) + + # Build the app + build_app( + scheme: "Runner", + workspace: "Runner.xcworkspace", + configuration: configuration, + export_method: "app-store", + xcargs: "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual", + export_options: { + provisioningProfiles: { + "#{app_identifier}" => "#{app_identifier} AppStore", + "#{app_identifier}.ShareExtension" => "#{app_identifier}.ShareExtension AppStore", + "#{app_identifier}.Widget" => "#{app_identifier}.Widget AppStore" + }, + signingStyle: "manual", + signingCertificate: CODE_SIGN_IDENTITY + } + ) + + # Upload to TestFlight + upload_to_testflight( + api_key: api_key, + skip_waiting_for_build_processing: true, + distribute_external: distribute_external + ) + end + + desc "iOS Development Build to TestFlight (requires separate bundle ID)" + lane :gha_testflight_dev do + api_key = get_api_key + + # Install development provisioning profiles + install_provisioning_profile(path: "profile_dev.mobileprovision") + install_provisioning_profile(path: "profile_dev_share.mobileprovision") + install_provisioning_profile(path: "profile_dev_widget.mobileprovision") + + # Configure code signing for dev bundle IDs + configure_code_signing(bundle_id_suffix: "development") + + # Build and upload + build_and_upload( + api_key: api_key, + bundle_id_suffix: "development", + configuration: "Profile", + distribute_external: false + ) + end + + desc "iOS Release to TestFlight" + lane :gha_release_prod do + api_key = get_api_key + + # Install provisioning profiles + install_provisioning_profile(path: "profile.mobileprovision") + install_provisioning_profile(path: "profile_share.mobileprovision") + install_provisioning_profile(path: "profile_widget.mobileprovision") + + + # Configure code signing for production bundle IDs + configure_code_signing + + # Build and upload with version number + build_and_upload( + api_key: api_key, + version_number: get_version_from_pubspec, + distribute_external: false, + ) + end + + desc "iOS Manual Release" + lane :release_manual do enable_automatic_code_signing( path: "./Runner.xcodeproj", + targets: ["Runner", "ShareExtension", "WidgetExtension"] ) + increment_version_number( - version_number: "1.143.0" + version_number: get_version_from_pubspec ) increment_build_number( build_number: latest_testflight_build_number + 1, ) - build_app(scheme: "Runner", - workspace: "Runner.xcworkspace", - xcargs: "-allowProvisioningUpdates") + + # Build archive with automatic signing + gym( + scheme: "Runner", + workspace: "Runner.xcworkspace", + configuration: "Release", + export_method: "app-store", + skip_package_ipa: false, + xcargs: "-skipMacroValidation -allowProvisioningUpdates", + export_options: { + method: "app-store", + signingStyle: "automatic", + uploadBitcode: false, + uploadSymbols: true, + compileBitcode: false + } + ) + upload_to_testflight( skip_waiting_for_build_processing: true ) end + desc "iOS Build Only (no TestFlight upload)" + lane :gha_build_only do + # Use the same build process as production, just skip the upload + # This ensures PR builds validate the same way as production builds + + # Install provisioning profiles (use development profiles for PR builds) + install_provisioning_profile(path: "profile_dev.mobileprovision") + install_provisioning_profile(path: "profile_dev_share.mobileprovision") + install_provisioning_profile(path: "profile_dev_widget.mobileprovision") + + # Configure code signing for dev bundle IDs + configure_code_signing(bundle_id_suffix: "development") + + # Build the app (same as gha_testflight_dev but without upload) + build_app( + scheme: "Runner", + workspace: "Runner.xcworkspace", + configuration: "Release", + export_method: "app-store", + skip_package_ipa: true, + xcargs: "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual", + export_options: { + provisioningProfiles: { + "#{BASE_BUNDLE_ID}.development" => "#{BASE_BUNDLE_ID}.development AppStore", + "#{BASE_BUNDLE_ID}.development.ShareExtension" => "#{BASE_BUNDLE_ID}.development.ShareExtension AppStore", + "#{BASE_BUNDLE_ID}.development.Widget" => "#{BASE_BUNDLE_ID}.development.Widget AppStore" + }, + signingStyle: "manual", + signingCertificate: CODE_SIGN_IDENTITY + } + ) + end + end diff --git a/mobile/ios/fastlane/README.md b/mobile/ios/fastlane/README.md index 2999821730..5fc8101b3a 100644 --- a/mobile/ios/fastlane/README.md +++ b/mobile/ios/fastlane/README.md @@ -15,13 +15,29 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do ## iOS -### ios release +### ios gha_testflight_dev ```sh -[bundle exec] fastlane ios release +[bundle exec] fastlane ios gha_testflight_dev ``` -iOS Release +iOS Development Build to TestFlight (requires separate bundle ID) + +### ios gha_release_prod + +```sh +[bundle exec] fastlane ios gha_release_prod +``` + +iOS Release to TestFlight + +### ios release_manual + +```sh +[bundle exec] fastlane ios release_manual +``` + +iOS Manual Release ---- diff --git a/mobile/lib/constants/constants.dart b/mobile/lib/constants/constants.dart index 7429616f14..8d4636bbe1 100644 --- a/mobile/lib/constants/constants.dart +++ b/mobile/lib/constants/constants.dart @@ -49,3 +49,15 @@ const double kUploadStatusFailed = -1.0; const double kUploadStatusCanceled = -2.0; const int kMinMonthsToEnableScrubberSnap = 12; + +const String kImmichAppStoreLink = "https://apps.apple.com/app/immich/id6449244941"; +const String kImmichPlayStoreLink = "https://play.google.com/store/apps/details?id=app.alextran.immich"; +const String kImmichLatestRelease = "https://github.com/immich-app/immich/releases/latest"; + +const int kPhotoTabIndex = 0; +const int kSearchTabIndex = 1; +const int kAlbumTabIndex = 2; +const int kLibraryTabIndex = 3; + +// Workaround for SQLite's variable limit (SQLITE_MAX_VARIABLE_NUMBER = 32766) +const int kDriftMaxChunk = 32000; diff --git a/mobile/lib/constants/enums.dart b/mobile/lib/constants/enums.dart index 6ec0ce37ea..91ca50a2c0 100644 --- a/mobile/lib/constants/enums.dart +++ b/mobile/lib/constants/enums.dart @@ -1,6 +1,6 @@ enum SortOrder { asc, desc } -enum TextSearchType { context, filename, description } +enum TextSearchType { context, filename, description, ocr } enum AssetVisibilityEnum { timeline, hidden, archive, locked } diff --git a/mobile/lib/domain/models/asset/base_asset.model.dart b/mobile/lib/domain/models/asset/base_asset.model.dart index 4d40be2d32..5774a13c90 100644 --- a/mobile/lib/domain/models/asset/base_asset.model.dart +++ b/mobile/lib/domain/models/asset/base_asset.model.dart @@ -56,6 +56,8 @@ sealed class BaseAsset { // Overridden in subclasses AssetState get storage; + String? get localId; + String? get remoteId; String get heroTag; @override diff --git a/mobile/lib/domain/models/asset/local_asset.model.dart b/mobile/lib/domain/models/asset/local_asset.model.dart index 9cd20acb0a..6f2f4c06ba 100644 --- a/mobile/lib/domain/models/asset/local_asset.model.dart +++ b/mobile/lib/domain/models/asset/local_asset.model.dart @@ -2,12 +2,12 @@ part of 'base_asset.model.dart'; class LocalAsset extends BaseAsset { final String id; - final String? remoteId; + final String? remoteAssetId; final int orientation; const LocalAsset({ required this.id, - this.remoteId, + String? remoteId, required super.name, super.checksum, required super.type, @@ -19,7 +19,13 @@ class LocalAsset extends BaseAsset { super.isFavorite = false, super.livePhotoVideoId, this.orientation = 0, - }); + }) : remoteAssetId = remoteId; + + @override + String? get localId => id; + + @override + String? get remoteId => remoteAssetId; @override AssetState get storage => remoteId == null ? AssetState.local : AssetState.merged; diff --git a/mobile/lib/domain/models/asset/remote_asset.model.dart b/mobile/lib/domain/models/asset/remote_asset.model.dart index 8648255167..4974dc9118 100644 --- a/mobile/lib/domain/models/asset/remote_asset.model.dart +++ b/mobile/lib/domain/models/asset/remote_asset.model.dart @@ -5,7 +5,7 @@ enum AssetVisibility { timeline, hidden, archive, locked } // Model for an asset stored in the server class RemoteAsset extends BaseAsset { final String id; - final String? localId; + final String? localAssetId; final String? thumbHash; final AssetVisibility visibility; final String ownerId; @@ -13,7 +13,7 @@ class RemoteAsset extends BaseAsset { const RemoteAsset({ required this.id, - this.localId, + String? localId, required super.name, required this.ownerId, required super.checksum, @@ -28,7 +28,13 @@ class RemoteAsset extends BaseAsset { this.visibility = AssetVisibility.timeline, super.livePhotoVideoId, this.stackId, - }); + }) : localAssetId = localId; + + @override + String? get localId => localAssetId; + + @override + String? get remoteId => id; @override AssetState get storage => localId == null ? AssetState.remote : AssetState.merged; diff --git a/mobile/lib/domain/models/search_result.model.dart b/mobile/lib/domain/models/search_result.model.dart index bae8b8e821..947bc6192f 100644 --- a/mobile/lib/domain/models/search_result.model.dart +++ b/mobile/lib/domain/models/search_result.model.dart @@ -3,27 +3,30 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; class SearchResult { final List assets; + final double scrollOffset; final int? nextPage; - const SearchResult({required this.assets, this.nextPage}); + const SearchResult({required this.assets, this.scrollOffset = 0.0, this.nextPage}); - int get totalAssets => assets.length; - - SearchResult copyWith({List? assets, int? nextPage}) { - return SearchResult(assets: assets ?? this.assets, nextPage: nextPage ?? this.nextPage); + SearchResult copyWith({List? assets, int? nextPage, double? scrollOffset}) { + return SearchResult( + assets: assets ?? this.assets, + nextPage: nextPage ?? this.nextPage, + scrollOffset: scrollOffset ?? this.scrollOffset, + ); } @override - String toString() => 'SearchResult(assets: $assets, nextPage: $nextPage)'; + String toString() => 'SearchResult(assets: ${assets.length}, nextPage: $nextPage, scrollOffset: $scrollOffset)'; @override bool operator ==(covariant SearchResult other) { if (identical(this, other)) return true; final listEquals = const DeepCollectionEquality().equals; - return listEquals(other.assets, assets) && other.nextPage == nextPage; + return listEquals(other.assets, assets) && other.nextPage == nextPage && other.scrollOffset == scrollOffset; } @override - int get hashCode => assets.hashCode ^ nextPage.hashCode; + int get hashCode => assets.hashCode ^ nextPage.hashCode ^ scrollOffset.hashCode; } diff --git a/mobile/lib/domain/models/setting.model.dart b/mobile/lib/domain/models/setting.model.dart index f427d93285..2c46507331 100644 --- a/mobile/lib/domain/models/setting.model.dart +++ b/mobile/lib/domain/models/setting.model.dart @@ -6,6 +6,7 @@ enum Setting { showStorageIndicator(StoreKey.storageIndicator, true), loadOriginal(StoreKey.loadOriginal, false), loadOriginalVideo(StoreKey.loadOriginalVideo, false), + autoPlayVideo(StoreKey.autoPlayVideo, true), preferRemoteImage(StoreKey.preferRemoteImage, false), advancedTroubleshooting(StoreKey.advancedTroubleshooting, false), enableBackup(StoreKey.enableBackup, false); diff --git a/mobile/lib/domain/models/store.model.dart b/mobile/lib/domain/models/store.model.dart index 62cb887c27..8e2f937f64 100644 --- a/mobile/lib/domain/models/store.model.dart +++ b/mobile/lib/domain/models/store.model.dart @@ -72,6 +72,7 @@ enum StoreKey { // Album grid/list view settings albumGridView._(139), + autoPlayVideo._(140), // Experimental stuff photoManagerCustomFilter._(1000), diff --git a/mobile/lib/domain/services/asset.service.dart b/mobile/lib/domain/services/asset.service.dart index 84f675f68b..7f8ade313c 100644 --- a/mobile/lib/domain/services/asset.service.dart +++ b/mobile/lib/domain/services/asset.service.dart @@ -84,8 +84,8 @@ class AssetService { return 1.0; } - Future> getPlaces() { - return _remoteAssetRepository.getPlaces(); + Future> getPlaces(String userId) { + return _remoteAssetRepository.getPlaces(userId); } Future<(int local, int remote)> getAssetCounts() async { diff --git a/mobile/lib/domain/services/background_worker.service.dart b/mobile/lib/domain/services/background_worker.service.dart index 942581633f..28c87293f9 100644 --- a/mobile/lib/domain/services/background_worker.service.dart +++ b/mobile/lib/domain/services/background_worker.service.dart @@ -11,8 +11,6 @@ import 'package:immich_mobile/domain/services/log.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/network_capability_extensions.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; -import 'package:immich_mobile/extensions/translate_extensions.dart'; -import 'package:immich_mobile/generated/intl_keys.g.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart'; import 'package:immich_mobile/platform/background_worker_api.g.dart'; @@ -28,14 +26,13 @@ import 'package:immich_mobile/repositories/file_media.repository.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/auth.service.dart'; import 'package:immich_mobile/services/localization.service.dart'; -import 'package:immich_mobile/services/server_info.service.dart'; import 'package:immich_mobile/services/upload.service.dart'; import 'package:immich_mobile/utils/bootstrap.dart'; import 'package:immich_mobile/utils/debug_print.dart'; import 'package:immich_mobile/utils/http_ssl_options.dart'; +import 'package:immich_mobile/wm_executor.dart'; import 'package:isar/isar.dart'; import 'package:logging/logging.dart'; -import 'package:worker_manager/worker_manager.dart'; class BackgroundWorkerFgService { final BackgroundWorkerFgHostApi _foregroundHostApi; @@ -45,6 +42,9 @@ class BackgroundWorkerFgService { // TODO: Move this call to native side once old timeline is removed Future enable() => _foregroundHostApi.enable(); + Future saveNotificationMessage(String title, String body) => + _foregroundHostApi.saveNotificationMessage(title, body); + Future configure({int? minimumDelaySeconds, bool? requireCharging}) => _foregroundHostApi.configure( BackgroundWorkerSettings( minimumDelaySeconds: @@ -94,7 +94,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { await Future.wait( [ loadTranslations(), - workerManager.init(dynamicSpawning: true), + workerManagerPatch.init(dynamicSpawning: true), _ref?.read(authServiceProvider).setOpenApiServiceEndpoint(), // Initialize the file downloader FileDownloader().configure( @@ -113,47 +113,43 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { configureFileDownloaderNotifications(); - if (Platform.isAndroid) { - await _backgroundHostApi.showNotification( - IntlKeys.uploading_media.t(), - IntlKeys.backup_background_service_in_progress_notification.t(), - ); - } - // Notify the host that the background worker service has been initialized and is ready to use - _backgroundHostApi.onInitialized(); + unawaited(_backgroundHostApi.onInitialized()); } catch (error, stack) { _logger.severe("Failed to initialize background worker", error, stack); - _backgroundHostApi.close(); + unawaited(_backgroundHostApi.close()); } } @override Future onAndroidUpload() async { + _logger.info('Android background processing started'); + final sw = Stopwatch()..start(); try { - _logger.info('Android background processing started'); - final sw = Stopwatch()..start(); - - await _syncAssets(hashTimeout: Duration(minutes: _isBackupEnabled ? 3 : 6)); + if (!await _syncAssets(hashTimeout: Duration(minutes: _isBackupEnabled ? 3 : 6))) { + _logger.warning("Remote sync did not complete successfully, skipping backup"); + return; + } await _handleBackup(); - - sw.stop(); - _logger.info("Android background processing completed in ${sw.elapsed.inSeconds}s"); } catch (error, stack) { _logger.severe("Failed to complete Android background processing", error, stack); } finally { + sw.stop(); + _logger.info("Android background processing completed in ${sw.elapsed.inSeconds}s"); await _cleanup(); } } @override Future onIosUpload(bool isRefresh, int? maxSeconds) async { + _logger.info('iOS background upload started with maxSeconds: ${maxSeconds}s'); + final sw = Stopwatch()..start(); try { - _logger.info('iOS background upload started with maxSeconds: ${maxSeconds}s'); - final sw = Stopwatch()..start(); - final timeout = isRefresh ? const Duration(seconds: 5) : Duration(minutes: _isBackupEnabled ? 3 : 6); - await _syncAssets(hashTimeout: timeout); + if (!await _syncAssets(hashTimeout: timeout)) { + _logger.warning("Remote sync did not complete successfully, skipping backup"); + return; + } final backupFuture = _handleBackup(); if (maxSeconds != null) { @@ -161,12 +157,11 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { } else { await backupFuture; } - - sw.stop(); - _logger.info("iOS background upload completed in ${sw.elapsed.inSeconds}s"); } catch (error, stack) { _logger.severe("Failed to complete iOS background upload", error, stack); } finally { + sw.stop(); + _logger.info("iOS background upload completed in ${sw.elapsed.inSeconds}s"); await _cleanup(); } } @@ -197,7 +192,8 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { _cancellationToken.cancel(); _logger.info("Cleaning up background worker"); final cleanupFutures = [ - workerManager.dispose().catchError((_) async { + nativeSyncApi?.cancelHashing(), + workerManagerPatch.dispose().catchError((_) async { // Discard any errors on the dispose call return; }), @@ -206,7 +202,6 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { _drift.close(), _driftLogger.close(), backgroundSyncManager?.cancel(), - nativeSyncApi?.cancelHashing(), ]; if (_isar.isOpen) { @@ -227,33 +222,24 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { } if (!_isBackupEnabled) { - _logger.info("[_handleBackup 1] Backup is disabled. Skipping backup routine"); + _logger.info("Backup is disabled. Skipping backup routine"); return; } - _logger.info("[_handleBackup 2] Enqueuing assets for backup from the background service"); - final currentUser = _ref?.read(currentUserProvider); if (currentUser == null) { - _logger.warning("[_handleBackup 3] No current user found. Skipping backup from background"); + _logger.warning("No current user found. Skipping backup from background"); return; } - _logger.info("[_handleBackup 4] Resume backup from background"); if (Platform.isIOS) { return _ref?.read(driftBackupProvider.notifier).handleBackupResume(currentUser.id); } - final canPing = await _ref?.read(serverInfoServiceProvider).ping() ?? false; - if (!canPing) { - _logger.warning("[_handleBackup 5] Server is not reachable. Skipping backup from background"); - return; - } - final networkCapabilities = await _ref?.read(connectivityApiProvider).getCapabilities() ?? []; return _ref ?.read(uploadServiceProvider) - .startBackupWithHttpClient(currentUser.id, networkCapabilities.hasWifi, _cancellationToken); + .startBackupWithHttpClient(currentUser.id, networkCapabilities.isUnmetered, _cancellationToken); }, (error, stack) { dPrint(() => "Error in backup zone $error, $stack"); @@ -261,15 +247,15 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { ); } - Future _syncAssets({Duration? hashTimeout}) async { + Future _syncAssets({Duration? hashTimeout}) async { await _ref?.read(backgroundSyncProvider).syncLocal(); if (_isCleanedUp) { - return; + return false; } - await _ref?.read(backgroundSyncProvider).syncRemote(); + final isSuccess = await _ref?.read(backgroundSyncProvider).syncRemote() ?? false; if (_isCleanedUp) { - return; + return isSuccess; } var hashFuture = _ref?.read(backgroundSyncProvider).hashAssets(); @@ -283,6 +269,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { } await hashFuture; + return isSuccess; } } diff --git a/mobile/lib/domain/services/hash.service.dart b/mobile/lib/domain/services/hash.service.dart index 90f29b8bc1..5e81643fc5 100644 --- a/mobile/lib/domain/services/hash.service.dart +++ b/mobile/lib/domain/services/hash.service.dart @@ -2,8 +2,10 @@ import 'package:flutter/services.dart'; import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart'; import 'package:immich_mobile/platform/native_sync_api.g.dart'; import 'package:logging/logging.dart'; @@ -13,6 +15,7 @@ class HashService { final int _batchSize; final DriftLocalAlbumRepository _localAlbumRepository; final DriftLocalAssetRepository _localAssetRepository; + final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository; final NativeSyncApi _nativeSyncApi; final bool Function()? _cancelChecker; final _log = Logger('HashService'); @@ -20,11 +23,13 @@ class HashService { HashService({ required DriftLocalAlbumRepository localAlbumRepository, required DriftLocalAssetRepository localAssetRepository, + required DriftTrashedLocalAssetRepository trashedLocalAssetRepository, required NativeSyncApi nativeSyncApi, bool Function()? cancelChecker, int? batchSize, }) : _localAlbumRepository = localAlbumRepository, _localAssetRepository = localAssetRepository, + _trashedLocalAssetRepository = trashedLocalAssetRepository, _cancelChecker = cancelChecker, _nativeSyncApi = nativeSyncApi, _batchSize = batchSize ?? kBatchHashFileLimit; @@ -49,6 +54,14 @@ class HashService { await _hashAssets(album, assetsToHash); } } + if (CurrentPlatform.isAndroid && localAlbums.isNotEmpty) { + final backupAlbumIds = localAlbums.map((e) => e.id); + final trashedToHash = await _trashedLocalAssetRepository.getAssetsToHash(backupAlbumIds); + if (trashedToHash.isNotEmpty) { + final pseudoAlbum = LocalAlbum(id: '-pseudoAlbum', name: 'Trash', updatedAt: DateTime.now()); + await _hashAssets(pseudoAlbum, trashedToHash, isTrashed: true); + } + } } on PlatformException catch (e) { if (e.code == _kHashCancelledCode) { _log.warning("Hashing cancelled by platform"); @@ -65,7 +78,7 @@ class HashService { /// Processes a list of [LocalAsset]s, storing their hash and updating the assets in the DB /// with hash for those that were successfully hashed. Hashes are looked up in a table /// [LocalAssetHashEntity] by local id. Only missing entries are newly hashed and added to the DB. - Future _hashAssets(LocalAlbum album, List assetsToHash) async { + Future _hashAssets(LocalAlbum album, List assetsToHash, {bool isTrashed = false}) async { final toHash = {}; for (final asset in assetsToHash) { @@ -76,16 +89,16 @@ class HashService { toHash[asset.id] = asset; if (toHash.length == _batchSize) { - await _processBatch(album, toHash); + await _processBatch(album, toHash, isTrashed); toHash.clear(); } } - await _processBatch(album, toHash); + await _processBatch(album, toHash, isTrashed); } /// Processes a batch of assets. - Future _processBatch(LocalAlbum album, Map toHash) async { + Future _processBatch(LocalAlbum album, Map toHash, bool isTrashed) async { if (toHash.isEmpty) { return; } @@ -120,7 +133,10 @@ class HashService { } _log.fine("Hashed ${hashed.length}/${toHash.length} assets"); - - await _localAssetRepository.updateHashes(hashed); + if (isTrashed) { + await _trashedLocalAssetRepository.updateHashes(hashed); + } else { + await _localAssetRepository.updateHashes(hashed); + } } } diff --git a/mobile/lib/domain/services/local_sync.service.dart b/mobile/lib/domain/services/local_sync.service.dart index d333af7481..5cbae9c5a1 100644 --- a/mobile/lib/domain/services/local_sync.service.dart +++ b/mobile/lib/domain/services/local_sync.service.dart @@ -4,9 +4,14 @@ import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart'; import 'package:immich_mobile/platform/native_sync_api.g.dart'; +import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; import 'package:immich_mobile/utils/datetime_helpers.dart'; import 'package:immich_mobile/utils/diff.dart'; import 'package:logging/logging.dart'; @@ -14,15 +19,34 @@ import 'package:logging/logging.dart'; class LocalSyncService { final DriftLocalAlbumRepository _localAlbumRepository; final NativeSyncApi _nativeSyncApi; + final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository; + final LocalFilesManagerRepository _localFilesManager; + final StorageRepository _storageRepository; final Logger _log = Logger("DeviceSyncService"); - LocalSyncService({required DriftLocalAlbumRepository localAlbumRepository, required NativeSyncApi nativeSyncApi}) - : _localAlbumRepository = localAlbumRepository, - _nativeSyncApi = nativeSyncApi; + LocalSyncService({ + required DriftLocalAlbumRepository localAlbumRepository, + required DriftTrashedLocalAssetRepository trashedLocalAssetRepository, + required LocalFilesManagerRepository localFilesManager, + required StorageRepository storageRepository, + required NativeSyncApi nativeSyncApi, + }) : _localAlbumRepository = localAlbumRepository, + _trashedLocalAssetRepository = trashedLocalAssetRepository, + _localFilesManager = localFilesManager, + _storageRepository = storageRepository, + _nativeSyncApi = nativeSyncApi; Future sync({bool full = false}) async { final Stopwatch stopwatch = Stopwatch()..start(); try { + if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) { + final hasPermission = await _localFilesManager.hasManageMediaPermission(); + if (hasPermission) { + await _syncTrashedAssets(); + } else { + _log.warning("syncTrashedAssets cannot proceed because MANAGE_MEDIA permission is missing"); + } + } if (full || await _nativeSyncApi.shouldFullSync()) { _log.fine("Full sync request from ${full ? "user" : "native"}"); return await fullSync(); @@ -69,7 +93,6 @@ class LocalSyncService { await updateAlbum(dbAlbum, album); } } - await _nativeSyncApi.checkpointSync(); } catch (e, s) { _log.severe("Error performing device sync", e, s); @@ -249,7 +272,7 @@ class LocalSyncService { if (assetsToUpsert.isEmpty && assetsToDelete.isEmpty) { _log.fine("No asset changes detected in album ${deviceAlbum.name}. Updating metadata."); - _localAlbumRepository.upsert(updatedDeviceAlbum); + await _localAlbumRepository.upsert(updatedDeviceAlbum); return true; } @@ -273,6 +296,48 @@ class LocalSyncService { bool _albumsEqual(LocalAlbum a, LocalAlbum b) { return a.name == b.name && a.assetCount == b.assetCount && a.updatedAt.isAtSameMomentAs(b.updatedAt); } + + Future _syncTrashedAssets() async { + final trashedAssetMap = await _nativeSyncApi.getTrashedAssets(); + await processTrashedAssets(trashedAssetMap); + } + + @visibleForTesting + Future processTrashedAssets(Map> trashedAssetMap) async { + if (trashedAssetMap.isEmpty) { + _log.info("syncTrashedAssets, No trashed assets found"); + } + final trashedAssets = trashedAssetMap.cast>().entries.expand( + (entry) => entry.value.cast().toTrashedAssets(entry.key), + ); + + _log.fine("syncTrashedAssets, trashedAssets: ${trashedAssets.map((e) => e.asset.id)}"); + await _trashedLocalAssetRepository.processTrashSnapshot(trashedAssets); + + final assetsToRestore = await _trashedLocalAssetRepository.getToRestore(); + if (assetsToRestore.isNotEmpty) { + final restoredIds = await _localFilesManager.restoreAssetsFromTrash(assetsToRestore); + await _trashedLocalAssetRepository.applyRestoredAssets(restoredIds); + } else { + _log.info("syncTrashedAssets, No remote assets found for restoration"); + } + + final localAssetsToTrash = await _trashedLocalAssetRepository.getToTrash(); + if (localAssetsToTrash.isNotEmpty) { + final mediaUrls = await Future.wait( + localAssetsToTrash.values + .expand((e) => e) + .map((localAsset) => _storageRepository.getAssetEntityForAsset(localAsset).then((e) => e?.getMediaUrl())), + ); + _log.info("Moving to trash ${mediaUrls.join(", ")} assets"); + final result = await _localFilesManager.moveToTrash(mediaUrls.nonNulls.toList()); + if (result) { + await _trashedLocalAssetRepository.trashLocalAsset(localAssetsToTrash); + } + } else { + _log.info("syncTrashedAssets, No assets found in backup-enabled albums for move to trash"); + } + } } extension on Iterable { @@ -281,7 +346,7 @@ extension on Iterable { (e) => LocalAlbum( id: e.id, name: e.name, - updatedAt: tryFromSecondsSinceEpoch(e.updatedAt) ?? DateTime.now(), + updatedAt: tryFromSecondsSinceEpoch(e.updatedAt, isUtc: true) ?? DateTime.timestamp(), assetCount: e.assetCount, ), ).toList(); @@ -290,20 +355,26 @@ extension on Iterable { extension on Iterable { List toLocalAssets() { - return map( - (e) => LocalAsset( - id: e.id, - name: e.name, - checksum: null, - type: AssetType.values.elementAtOrNull(e.type) ?? AssetType.other, - createdAt: tryFromSecondsSinceEpoch(e.createdAt) ?? DateTime.now(), - updatedAt: tryFromSecondsSinceEpoch(e.updatedAt) ?? DateTime.now(), - width: e.width, - height: e.height, - durationInSeconds: e.durationInSeconds, - orientation: e.orientation, - isFavorite: e.isFavorite, - ), - ).toList(); + return map((e) => e.toLocalAsset()).toList(); + } + + Iterable toTrashedAssets(String albumId) { + return map((e) => (albumId: albumId, asset: e.toLocalAsset())); } } + +extension on PlatformAsset { + LocalAsset toLocalAsset() => LocalAsset( + id: id, + name: name, + checksum: null, + type: AssetType.values.elementAtOrNull(type) ?? AssetType.other, + createdAt: tryFromSecondsSinceEpoch(createdAt, isUtc: true) ?? DateTime.timestamp(), + updatedAt: tryFromSecondsSinceEpoch(createdAt, isUtc: true) ?? DateTime.timestamp(), + width: width, + height: height, + durationInSeconds: durationInSeconds, + isFavorite: isFavorite, + orientation: orientation, + ); +} diff --git a/mobile/lib/domain/services/remote_album.service.dart b/mobile/lib/domain/services/remote_album.service.dart index cc28dfafd5..67e91188e2 100644 --- a/mobile/lib/domain/services/remote_album.service.dart +++ b/mobile/lib/domain/services/remote_album.service.dart @@ -120,6 +120,10 @@ class RemoteAlbumService { return _repository.getSharedUsers(albumId); } + Future getUserRole(String albumId, String userId) { + return _repository.getUserRole(albumId, userId); + } + Future> getAssets(String albumId) { return _repository.getAssets(albumId); } @@ -160,6 +164,10 @@ class RemoteAlbumService { return _repository.getCount(); } + Future> getAlbumsContainingAsset(String assetId) { + return _repository.getAlbumsContainingAsset(assetId); + } + Future> _sortByNewestAsset(List albums) async { // map album IDs to their newest asset dates final Map> assetTimestampFutures = {}; diff --git a/mobile/lib/domain/services/store.service.dart b/mobile/lib/domain/services/store.service.dart index f9b4a0aa81..0098c3d262 100644 --- a/mobile/lib/domain/services/store.service.dart +++ b/mobile/lib/domain/services/store.service.dart @@ -86,7 +86,7 @@ class StoreService { _cache.remove(key.id); } - /// Clears all values from thw store (cache and DB) + /// Clears all values from the store (cache and DB) Future clear() async { await _storeRepository.deleteAll(); _cache.clear(); diff --git a/mobile/lib/domain/services/sync_stream.service.dart b/mobile/lib/domain/services/sync_stream.service.dart index 6c8e444d50..2ff0f18fcf 100644 --- a/mobile/lib/domain/services/sync_stream.service.dart +++ b/mobile/lib/domain/services/sync_stream.service.dart @@ -1,8 +1,15 @@ import 'dart:async'; +import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/sync_event.model.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/extensions/platform_extensions.dart'; +import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart'; +import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; import 'package:logging/logging.dart'; import 'package:openapi/api.dart'; @@ -11,19 +18,31 @@ class SyncStreamService { final SyncApiRepository _syncApiRepository; final SyncStreamRepository _syncStreamRepository; + final DriftLocalAssetRepository _localAssetRepository; + final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository; + final LocalFilesManagerRepository _localFilesManager; + final StorageRepository _storageRepository; final bool Function()? _cancelChecker; SyncStreamService({ required SyncApiRepository syncApiRepository, required SyncStreamRepository syncStreamRepository, + required DriftLocalAssetRepository localAssetRepository, + required DriftTrashedLocalAssetRepository trashedLocalAssetRepository, + required LocalFilesManagerRepository localFilesManager, + required StorageRepository storageRepository, bool Function()? cancelChecker, }) : _syncApiRepository = syncApiRepository, _syncStreamRepository = syncStreamRepository, + _localAssetRepository = localAssetRepository, + _trashedLocalAssetRepository = trashedLocalAssetRepository, + _localFilesManager = localFilesManager, + _storageRepository = storageRepository, _cancelChecker = cancelChecker; bool get isCancelled => _cancelChecker?.call() ?? false; - Future sync() async { + Future sync() async { _logger.info("Remote sync request for user"); // Start the sync stream and handle events bool shouldReset = false; @@ -32,6 +51,7 @@ class SyncStreamService { _logger.info("Resetting sync state as requested by server"); await _syncApiRepository.streamChanges(_handleEvents); } + return true; } Future _handleEvents(List events, Function() abort, Function() reset) async { @@ -82,7 +102,18 @@ class SyncStreamService { case SyncEntityType.partnerDeleteV1: return _syncStreamRepository.deletePartnerV1(data.cast()); case SyncEntityType.assetV1: - return _syncStreamRepository.updateAssetsV1(data.cast()); + final remoteSyncAssets = data.cast(); + await _syncStreamRepository.updateAssetsV1(remoteSyncAssets); + if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) { + final hasPermission = await _localFilesManager.hasManageMediaPermission(); + if (hasPermission) { + await _handleRemoteTrashed(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.checksum)); + await _applyRemoteRestoreToLocal(); + } else { + _logger.warning("sync Trashed Assets cannot proceed because MANAGE_MEDIA permission is missing"); + } + } + return; case SyncEntityType.assetDeleteV1: return _syncStreamRepository.deleteAssetsV1(data.cast()); case SyncEntityType.assetExifV1: @@ -129,9 +160,10 @@ class SyncStreamService { // to acknowledge that the client has processed all the backfill events case SyncEntityType.syncAckV1: return; - // No-op. SyncCompleteV1 is used to signal the completion of the sync process + // SyncCompleteV1 is used to signal the completion of the sync process. Cleanup stale assets and signal completion case SyncEntityType.syncCompleteV1: return; + // return _syncStreamRepository.pruneAssets(); // Request to reset the client state. Clear everything related to remote entities case SyncEntityType.syncResetV1: return _syncStreamRepository.reset(); @@ -210,4 +242,36 @@ class SyncStreamService { _logger.severe("Error processing AssetUploadReadyV1 websocket batch events", error, stackTrace); } } + + Future _handleRemoteTrashed(Iterable checksums) async { + if (checksums.isEmpty) { + return Future.value(); + } else { + final localAssetsToTrash = await _localAssetRepository.getAssetsFromBackupAlbums(checksums); + if (localAssetsToTrash.isNotEmpty) { + final mediaUrls = await Future.wait( + localAssetsToTrash.values + .expand((e) => e) + .map((localAsset) => _storageRepository.getAssetEntityForAsset(localAsset).then((e) => e?.getMediaUrl())), + ); + _logger.info("Moving to trash ${mediaUrls.join(", ")} assets"); + final result = await _localFilesManager.moveToTrash(mediaUrls.nonNulls.toList()); + if (result) { + await _trashedLocalAssetRepository.trashLocalAsset(localAssetsToTrash); + } + } else { + _logger.info("No assets found in backup-enabled albums for assets: $checksums"); + } + } + } + + Future _applyRemoteRestoreToLocal() async { + final assetsToRestore = await _trashedLocalAssetRepository.getToRestore(); + if (assetsToRestore.isNotEmpty) { + final restoredIds = await _localFilesManager.restoreAssetsFromTrash(assetsToRestore); + await _trashedLocalAssetRepository.applyRestoredAssets(restoredIds); + } else { + _logger.info("No remote assets found for restoration"); + } + } } diff --git a/mobile/lib/domain/services/timeline.service.dart b/mobile/lib/domain/services/timeline.service.dart index ab623720b7..9537fe667a 100644 --- a/mobile/lib/domain/services/timeline.service.dart +++ b/mobile/lib/domain/services/timeline.service.dart @@ -16,7 +16,25 @@ typedef TimelineAssetSource = Future> Function(int index, int co typedef TimelineBucketSource = Stream> Function(); -typedef TimelineQuery = ({TimelineAssetSource assetSource, TimelineBucketSource bucketSource}); +typedef TimelineQuery = ({TimelineAssetSource assetSource, TimelineBucketSource bucketSource, TimelineOrigin origin}); + +enum TimelineOrigin { + main, + localAlbum, + remoteAlbum, + remoteAssets, + favorite, + trash, + archive, + lockedFolder, + video, + place, + person, + map, + search, + deepLink, + albumActivities, +} class TimelineFactory { final DriftTimelineRepository _timelineRepository; @@ -57,14 +75,17 @@ class TimelineFactory { TimelineService person(String userId, String personId) => TimelineService(_timelineRepository.person(userId, personId, groupBy)); - TimelineService fromAssets(List assets) => TimelineService(_timelineRepository.fromAssets(assets)); + TimelineService fromAssets(List assets, TimelineOrigin type) => + TimelineService(_timelineRepository.fromAssets(assets, type)); - TimelineService map(LatLngBounds bounds) => TimelineService(_timelineRepository.map(bounds, groupBy)); + TimelineService map(String userId, LatLngBounds bounds) => + TimelineService(_timelineRepository.map(userId, bounds, groupBy)); } class TimelineService { final TimelineAssetSource _assetSource; final TimelineBucketSource _bucketSource; + final TimelineOrigin origin; final AsyncMutex _mutex = AsyncMutex(); int _bufferOffset = 0; List _buffer = []; @@ -73,11 +94,15 @@ class TimelineService { int _totalAssets = 0; int get totalAssets => _totalAssets; - TimelineService(TimelineQuery query) : this._(assetSource: query.assetSource, bucketSource: query.bucketSource); + TimelineService(TimelineQuery query) + : this._(assetSource: query.assetSource, bucketSource: query.bucketSource, origin: query.origin); - TimelineService._({required TimelineAssetSource assetSource, required TimelineBucketSource bucketSource}) - : _assetSource = assetSource, - _bucketSource = bucketSource { + TimelineService._({ + required TimelineAssetSource assetSource, + required TimelineBucketSource bucketSource, + required this.origin, + }) : _assetSource = assetSource, + _bucketSource = bucketSource { _bucketSubscription = _bucketSource().listen((buckets) { _mutex.run(() async { final totalAssets = buckets.fold(0, (acc, bucket) => acc + bucket.assetCount); @@ -202,7 +227,7 @@ class TimelineService { Future dispose() async { await _bucketSubscription?.cancel(); _bucketSubscription = null; - _buffer.clear(); + _buffer = []; _bufferOffset = 0; } } diff --git a/mobile/lib/domain/utils/background_sync.dart b/mobile/lib/domain/utils/background_sync.dart index ffbb020345..38e249b9f1 100644 --- a/mobile/lib/domain/utils/background_sync.dart +++ b/mobile/lib/domain/utils/background_sync.dart @@ -6,11 +6,12 @@ import 'package:immich_mobile/utils/isolate.dart'; import 'package:worker_manager/worker_manager.dart'; typedef SyncCallback = void Function(); +typedef SyncCallbackWithResult = void Function(T result); typedef SyncErrorCallback = void Function(String error); class BackgroundSyncManager { final SyncCallback? onRemoteSyncStart; - final SyncCallback? onRemoteSyncComplete; + final SyncCallbackWithResult? onRemoteSyncComplete; final SyncErrorCallback? onRemoteSyncError; final SyncCallback? onLocalSyncStart; @@ -21,7 +22,7 @@ class BackgroundSyncManager { final SyncCallback? onHashingComplete; final SyncErrorCallback? onHashingError; - Cancelable? _syncTask; + Cancelable? _syncTask; Cancelable? _syncWebsocketTask; Cancelable? _deviceAlbumSyncTask; Cancelable? _linkedAlbumSyncTask; @@ -144,9 +145,9 @@ class BackgroundSyncManager { }); } - Future syncRemote() { + Future syncRemote() { if (_syncTask != null) { - return _syncTask!.future; + return _syncTask!.future.then((result) => result ?? false).catchError((_) => false); } onRemoteSyncStart?.call(); @@ -156,13 +157,18 @@ class BackgroundSyncManager { debugLabel: 'remote-sync', ); return _syncTask! - .whenComplete(() { - onRemoteSyncComplete?.call(); - _syncTask = null; + .then((result) { + final success = result ?? false; + onRemoteSyncComplete?.call(success); + return success; }) .catchError((error) { onRemoteSyncError?.call(error.toString()); _syncTask = null; + return false; + }) + .whenComplete(() { + _syncTask = null; }); } diff --git a/mobile/lib/entities/album.entity.g.dart b/mobile/lib/entities/album.entity.g.dart index e6ecde7f9a..ecbbab48c2 100644 --- a/mobile/lib/entities/album.entity.g.dart +++ b/mobile/lib/entities/album.entity.g.dart @@ -132,7 +132,7 @@ const AlbumSchema = CollectionSchema( getId: _albumGetId, getLinks: _albumGetLinks, attach: _albumAttach, - version: '3.1.8', + version: '3.3.0-dev.3', ); int _albumEstimateSize( diff --git a/mobile/lib/entities/android_device_asset.entity.g.dart b/mobile/lib/entities/android_device_asset.entity.g.dart index 9034709b8e..f8b1e32c72 100644 --- a/mobile/lib/entities/android_device_asset.entity.g.dart +++ b/mobile/lib/entities/android_device_asset.entity.g.dart @@ -47,7 +47,7 @@ const AndroidDeviceAssetSchema = CollectionSchema( getId: _androidDeviceAssetGetId, getLinks: _androidDeviceAssetGetLinks, attach: _androidDeviceAssetAttach, - version: '3.1.8', + version: '3.3.0-dev.3', ); int _androidDeviceAssetEstimateSize( diff --git a/mobile/lib/entities/asset.entity.g.dart b/mobile/lib/entities/asset.entity.g.dart index be5b427d01..db6bc72331 100644 --- a/mobile/lib/entities/asset.entity.g.dart +++ b/mobile/lib/entities/asset.entity.g.dart @@ -168,7 +168,7 @@ const AssetSchema = CollectionSchema( getId: _assetGetId, getLinks: _assetGetLinks, attach: _assetAttach, - version: '3.1.8', + version: '3.3.0-dev.3', ); int _assetEstimateSize( diff --git a/mobile/lib/entities/backup_album.entity.g.dart b/mobile/lib/entities/backup_album.entity.g.dart index ed98503119..583aa55c4d 100644 --- a/mobile/lib/entities/backup_album.entity.g.dart +++ b/mobile/lib/entities/backup_album.entity.g.dart @@ -43,7 +43,7 @@ const BackupAlbumSchema = CollectionSchema( getId: _backupAlbumGetId, getLinks: _backupAlbumGetLinks, attach: _backupAlbumAttach, - version: '3.1.8', + version: '3.3.0-dev.3', ); int _backupAlbumEstimateSize( diff --git a/mobile/lib/entities/duplicated_asset.entity.g.dart b/mobile/lib/entities/duplicated_asset.entity.g.dart index 6cf08ad9cc..80d2f344e6 100644 --- a/mobile/lib/entities/duplicated_asset.entity.g.dart +++ b/mobile/lib/entities/duplicated_asset.entity.g.dart @@ -32,7 +32,7 @@ const DuplicatedAssetSchema = CollectionSchema( getId: _duplicatedAssetGetId, getLinks: _duplicatedAssetGetLinks, attach: _duplicatedAssetAttach, - version: '3.1.8', + version: '3.3.0-dev.3', ); int _duplicatedAssetEstimateSize( diff --git a/mobile/lib/entities/etag.entity.g.dart b/mobile/lib/entities/etag.entity.g.dart index b1abba6bb7..03b4ea9918 100644 --- a/mobile/lib/entities/etag.entity.g.dart +++ b/mobile/lib/entities/etag.entity.g.dart @@ -52,7 +52,7 @@ const ETagSchema = CollectionSchema( getId: _eTagGetId, getLinks: _eTagGetLinks, attach: _eTagAttach, - version: '3.1.8', + version: '3.3.0-dev.3', ); int _eTagEstimateSize( diff --git a/mobile/lib/entities/ios_device_asset.entity.g.dart b/mobile/lib/entities/ios_device_asset.entity.g.dart index 8d8fec945b..252fe127bb 100644 --- a/mobile/lib/entities/ios_device_asset.entity.g.dart +++ b/mobile/lib/entities/ios_device_asset.entity.g.dart @@ -60,7 +60,7 @@ const IOSDeviceAssetSchema = CollectionSchema( getId: _iOSDeviceAssetGetId, getLinks: _iOSDeviceAssetGetLinks, attach: _iOSDeviceAssetAttach, - version: '3.1.8', + version: '3.3.0-dev.3', ); int _iOSDeviceAssetEstimateSize( diff --git a/mobile/lib/extensions/string_extensions.dart b/mobile/lib/extensions/string_extensions.dart index 6cd6e1e4b4..ae31565044 100644 --- a/mobile/lib/extensions/string_extensions.dart +++ b/mobile/lib/extensions/string_extensions.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + extension StringExtension on String { String capitalize() { return split(" ").map((str) => str.isEmpty ? str : str[0].toUpperCase() + str.substring(1)).join(" "); @@ -23,3 +25,11 @@ extension DurationExtension on String { return int.parse(this); } } + +Map? tryJsonDecode(dynamic json) { + try { + return jsonDecode(json) as Map; + } catch (e) { + return null; + } +} diff --git a/mobile/lib/infrastructure/entities/device_asset.entity.g.dart b/mobile/lib/infrastructure/entities/device_asset.entity.g.dart index 87ae54ad40..b6c30aca6f 100644 --- a/mobile/lib/infrastructure/entities/device_asset.entity.g.dart +++ b/mobile/lib/infrastructure/entities/device_asset.entity.g.dart @@ -65,7 +65,7 @@ const DeviceAssetEntitySchema = CollectionSchema( getId: _deviceAssetEntityGetId, getLinks: _deviceAssetEntityGetLinks, attach: _deviceAssetEntityAttach, - version: '3.1.8', + version: '3.3.0-dev.3', ); int _deviceAssetEntityEstimateSize( diff --git a/mobile/lib/infrastructure/entities/exif.entity.g.dart b/mobile/lib/infrastructure/entities/exif.entity.g.dart index d2f9ebda27..ffbfd0d8f0 100644 --- a/mobile/lib/infrastructure/entities/exif.entity.g.dart +++ b/mobile/lib/infrastructure/entities/exif.entity.g.dart @@ -68,7 +68,7 @@ const ExifInfoSchema = CollectionSchema( getId: _exifInfoGetId, getLinks: _exifInfoGetLinks, attach: _exifInfoAttach, - version: '3.1.8', + version: '3.3.0-dev.3', ); int _exifInfoEstimateSize( diff --git a/mobile/lib/infrastructure/entities/local_asset.entity.dart b/mobile/lib/infrastructure/entities/local_asset.entity.dart index 337a6d728d..8b253f83a3 100644 --- a/mobile/lib/infrastructure/entities/local_asset.entity.dart +++ b/mobile/lib/infrastructure/entities/local_asset.entity.dart @@ -21,7 +21,7 @@ class LocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin { } extension LocalAssetEntityDataDomainExtension on LocalAssetEntityData { - LocalAsset toDto() => LocalAsset( + LocalAsset toDto({String? remoteId}) => LocalAsset( id: id, name: name, checksum: checksum, @@ -32,7 +32,7 @@ extension LocalAssetEntityDataDomainExtension on LocalAssetEntityData { isFavorite: isFavorite, height: height, width: width, - remoteId: null, + remoteId: remoteId, orientation: orientation, ); } diff --git a/mobile/lib/infrastructure/entities/remote_asset.entity.dart b/mobile/lib/infrastructure/entities/remote_asset.entity.dart index 4426974413..dcc885a2a9 100644 --- a/mobile/lib/infrastructure/entities/remote_asset.entity.dart +++ b/mobile/lib/infrastructure/entities/remote_asset.entity.dart @@ -49,7 +49,7 @@ class RemoteAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin } extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData { - RemoteAsset toDto() => RemoteAsset( + RemoteAsset toDto({String? localId}) => RemoteAsset( id: id, name: name, ownerId: ownerId, @@ -64,7 +64,7 @@ extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData { thumbHash: thumbHash, visibility: visibility, livePhotoVideoId: livePhotoVideoId, - localId: null, + localId: localId, stackId: stackId, ); } diff --git a/mobile/lib/infrastructure/entities/store.entity.g.dart b/mobile/lib/infrastructure/entities/store.entity.g.dart index 7da92cf778..626c3084fe 100644 --- a/mobile/lib/infrastructure/entities/store.entity.g.dart +++ b/mobile/lib/infrastructure/entities/store.entity.g.dart @@ -37,7 +37,7 @@ const StoreValueSchema = CollectionSchema( getId: _storeValueGetId, getLinks: _storeValueGetLinks, attach: _storeValueAttach, - version: '3.1.8', + version: '3.3.0-dev.3', ); int _storeValueEstimateSize( diff --git a/mobile/lib/infrastructure/entities/trashed_local_asset.entity.dart b/mobile/lib/infrastructure/entities/trashed_local_asset.entity.dart new file mode 100644 index 0000000000..308130b9ea --- /dev/null +++ b/mobile/lib/infrastructure/entities/trashed_local_asset.entity.dart @@ -0,0 +1,40 @@ +import 'package:drift/drift.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/utils/asset.mixin.dart'; +import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart'; + +@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)') +@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)') +class TrashedLocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin { + const TrashedLocalAssetEntity(); + + TextColumn get id => text()(); + + TextColumn get albumId => text()(); + + TextColumn get checksum => text().nullable()(); + + BoolColumn get isFavorite => boolean().withDefault(const Constant(false))(); + + IntColumn get orientation => integer().withDefault(const Constant(0))(); + + @override + Set get primaryKey => {id, albumId}; +} + +extension TrashedLocalAssetEntityDataDomainExtension on TrashedLocalAssetEntityData { + LocalAsset toLocalAsset() => LocalAsset( + id: id, + name: name, + checksum: checksum, + type: type, + createdAt: createdAt, + updatedAt: updatedAt, + durationInSeconds: durationInSeconds, + isFavorite: isFavorite, + height: height, + width: width, + orientation: orientation, + ); +} diff --git a/mobile/lib/infrastructure/entities/trashed_local_asset.entity.drift.dart b/mobile/lib/infrastructure/entities/trashed_local_asset.entity.drift.dart new file mode 100644 index 0000000000..aab226c3a2 --- /dev/null +++ b/mobile/lib/infrastructure/entities/trashed_local_asset.entity.drift.dart @@ -0,0 +1,1080 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart' + as i1; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart' as i2; +import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.dart' + as i3; +import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4; + +typedef $$TrashedLocalAssetEntityTableCreateCompanionBuilder = + i1.TrashedLocalAssetEntityCompanion Function({ + required String name, + required i2.AssetType type, + i0.Value createdAt, + i0.Value updatedAt, + i0.Value width, + i0.Value height, + i0.Value durationInSeconds, + required String id, + required String albumId, + i0.Value checksum, + i0.Value isFavorite, + i0.Value orientation, + }); +typedef $$TrashedLocalAssetEntityTableUpdateCompanionBuilder = + i1.TrashedLocalAssetEntityCompanion Function({ + i0.Value name, + i0.Value type, + i0.Value createdAt, + i0.Value updatedAt, + i0.Value width, + i0.Value height, + i0.Value durationInSeconds, + i0.Value id, + i0.Value albumId, + i0.Value checksum, + i0.Value isFavorite, + i0.Value orientation, + }); + +class $$TrashedLocalAssetEntityTableFilterComposer + extends + i0.Composer { + $$TrashedLocalAssetEntityTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnWithTypeConverterFilters get type => + $composableBuilder( + column: $table.type, + builder: (column) => i0.ColumnWithTypeConverterFilters(column), + ); + + i0.ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get width => $composableBuilder( + column: $table.width, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get height => $composableBuilder( + column: $table.height, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get durationInSeconds => $composableBuilder( + column: $table.durationInSeconds, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get albumId => $composableBuilder( + column: $table.albumId, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get checksum => $composableBuilder( + column: $table.checksum, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get isFavorite => $composableBuilder( + column: $table.isFavorite, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get orientation => $composableBuilder( + column: $table.orientation, + builder: (column) => i0.ColumnFilters(column), + ); +} + +class $$TrashedLocalAssetEntityTableOrderingComposer + extends + i0.Composer { + $$TrashedLocalAssetEntityTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get type => $composableBuilder( + column: $table.type, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get width => $composableBuilder( + column: $table.width, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get height => $composableBuilder( + column: $table.height, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get durationInSeconds => $composableBuilder( + column: $table.durationInSeconds, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get albumId => $composableBuilder( + column: $table.albumId, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get checksum => $composableBuilder( + column: $table.checksum, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get isFavorite => $composableBuilder( + column: $table.isFavorite, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get orientation => $composableBuilder( + column: $table.orientation, + builder: (column) => i0.ColumnOrderings(column), + ); +} + +class $$TrashedLocalAssetEntityTableAnnotationComposer + extends + i0.Composer { + $$TrashedLocalAssetEntityTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + i0.GeneratedColumnWithTypeConverter get type => + $composableBuilder(column: $table.type, builder: (column) => column); + + i0.GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + i0.GeneratedColumn get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => column); + + i0.GeneratedColumn get width => + $composableBuilder(column: $table.width, builder: (column) => column); + + i0.GeneratedColumn get height => + $composableBuilder(column: $table.height, builder: (column) => column); + + i0.GeneratedColumn get durationInSeconds => $composableBuilder( + column: $table.durationInSeconds, + builder: (column) => column, + ); + + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get albumId => + $composableBuilder(column: $table.albumId, builder: (column) => column); + + i0.GeneratedColumn get checksum => + $composableBuilder(column: $table.checksum, builder: (column) => column); + + i0.GeneratedColumn get isFavorite => $composableBuilder( + column: $table.isFavorite, + builder: (column) => column, + ); + + i0.GeneratedColumn get orientation => $composableBuilder( + column: $table.orientation, + builder: (column) => column, + ); +} + +class $$TrashedLocalAssetEntityTableTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.$TrashedLocalAssetEntityTable, + i1.TrashedLocalAssetEntityData, + i1.$$TrashedLocalAssetEntityTableFilterComposer, + i1.$$TrashedLocalAssetEntityTableOrderingComposer, + i1.$$TrashedLocalAssetEntityTableAnnotationComposer, + $$TrashedLocalAssetEntityTableCreateCompanionBuilder, + $$TrashedLocalAssetEntityTableUpdateCompanionBuilder, + ( + i1.TrashedLocalAssetEntityData, + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$TrashedLocalAssetEntityTable, + i1.TrashedLocalAssetEntityData + >, + ), + i1.TrashedLocalAssetEntityData, + i0.PrefetchHooks Function() + > { + $$TrashedLocalAssetEntityTableTableManager( + i0.GeneratedDatabase db, + i1.$TrashedLocalAssetEntityTable table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$TrashedLocalAssetEntityTableFilterComposer( + $db: db, + $table: table, + ), + createOrderingComposer: () => + i1.$$TrashedLocalAssetEntityTableOrderingComposer( + $db: db, + $table: table, + ), + createComputedFieldComposer: () => + i1.$$TrashedLocalAssetEntityTableAnnotationComposer( + $db: db, + $table: table, + ), + updateCompanionCallback: + ({ + i0.Value name = const i0.Value.absent(), + i0.Value type = const i0.Value.absent(), + i0.Value createdAt = const i0.Value.absent(), + i0.Value updatedAt = const i0.Value.absent(), + i0.Value width = const i0.Value.absent(), + i0.Value height = const i0.Value.absent(), + i0.Value durationInSeconds = const i0.Value.absent(), + i0.Value id = const i0.Value.absent(), + i0.Value albumId = const i0.Value.absent(), + i0.Value checksum = const i0.Value.absent(), + i0.Value isFavorite = const i0.Value.absent(), + i0.Value orientation = const i0.Value.absent(), + }) => i1.TrashedLocalAssetEntityCompanion( + name: name, + type: type, + createdAt: createdAt, + updatedAt: updatedAt, + width: width, + height: height, + durationInSeconds: durationInSeconds, + id: id, + albumId: albumId, + checksum: checksum, + isFavorite: isFavorite, + orientation: orientation, + ), + createCompanionCallback: + ({ + required String name, + required i2.AssetType type, + i0.Value createdAt = const i0.Value.absent(), + i0.Value updatedAt = const i0.Value.absent(), + i0.Value width = const i0.Value.absent(), + i0.Value height = const i0.Value.absent(), + i0.Value durationInSeconds = const i0.Value.absent(), + required String id, + required String albumId, + i0.Value checksum = const i0.Value.absent(), + i0.Value isFavorite = const i0.Value.absent(), + i0.Value orientation = const i0.Value.absent(), + }) => i1.TrashedLocalAssetEntityCompanion.insert( + name: name, + type: type, + createdAt: createdAt, + updatedAt: updatedAt, + width: width, + height: height, + durationInSeconds: durationInSeconds, + id: id, + albumId: albumId, + checksum: checksum, + isFavorite: isFavorite, + orientation: orientation, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$TrashedLocalAssetEntityTableProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$TrashedLocalAssetEntityTable, + i1.TrashedLocalAssetEntityData, + i1.$$TrashedLocalAssetEntityTableFilterComposer, + i1.$$TrashedLocalAssetEntityTableOrderingComposer, + i1.$$TrashedLocalAssetEntityTableAnnotationComposer, + $$TrashedLocalAssetEntityTableCreateCompanionBuilder, + $$TrashedLocalAssetEntityTableUpdateCompanionBuilder, + ( + i1.TrashedLocalAssetEntityData, + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$TrashedLocalAssetEntityTable, + i1.TrashedLocalAssetEntityData + >, + ), + i1.TrashedLocalAssetEntityData, + i0.PrefetchHooks Function() + >; +i0.Index get idxTrashedLocalAssetChecksum => i0.Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', +); + +class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity + with + i0.TableInfo< + $TrashedLocalAssetEntityTable, + i1.TrashedLocalAssetEntityData + > { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $TrashedLocalAssetEntityTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _nameMeta = const i0.VerificationMeta( + 'name', + ); + @override + late final i0.GeneratedColumn name = i0.GeneratedColumn( + 'name', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + late final i0.GeneratedColumnWithTypeConverter type = + i0.GeneratedColumn( + 'type', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + ).withConverter( + i1.$TrashedLocalAssetEntityTable.$convertertype, + ); + static const i0.VerificationMeta _createdAtMeta = const i0.VerificationMeta( + 'createdAt', + ); + @override + late final i0.GeneratedColumn createdAt = + i0.GeneratedColumn( + 'created_at', + aliasedName, + false, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: i4.currentDateAndTime, + ); + static const i0.VerificationMeta _updatedAtMeta = const i0.VerificationMeta( + 'updatedAt', + ); + @override + late final i0.GeneratedColumn updatedAt = + i0.GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: i4.currentDateAndTime, + ); + static const i0.VerificationMeta _widthMeta = const i0.VerificationMeta( + 'width', + ); + @override + late final i0.GeneratedColumn width = i0.GeneratedColumn( + 'width', + aliasedName, + true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _heightMeta = const i0.VerificationMeta( + 'height', + ); + @override + late final i0.GeneratedColumn height = i0.GeneratedColumn( + 'height', + aliasedName, + true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _durationInSecondsMeta = + const i0.VerificationMeta('durationInSeconds'); + @override + late final i0.GeneratedColumn durationInSeconds = + i0.GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + @override + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _albumIdMeta = const i0.VerificationMeta( + 'albumId', + ); + @override + late final i0.GeneratedColumn albumId = i0.GeneratedColumn( + 'album_id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _checksumMeta = const i0.VerificationMeta( + 'checksum', + ); + @override + late final i0.GeneratedColumn checksum = i0.GeneratedColumn( + 'checksum', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _isFavoriteMeta = const i0.VerificationMeta( + 'isFavorite', + ); + @override + late final i0.GeneratedColumn isFavorite = i0.GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: i0.DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const i4.Constant(false), + ); + static const i0.VerificationMeta _orientationMeta = const i0.VerificationMeta( + 'orientation', + ); + @override + late final i0.GeneratedColumn orientation = i0.GeneratedColumn( + 'orientation', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const i4.Constant(0), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'trashed_local_asset_entity'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } + if (data.containsKey('updated_at')) { + context.handle( + _updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta), + ); + } + if (data.containsKey('width')) { + context.handle( + _widthMeta, + width.isAcceptableOrUnknown(data['width']!, _widthMeta), + ); + } + if (data.containsKey('height')) { + context.handle( + _heightMeta, + height.isAcceptableOrUnknown(data['height']!, _heightMeta), + ); + } + if (data.containsKey('duration_in_seconds')) { + context.handle( + _durationInSecondsMeta, + durationInSeconds.isAcceptableOrUnknown( + data['duration_in_seconds']!, + _durationInSecondsMeta, + ), + ); + } + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('album_id')) { + context.handle( + _albumIdMeta, + albumId.isAcceptableOrUnknown(data['album_id']!, _albumIdMeta), + ); + } else if (isInserting) { + context.missing(_albumIdMeta); + } + if (data.containsKey('checksum')) { + context.handle( + _checksumMeta, + checksum.isAcceptableOrUnknown(data['checksum']!, _checksumMeta), + ); + } + if (data.containsKey('is_favorite')) { + context.handle( + _isFavoriteMeta, + isFavorite.isAcceptableOrUnknown(data['is_favorite']!, _isFavoriteMeta), + ); + } + if (data.containsKey('orientation')) { + context.handle( + _orientationMeta, + orientation.isAcceptableOrUnknown( + data['orientation']!, + _orientationMeta, + ), + ); + } + return context; + } + + @override + Set get $primaryKey => {id, albumId}; + @override + i1.TrashedLocalAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.TrashedLocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: i1.$TrashedLocalAssetEntityTable.$convertertype.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + ), + createdAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + albumId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + checksum: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + i0.DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + ); + } + + @override + $TrashedLocalAssetEntityTable createAlias(String alias) { + return $TrashedLocalAssetEntityTable(attachedDatabase, alias); + } + + static i0.JsonTypeConverter2 $convertertype = + const i0.EnumIndexConverter(i2.AssetType.values); + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class TrashedLocalAssetEntityData extends i0.DataClass + implements i0.Insertable { + final String name; + final i2.AssetType type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String albumId; + final String? checksum; + final bool isFavorite; + final int orientation; + const TrashedLocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.albumId, + this.checksum, + required this.isFavorite, + required this.orientation, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = i0.Variable(name); + { + map['type'] = i0.Variable( + i1.$TrashedLocalAssetEntityTable.$convertertype.toSql(type), + ); + } + map['created_at'] = i0.Variable(createdAt); + map['updated_at'] = i0.Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = i0.Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = i0.Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = i0.Variable(durationInSeconds); + } + map['id'] = i0.Variable(id); + map['album_id'] = i0.Variable(albumId); + if (!nullToAbsent || checksum != null) { + map['checksum'] = i0.Variable(checksum); + } + map['is_favorite'] = i0.Variable(isFavorite); + map['orientation'] = i0.Variable(orientation); + return map; + } + + factory TrashedLocalAssetEntityData.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return TrashedLocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: i1.$TrashedLocalAssetEntityTable.$convertertype.fromJson( + serializer.fromJson(json['type']), + ), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + albumId: serializer.fromJson(json['albumId']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson( + i1.$TrashedLocalAssetEntityTable.$convertertype.toJson(type), + ), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'albumId': serializer.toJson(albumId), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + }; + } + + i1.TrashedLocalAssetEntityData copyWith({ + String? name, + i2.AssetType? type, + DateTime? createdAt, + DateTime? updatedAt, + i0.Value width = const i0.Value.absent(), + i0.Value height = const i0.Value.absent(), + i0.Value durationInSeconds = const i0.Value.absent(), + String? id, + String? albumId, + i0.Value checksum = const i0.Value.absent(), + bool? isFavorite, + int? orientation, + }) => i1.TrashedLocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + TrashedLocalAssetEntityData copyWithCompanion( + i1.TrashedLocalAssetEntityCompanion data, + ) { + return TrashedLocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + ); + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.TrashedLocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.albumId == this.albumId && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation); +} + +class TrashedLocalAssetEntityCompanion + extends i0.UpdateCompanion { + final i0.Value name; + final i0.Value type; + final i0.Value createdAt; + final i0.Value updatedAt; + final i0.Value width; + final i0.Value height; + final i0.Value durationInSeconds; + final i0.Value id; + final i0.Value albumId; + final i0.Value checksum; + final i0.Value isFavorite; + final i0.Value orientation; + const TrashedLocalAssetEntityCompanion({ + this.name = const i0.Value.absent(), + this.type = const i0.Value.absent(), + this.createdAt = const i0.Value.absent(), + this.updatedAt = const i0.Value.absent(), + this.width = const i0.Value.absent(), + this.height = const i0.Value.absent(), + this.durationInSeconds = const i0.Value.absent(), + this.id = const i0.Value.absent(), + this.albumId = const i0.Value.absent(), + this.checksum = const i0.Value.absent(), + this.isFavorite = const i0.Value.absent(), + this.orientation = const i0.Value.absent(), + }); + TrashedLocalAssetEntityCompanion.insert({ + required String name, + required i2.AssetType type, + this.createdAt = const i0.Value.absent(), + this.updatedAt = const i0.Value.absent(), + this.width = const i0.Value.absent(), + this.height = const i0.Value.absent(), + this.durationInSeconds = const i0.Value.absent(), + required String id, + required String albumId, + this.checksum = const i0.Value.absent(), + this.isFavorite = const i0.Value.absent(), + this.orientation = const i0.Value.absent(), + }) : name = i0.Value(name), + type = i0.Value(type), + id = i0.Value(id), + albumId = i0.Value(albumId); + static i0.Insertable custom({ + i0.Expression? name, + i0.Expression? type, + i0.Expression? createdAt, + i0.Expression? updatedAt, + i0.Expression? width, + i0.Expression? height, + i0.Expression? durationInSeconds, + i0.Expression? id, + i0.Expression? albumId, + i0.Expression? checksum, + i0.Expression? isFavorite, + i0.Expression? orientation, + }) { + return i0.RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (albumId != null) 'album_id': albumId, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + }); + } + + i1.TrashedLocalAssetEntityCompanion copyWith({ + i0.Value? name, + i0.Value? type, + i0.Value? createdAt, + i0.Value? updatedAt, + i0.Value? width, + i0.Value? height, + i0.Value? durationInSeconds, + i0.Value? id, + i0.Value? albumId, + i0.Value? checksum, + i0.Value? isFavorite, + i0.Value? orientation, + }) { + return i1.TrashedLocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = i0.Variable(name.value); + } + if (type.present) { + map['type'] = i0.Variable( + i1.$TrashedLocalAssetEntityTable.$convertertype.toSql(type.value), + ); + } + if (createdAt.present) { + map['created_at'] = i0.Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = i0.Variable(updatedAt.value); + } + if (width.present) { + map['width'] = i0.Variable(width.value); + } + if (height.present) { + map['height'] = i0.Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = i0.Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (albumId.present) { + map['album_id'] = i0.Variable(albumId.value); + } + if (checksum.present) { + map['checksum'] = i0.Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = i0.Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = i0.Variable(orientation.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } +} + +i0.Index get idxTrashedLocalAssetAlbum => i0.Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', +); diff --git a/mobile/lib/infrastructure/entities/user.entity.g.dart b/mobile/lib/infrastructure/entities/user.entity.g.dart index bb87051731..7e0af41b77 100644 --- a/mobile/lib/infrastructure/entities/user.entity.g.dart +++ b/mobile/lib/infrastructure/entities/user.entity.g.dart @@ -95,7 +95,7 @@ const UserSchema = CollectionSchema( getId: _userGetId, getLinks: _userGetLinks, attach: _userAttach, - version: '3.1.8', + version: '3.3.0-dev.3', ); int _userEstimateSize( diff --git a/mobile/lib/infrastructure/loaders/remote_image_request.dart b/mobile/lib/infrastructure/loaders/remote_image_request.dart index 78f6b9479b..03dcd6454a 100644 --- a/mobile/lib/infrastructure/loaders/remote_image_request.dart +++ b/mobile/lib/infrastructure/loaders/remote_image_request.dart @@ -68,7 +68,7 @@ class RemoteImageRequest extends ImageRequest { final cacheManager = this.cacheManager; final streamController = StreamController>(sync: true); final Stream> stream; - cacheManager?.putStreamedFile(url, streamController.stream); + unawaited(cacheManager?.putStreamedFile(url, streamController.stream)); stream = response.map((chunk) { if (_isCancelled) { throw StateError('Cancelled request'); @@ -81,11 +81,11 @@ class RemoteImageRequest extends ImageRequest { try { final Uint8List bytes = await _downloadBytes(stream, response.contentLength); - streamController.close(); + unawaited(streamController.close()); return await ImmutableBuffer.fromUint8List(bytes); } catch (e) { streamController.addError(e); - streamController.close(); + unawaited(streamController.close()); if (_isCancelled) { return null; } @@ -143,7 +143,7 @@ class RemoteImageRequest extends ImageRequest { return await _decodeBuffer(buffer, decode, scale); } catch (e) { log.severe('Failed to decode cached image', e); - _evictFile(url); + unawaited(_evictFile(url)); return null; } } diff --git a/mobile/lib/infrastructure/repositories/backup.repository.dart b/mobile/lib/infrastructure/repositories/backup.repository.dart index cc6fd7dfe3..0241711d4b 100644 --- a/mobile/lib/infrastructure/repositories/backup.repository.dart +++ b/mobile/lib/infrastructure/repositories/backup.repository.dart @@ -81,7 +81,7 @@ class DriftBackupRepository extends DriftDatabaseRepository { ); } - Future> getCandidates(String userId) async { + Future> getCandidates(String userId, {bool onlyHashed = true}) async { final selectedAlbumIds = _db.localAlbumEntity.selectOnly(distinct: true) ..addColumns([_db.localAlbumEntity.id]) ..where(_db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.selected)); @@ -89,7 +89,6 @@ class DriftBackupRepository extends DriftDatabaseRepository { final query = _db.localAssetEntity.select() ..where( (lae) => - lae.checksum.isNotNull() & existsQuery( _db.localAlbumAssetEntity.selectOnly() ..addColumns([_db.localAlbumAssetEntity.assetId]) @@ -109,6 +108,10 @@ class DriftBackupRepository extends DriftDatabaseRepository { ) ..orderBy([(localAsset) => OrderingTerm.desc(localAsset.createdAt)]); + if (onlyHashed) { + query.where((lae) => lae.checksum.isNotNull()); + } + return query.map((localAsset) => localAsset.toDto()).get(); } } diff --git a/mobile/lib/infrastructure/repositories/db.repository.dart b/mobile/lib/infrastructure/repositories/db.repository.dart index 65d26d9747..548aa2e384 100644 --- a/mobile/lib/infrastructure/repositories/db.repository.dart +++ b/mobile/lib/infrastructure/repositories/db.repository.dart @@ -10,6 +10,7 @@ import 'package:immich_mobile/infrastructure/entities/exif.entity.dart'; import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart'; import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/memory.entity.dart'; import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/partner.entity.dart'; @@ -62,6 +63,7 @@ class IsarDatabaseRepository implements IDatabaseRepository { PersonEntity, AssetFaceEntity, StoreEntity, + TrashedLocalAssetEntity, ], include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'}, ) @@ -93,7 +95,7 @@ class Drift extends $Drift implements IDatabaseRepository { } @override - int get schemaVersion => 11; + int get schemaVersion => 13; @override MigrationStrategy get migration => MigrationStrategy( @@ -159,6 +161,30 @@ class Drift extends $Drift implements IDatabaseRepository { from10To11: (m, v11) async { await m.addColumn(v11.localAlbumAssetEntity, v11.localAlbumAssetEntity.marker_); }, + from11To12: (m, v12) async { + final localToUTCMapping = { + v12.localAssetEntity: [v12.localAssetEntity.createdAt, v12.localAssetEntity.updatedAt], + v12.localAlbumEntity: [v12.localAlbumEntity.updatedAt], + }; + + for (final entry in localToUTCMapping.entries) { + final table = entry.key; + await m.alterTable( + TableMigration( + table, + columnTransformer: { + for (final column in entry.value) + column: column.modify(const DateTimeModifier.utc()).strftime('%Y-%m-%dT%H:%M:%fZ'), + }, + ), + ); + } + }, + from12To13: (m, v13) async { + await m.create(v13.trashedLocalAssetEntity); + await m.createIndex(v13.idxTrashedLocalAssetChecksum); + await m.createIndex(v13.idxTrashedLocalAssetAlbum); + }, ), ); diff --git a/mobile/lib/infrastructure/repositories/db.repository.drift.dart b/mobile/lib/infrastructure/repositories/db.repository.drift.dart index e39ed8a560..bd72da949c 100644 --- a/mobile/lib/infrastructure/repositories/db.repository.drift.dart +++ b/mobile/lib/infrastructure/repositories/db.repository.drift.dart @@ -37,9 +37,11 @@ import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.da as i17; import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart' as i18; -import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart' +import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart' as i19; -import 'package:drift/internal/modular.dart' as i20; +import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart' + as i20; +import 'package:drift/internal/modular.dart' as i21; abstract class $Drift extends i0.GeneratedDatabase { $Drift(i0.QueryExecutor e) : super(e); @@ -77,9 +79,11 @@ abstract class $Drift extends i0.GeneratedDatabase { late final i17.$AssetFaceEntityTable assetFaceEntity = i17 .$AssetFaceEntityTable(this); late final i18.$StoreEntityTable storeEntity = i18.$StoreEntityTable(this); - i19.MergedAssetDrift get mergedAssetDrift => i20.ReadDatabaseContainer( + late final i19.$TrashedLocalAssetEntityTable trashedLocalAssetEntity = i19 + .$TrashedLocalAssetEntityTable(this); + i20.MergedAssetDrift get mergedAssetDrift => i21.ReadDatabaseContainer( this, - ).accessor(i19.MergedAssetDrift.new); + ).accessor(i20.MergedAssetDrift.new); @override Iterable> get allTables => allSchemaEntities.whereType>(); @@ -108,7 +112,10 @@ abstract class $Drift extends i0.GeneratedDatabase { personEntity, assetFaceEntity, storeEntity, + trashedLocalAssetEntity, i11.idxLatLng, + i19.idxTrashedLocalAssetChecksum, + i19.idxTrashedLocalAssetAlbum, ]; @override i0.StreamQueryUpdateRules @@ -336,4 +343,9 @@ class $DriftManager { i17.$$AssetFaceEntityTableTableManager(_db, _db.assetFaceEntity); i18.$$StoreEntityTableTableManager get storeEntity => i18.$$StoreEntityTableTableManager(_db, _db.storeEntity); + i19.$$TrashedLocalAssetEntityTableTableManager get trashedLocalAssetEntity => + i19.$$TrashedLocalAssetEntityTableTableManager( + _db, + _db.trashedLocalAssetEntity, + ); } diff --git a/mobile/lib/infrastructure/repositories/db.repository.steps.dart b/mobile/lib/infrastructure/repositories/db.repository.steps.dart index 7910d9fcee..f2d87a7f83 100644 --- a/mobile/lib/infrastructure/repositories/db.repository.steps.dart +++ b/mobile/lib/infrastructure/repositories/db.repository.steps.dart @@ -4659,6 +4659,832 @@ class Shape22 extends i0.VersionedTable { columnsByName['marker']! as i1.GeneratedColumn; } +final class Schema12 extends i0.VersionedSchema { + Schema12({required super.database}) : super(version: 12); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + idxLatLng, + ]; + late final Shape20 userEntity = Shape20( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_84, + _column_85, + _column_91, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape17 remoteAssetEntity = Shape17( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_13, + _column_14, + _column_15, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_86, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 stackEntity = Shape3( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_0, _column_9, _column_5, _column_15, _column_75], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape2 localAssetEntity = Shape2( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_22, + _column_14, + _column_23, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape9 remoteAlbumEntity = Shape9( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_56, + _column_9, + _column_5, + _column_15, + _column_57, + _column_58, + _column_59, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape19 localAlbumEntity = Shape19( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_5, + _column_31, + _column_32, + _column_90, + _column_33, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape22 localAlbumAssetEntity = Shape22( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_34, _column_35, _column_33], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index idxRemoteAssetOwnerChecksum = i1.Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Shape21 authUserEntity = Shape21( + source: i0.VersionedTable( + entityName: 'auth_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_2, + _column_84, + _column_85, + _column_92, + _column_93, + _column_7, + _column_94, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_25, _column_26, _column_27], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape5 partnerEntity = Shape5( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_28, _column_29, _column_30], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape8 remoteExifEntity = Shape8( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_37, + _column_38, + _column_39, + _column_40, + _column_41, + _column_11, + _column_10, + _column_42, + _column_43, + _column_44, + _column_45, + _column_46, + _column_47, + _column_48, + _column_49, + _column_50, + _column_51, + _column_52, + _column_53, + _column_54, + _column_55, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_36, _column_60], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_60, _column_25, _column_61], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape11 memoryEntity = Shape11( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_18, + _column_15, + _column_8, + _column_62, + _column_63, + _column_64, + _column_65, + _column_66, + _column_67, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_36, _column_68], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape14 personEntity = Shape14( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_15, + _column_1, + _column_69, + _column_71, + _column_72, + _column_73, + _column_74, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape15 assetFaceEntity = Shape15( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_36, + _column_76, + _column_77, + _column_78, + _column_79, + _column_80, + _column_81, + _column_82, + _column_83, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape18 storeEntity = Shape18( + source: i0.VersionedTable( + entityName: 'store_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_87, _column_88, _column_89], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLatLng = i1.Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); +} + +final class Schema13 extends i0.VersionedSchema { + Schema13({required super.database}) : super(version: 13); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + idxLatLng, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + ]; + late final Shape20 userEntity = Shape20( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_84, + _column_85, + _column_91, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape17 remoteAssetEntity = Shape17( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_13, + _column_14, + _column_15, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_86, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 stackEntity = Shape3( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_0, _column_9, _column_5, _column_15, _column_75], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape2 localAssetEntity = Shape2( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_22, + _column_14, + _column_23, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape9 remoteAlbumEntity = Shape9( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_56, + _column_9, + _column_5, + _column_15, + _column_57, + _column_58, + _column_59, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape19 localAlbumEntity = Shape19( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_5, + _column_31, + _column_32, + _column_90, + _column_33, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape22 localAlbumAssetEntity = Shape22( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_34, _column_35, _column_33], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index idxRemoteAssetOwnerChecksum = i1.Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Shape21 authUserEntity = Shape21( + source: i0.VersionedTable( + entityName: 'auth_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_2, + _column_84, + _column_85, + _column_92, + _column_93, + _column_7, + _column_94, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_25, _column_26, _column_27], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape5 partnerEntity = Shape5( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_28, _column_29, _column_30], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape8 remoteExifEntity = Shape8( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_37, + _column_38, + _column_39, + _column_40, + _column_41, + _column_11, + _column_10, + _column_42, + _column_43, + _column_44, + _column_45, + _column_46, + _column_47, + _column_48, + _column_49, + _column_50, + _column_51, + _column_52, + _column_53, + _column_54, + _column_55, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_36, _column_60], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_60, _column_25, _column_61], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape11 memoryEntity = Shape11( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_18, + _column_15, + _column_8, + _column_62, + _column_63, + _column_64, + _column_65, + _column_66, + _column_67, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_36, _column_68], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape14 personEntity = Shape14( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_15, + _column_1, + _column_69, + _column_71, + _column_72, + _column_73, + _column_74, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape15 assetFaceEntity = Shape15( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_36, + _column_76, + _column_77, + _column_78, + _column_79, + _column_80, + _column_81, + _column_82, + _column_83, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape18 storeEntity = Shape18( + source: i0.VersionedTable( + entityName: 'store_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_87, _column_88, _column_89], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape23 trashedLocalAssetEntity = Shape23( + source: i0.VersionedTable( + entityName: 'trashed_local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id, album_id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_95, + _column_22, + _column_14, + _column_23, + ], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLatLng = i1.Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + final i1.Index idxTrashedLocalAssetChecksum = i1.Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + final i1.Index idxTrashedLocalAssetAlbum = i1.Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); +} + +class Shape23 extends i0.VersionedTable { + Shape23({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get type => + columnsByName['type']! as i1.GeneratedColumn; + i1.GeneratedColumn get createdAt => + columnsByName['created_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get updatedAt => + columnsByName['updated_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get width => + columnsByName['width']! as i1.GeneratedColumn; + i1.GeneratedColumn get height => + columnsByName['height']! as i1.GeneratedColumn; + i1.GeneratedColumn get durationInSeconds => + columnsByName['duration_in_seconds']! as i1.GeneratedColumn; + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get albumId => + columnsByName['album_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get checksum => + columnsByName['checksum']! as i1.GeneratedColumn; + i1.GeneratedColumn get isFavorite => + columnsByName['is_favorite']! as i1.GeneratedColumn; + i1.GeneratedColumn get orientation => + columnsByName['orientation']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_95(String aliasedName) => + i1.GeneratedColumn( + 'album_id', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); i0.MigrationStepWithVersion migrationSteps({ required Future Function(i1.Migrator m, Schema2 schema) from1To2, required Future Function(i1.Migrator m, Schema3 schema) from2To3, @@ -4670,6 +5496,8 @@ i0.MigrationStepWithVersion migrationSteps({ required Future Function(i1.Migrator m, Schema9 schema) from8To9, required Future Function(i1.Migrator m, Schema10 schema) from9To10, required Future Function(i1.Migrator m, Schema11 schema) from10To11, + required Future Function(i1.Migrator m, Schema12 schema) from11To12, + required Future Function(i1.Migrator m, Schema13 schema) from12To13, }) { return (currentVersion, database) async { switch (currentVersion) { @@ -4723,6 +5551,16 @@ i0.MigrationStepWithVersion migrationSteps({ final migrator = i1.Migrator(database, schema); await from10To11(migrator, schema); return 11; + case 11: + final schema = Schema12(database: database); + final migrator = i1.Migrator(database, schema); + await from11To12(migrator, schema); + return 12; + case 12: + final schema = Schema13(database: database); + final migrator = i1.Migrator(database, schema); + await from12To13(migrator, schema); + return 13; default: throw ArgumentError.value('Unknown migration from $currentVersion'); } @@ -4740,6 +5578,8 @@ i1.OnUpgrade stepByStep({ required Future Function(i1.Migrator m, Schema9 schema) from8To9, required Future Function(i1.Migrator m, Schema10 schema) from9To10, required Future Function(i1.Migrator m, Schema11 schema) from10To11, + required Future Function(i1.Migrator m, Schema12 schema) from11To12, + required Future Function(i1.Migrator m, Schema13 schema) from12To13, }) => i0.VersionedSchema.stepByStepHelper( step: migrationSteps( from1To2: from1To2, @@ -4752,5 +5592,7 @@ i1.OnUpgrade stepByStep({ from8To9: from8To9, from9To10: from9To10, from10To11: from10To11, + from11To12: from11To12, + from12To13: from12To13, ), ); diff --git a/mobile/lib/infrastructure/repositories/local_album.repository.dart b/mobile/lib/infrastructure/repositories/local_album.repository.dart index e4bff24879..63259bc62b 100644 --- a/mobile/lib/infrastructure/repositories/local_album.repository.dart +++ b/mobile/lib/infrastructure/repositories/local_album.repository.dart @@ -361,15 +361,13 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository { return _db.managers.localAlbumEntity.count(); } - Future unlinkRemoteAlbum(String id) async { - return _db.localAlbumEntity.update() - ..where((row) => row.id.equals(id)) - ..write(const LocalAlbumEntityCompanion(linkedRemoteAlbumId: Value(null))); + Future unlinkRemoteAlbum(String id) async { + final query = _db.localAlbumEntity.update()..where((row) => row.id.equals(id)); + await query.write(const LocalAlbumEntityCompanion(linkedRemoteAlbumId: Value(null))); } - Future linkRemoteAlbum(String localAlbumId, String remoteAlbumId) async { - return _db.localAlbumEntity.update() - ..where((row) => row.id.equals(localAlbumId)) - ..write(LocalAlbumEntityCompanion(linkedRemoteAlbumId: Value(remoteAlbumId))); + Future linkRemoteAlbum(String localAlbumId, String remoteAlbumId) async { + final query = _db.localAlbumEntity.update()..where((row) => row.id.equals(localAlbumId)); + await query.write(LocalAlbumEntityCompanion(linkedRemoteAlbumId: Value(remoteAlbumId))); } } diff --git a/mobile/lib/infrastructure/repositories/local_asset.repository.dart b/mobile/lib/infrastructure/repositories/local_asset.repository.dart index 2b76472c9e..4d30e09716 100644 --- a/mobile/lib/infrastructure/repositories/local_asset.repository.dart +++ b/mobile/lib/infrastructure/repositories/local_asset.repository.dart @@ -1,4 +1,6 @@ +import 'package:collection/collection.dart'; import 'package:drift/drift.dart'; +import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart'; @@ -8,6 +10,7 @@ import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; class DriftLocalAssetRepository extends DriftDatabaseRepository { final Drift _db; + const DriftLocalAssetRepository(this._db) : super(_db); SingleOrNullSelectable _assetSelectable(String id) { @@ -95,4 +98,32 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository { } return query.map((localAlbum) => localAlbum.toDto()).get(); } + + Future>> getAssetsFromBackupAlbums(Iterable checksums) async { + if (checksums.isEmpty) { + return {}; + } + + final result = >{}; + + for (final slice in checksums.toSet().slices(kDriftMaxChunk)) { + final rows = + await (_db.select(_db.localAlbumAssetEntity).join([ + innerJoin(_db.localAlbumEntity, _db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id)), + innerJoin(_db.localAssetEntity, _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id)), + ])..where( + _db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.selected) & + _db.localAssetEntity.checksum.isIn(slice), + )) + .get(); + + for (final row in rows) { + final albumId = row.readTable(_db.localAlbumAssetEntity).albumId; + final assetData = row.readTable(_db.localAssetEntity); + final asset = assetData.toDto(); + (result[albumId] ??= []).add(asset); + } + } + return result; + } } diff --git a/mobile/lib/infrastructure/repositories/remote_album.repository.dart b/mobile/lib/infrastructure/repositories/remote_album.repository.dart index 5dfe4ac9b3..d7d4a250ad 100644 --- a/mobile/lib/infrastructure/repositories/remote_album.repository.dart +++ b/mobile/lib/infrastructure/repositories/remote_album.repository.dart @@ -62,7 +62,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { .toDto( assetCount: row.read(assetCount) ?? 0, ownerName: row.read(_db.userEntity.name)!, - isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 2, + isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0, ), ) .get(); @@ -107,7 +107,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { .toDto( assetCount: row.read(assetCount) ?? 0, ownerName: row.read(_db.userEntity.name)!, - isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 2, + isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0, ), ) .getSingleOrNull(); @@ -221,6 +221,15 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { .get(); } + Future getUserRole(String albumId, String userId) async { + final query = _db.remoteAlbumUserEntity.select() + ..where((row) => row.albumId.equals(albumId) & row.userId.equals(userId)) + ..limit(1); + + final result = await query.getSingleOrNull(); + return result?.role; + } + Future> getAssets(String albumId) { final query = _db.remoteAlbumAssetEntity.select().join([ innerJoin(_db.remoteAssetEntity, _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId)), @@ -305,8 +314,9 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { .readTable(_db.remoteAlbumEntity) .toDto( ownerName: row.read(_db.userEntity.name)!, - isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 2, + isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0, ); + return album; }).watchSingleOrNull(); } @@ -372,6 +382,61 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { return query.map((row) => row.read(_db.remoteAssetEntity.id)!).get(); } + + Future> getAlbumsContainingAsset(String assetId) async { + // Note: this needs to be 2 queries as the where clause filtering causes the assetCount to always be 1 + final albumIdsQuery = _db.remoteAlbumAssetEntity.selectOnly() + ..addColumns([_db.remoteAlbumAssetEntity.albumId]) + ..where(_db.remoteAlbumAssetEntity.assetId.equals(assetId)); + + final albumIds = await albumIdsQuery.map((row) => row.read(_db.remoteAlbumAssetEntity.albumId)!).get(); + + if (albumIds.isEmpty) { + return []; + } + + final assetCount = _db.remoteAlbumAssetEntity.assetId.count(distinct: true); + final query = + _db.remoteAlbumEntity.select().join([ + leftOuterJoin( + _db.remoteAlbumAssetEntity, + _db.remoteAlbumAssetEntity.albumId.equalsExp(_db.remoteAlbumEntity.id), + useColumns: false, + ), + leftOuterJoin( + _db.remoteAssetEntity, + _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId), + useColumns: false, + ), + leftOuterJoin( + _db.userEntity, + _db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId), + useColumns: false, + ), + leftOuterJoin( + _db.remoteAlbumUserEntity, + _db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id), + useColumns: false, + ), + ]) + ..where(_db.remoteAlbumEntity.id.isIn(albumIds) & _db.remoteAssetEntity.deletedAt.isNull()) + ..addColumns([assetCount]) + ..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)]) + ..addColumns([_db.userEntity.name]) + ..groupBy([_db.remoteAlbumEntity.id]); + + return query + .map( + (row) => row + .readTable(_db.remoteAlbumEntity) + .toDto( + ownerName: row.read(_db.userEntity.name) ?? '', + isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0, + assetCount: row.read(assetCount) ?? 0, + ), + ) + .get(); + } } extension on RemoteAlbumEntityData { diff --git a/mobile/lib/infrastructure/repositories/remote_asset.repository.dart b/mobile/lib/infrastructure/repositories/remote_asset.repository.dart index 092bb728d9..96c204ea0e 100644 --- a/mobile/lib/infrastructure/repositories/remote_asset.repository.dart +++ b/mobile/lib/infrastructure/repositories/remote_asset.repository.dart @@ -12,6 +12,7 @@ import 'package:maplibre_gl/maplibre_gl.dart'; class RemoteAssetRepository extends DriftDatabaseRepository { final Drift _db; + const RemoteAssetRepository(this._db) : super(_db); /// For testing purposes @@ -81,9 +82,11 @@ class RemoteAssetRepository extends DriftDatabaseRepository { .getSingleOrNull(); } - Future> getPlaces() { + Future> getPlaces(String userId) { final asset = Subquery( - _db.remoteAssetEntity.select()..orderBy([(row) => OrderingTerm.desc(row.createdAt)]), + _db.remoteAssetEntity.select() + ..where((row) => row.ownerId.equals(userId)) + ..orderBy([(row) => OrderingTerm.desc(row.createdAt)]), "asset", ); diff --git a/mobile/lib/infrastructure/repositories/search_api.repository.dart b/mobile/lib/infrastructure/repositories/search_api.repository.dart index dd72333398..34870dc1b3 100644 --- a/mobile/lib/infrastructure/repositories/search_api.repository.dart +++ b/mobile/lib/infrastructure/repositories/search_api.repository.dart @@ -5,6 +5,7 @@ import 'package:openapi/api.dart'; class SearchApiRepository extends ApiRepository { final SearchApi _api; + const SearchApiRepository(this._api); Future search(SearchFilter filter, int page) { @@ -15,10 +16,12 @@ class SearchApiRepository extends ApiRepository { type = AssetTypeEnum.VIDEO; } - if (filter.context != null && filter.context!.isNotEmpty) { + if ((filter.context != null && filter.context!.isNotEmpty) || + (filter.assetId != null && filter.assetId!.isNotEmpty)) { return _api.searchSmart( SmartSearchDto( - query: filter.context!, + query: filter.context, + queryAssetId: filter.assetId, language: filter.language, country: filter.location.country, state: filter.location.state, @@ -43,6 +46,7 @@ class SearchApiRepository extends ApiRepository { originalFileName: filter.filename != null && filter.filename!.isNotEmpty ? filter.filename : null, country: filter.location.country, description: filter.description != null && filter.description!.isNotEmpty ? filter.description : null, + ocr: filter.ocr != null && filter.ocr!.isNotEmpty ? filter.ocr : null, state: filter.location.state, city: filter.location.city, make: filter.camera.make, diff --git a/mobile/lib/infrastructure/repositories/storage.repository.dart b/mobile/lib/infrastructure/repositories/storage.repository.dart index 164fa04529..9532025d58 100644 --- a/mobile/lib/infrastructure/repositories/storage.repository.dart +++ b/mobile/lib/infrastructure/repositories/storage.repository.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:logging/logging.dart'; import 'package:photo_manager/photo_manager.dart'; @@ -89,5 +90,17 @@ class StorageRepository { } catch (error, stackTrace) { log.warning("Error clearing cache", error, stackTrace); } + + if (!CurrentPlatform.isIOS) { + return; + } + + try { + if (await Directory.systemTemp.exists()) { + await Directory.systemTemp.delete(recursive: true); + } + } catch (error, stackTrace) { + log.warning("Error deleting temporary directory", error, stackTrace); + } } } diff --git a/mobile/lib/infrastructure/repositories/sync_api.repository.dart b/mobile/lib/infrastructure/repositories/sync_api.repository.dart index 3969286d28..8bf2e80579 100644 --- a/mobile/lib/infrastructure/repositories/sync_api.repository.dart +++ b/mobile/lib/infrastructure/repositories/sync_api.repository.dart @@ -110,7 +110,6 @@ class SyncApiRepository { await onData(_parseLines(lines), abort, reset); } } catch (error, stack) { - _logger.severe("Error processing stream", error, stack); return Future.error(error, stack); } finally { client.close(); diff --git a/mobile/lib/infrastructure/repositories/sync_stream.repository.dart b/mobile/lib/infrastructure/repositories/sync_stream.repository.dart index f4720fb110..8e087f836f 100644 --- a/mobile/lib/infrastructure/repositories/sync_stream.repository.dart +++ b/mobile/lib/infrastructure/repositories/sync_stream.repository.dart @@ -51,6 +51,7 @@ class SyncStreamRepository extends DriftDatabaseRepository { await _db.remoteAssetEntity.deleteAll(); await _db.remoteExifEntity.deleteAll(); await _db.stackEntity.deleteAll(); + await _db.authUserEntity.deleteAll(); await _db.userEntity.deleteAll(); await _db.userMetadataEntity.deleteAll(); }); @@ -591,6 +592,40 @@ class SyncStreamRepository extends DriftDatabaseRepository { rethrow; } } + + Future pruneAssets() async { + try { + await _db.transaction(() async { + final authQuery = _db.authUserEntity.selectOnly() + ..addColumns([_db.authUserEntity.id]) + ..limit(1); + final currentUserId = await authQuery.map((row) => row.read(_db.authUserEntity.id)).getSingleOrNull(); + if (currentUserId == null) { + _logger.warning('No authenticated user found during pruneAssets. Skipping asset pruning.'); + return; + } + + final partnerQuery = _db.partnerEntity.selectOnly() + ..addColumns([_db.partnerEntity.sharedById]) + ..where(_db.partnerEntity.sharedWithId.equals(currentUserId)); + final partnerIds = await partnerQuery.map((row) => row.read(_db.partnerEntity.sharedById)).get(); + + final validUsers = {currentUserId, ...partnerIds.nonNulls}; + + // Asset is not owned by the current user or any of their partners and is not part of any (shared) album + // Likely a stale asset that was previously shared but has been removed + await _db.remoteAssetEntity.deleteWhere((asset) { + return asset.ownerId.isNotIn(validUsers) & + asset.id.isNotInQuery( + _db.remoteAlbumAssetEntity.selectOnly()..addColumns([_db.remoteAlbumAssetEntity.assetId]), + ); + }); + }); + } catch (error, stack) { + _logger.severe('Error: pruneAssets', error, stack); + // We do not rethrow here as this is a client-only cleanup and should not affect the sync process + } + } } extension on AssetTypeEnum { diff --git a/mobile/lib/infrastructure/repositories/timeline.repository.dart b/mobile/lib/infrastructure/repositories/timeline.repository.dart index 86f68c397e..1fc0ee43e5 100644 --- a/mobile/lib/infrastructure/repositories/timeline.repository.dart +++ b/mobile/lib/infrastructure/repositories/timeline.repository.dart @@ -35,6 +35,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository { TimelineQuery main(List userIds, GroupAssetsBy groupBy) => ( bucketSource: () => _watchMainBucket(userIds, groupBy: groupBy), assetSource: (offset, count) => _getMainBucketAssets(userIds, offset: offset, count: count), + origin: TimelineOrigin.main, ); Stream> _watchMainBucket(List userIds, {GroupAssetsBy groupBy = GroupAssetsBy.day}) { @@ -43,7 +44,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository { } return _db.mergedAssetDrift.mergedBucket(userIds: userIds, groupBy: groupBy.index).map((row) { - final date = row.bucketDate.dateFmt(groupBy); + final date = row.bucketDate.truncateDate(groupBy); return TimeBucket(date: date, assetCount: row.assetCount); }).watch(); } @@ -91,6 +92,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository { TimelineQuery localAlbum(String albumId, GroupAssetsBy groupBy) => ( bucketSource: () => _watchLocalAlbumBucket(albumId, groupBy: groupBy), assetSource: (offset, count) => _getLocalAlbumBucketAssets(albumId, offset: offset, count: count), + origin: TimelineOrigin.localAlbum, ); Stream> _watchLocalAlbumBucket(String albumId, {GroupAssetsBy groupBy = GroupAssetsBy.day}) { @@ -123,7 +125,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository { ..orderBy([OrderingTerm.desc(dateExp)]); return query.map((row) { - final timeline = row.read(dateExp)!.dateFmt(groupBy); + final timeline = row.read(dateExp)!.truncateDate(groupBy); final assetCount = row.read(assetCountExp)!; return TimeBucket(date: timeline, assetCount: assetCount); }).watch(); @@ -148,15 +150,15 @@ class DriftTimelineRepository extends DriftDatabaseRepository { ..orderBy([OrderingTerm.desc(_db.localAssetEntity.createdAt)]) ..limit(count, offset: offset); - return query.map((row) { - final asset = row.readTable(_db.localAssetEntity).toDto(); - return asset.copyWith(remoteId: row.read(_db.remoteAssetEntity.id)); - }).get(); + return query + .map((row) => row.readTable(_db.localAssetEntity).toDto(remoteId: row.read(_db.remoteAssetEntity.id))) + .get(); } TimelineQuery remoteAlbum(String albumId, GroupAssetsBy groupBy) => ( bucketSource: () => _watchRemoteAlbumBucket(albumId, groupBy: groupBy), assetSource: (offset, count) => _getRemoteAlbumBucketAssets(albumId, offset: offset, count: count), + origin: TimelineOrigin.remoteAlbum, ); Stream> _watchRemoteAlbumBucket(String albumId, {GroupAssetsBy groupBy = GroupAssetsBy.day}) { @@ -165,17 +167,15 @@ class DriftTimelineRepository extends DriftDatabaseRepository { .count(where: (row) => row.albumId.equals(albumId)) .map(_generateBuckets) .watch() - .map((results) => results.isNotEmpty ? results.first : []) - .handleError((error) { - return []; - }); + .map((results) => results.isNotEmpty ? results.first : const []) + .handleError((error) => const []); } return (_db.remoteAlbumEntity.select()..where((row) => row.id.equals(albumId))) .watch() .switchMap((albums) { if (albums.isEmpty) { - return Stream.value([]); + return Stream.value(const []); } final album = albums.first; @@ -202,15 +202,13 @@ class DriftTimelineRepository extends DriftDatabaseRepository { } return query.map((row) { - final timeline = row.read(dateExp)!.dateFmt(groupBy); + final timeline = row.read(dateExp)!.truncateDate(groupBy); final assetCount = row.read(assetCountExp)!; return TimeBucket(date: timeline, assetCount: assetCount); }).watch(); }) - .handleError((error) { - // If there's an error (e.g., album was deleted), return empty buckets - return []; - }); + // If there's an error (e.g., album was deleted), return empty buckets + .handleError((error) => const []); } Future> _getRemoteAlbumBucketAssets(String albumId, {required int offset, required int count}) async { @@ -218,17 +216,22 @@ class DriftTimelineRepository extends DriftDatabaseRepository { // If album doesn't exist (was deleted), return empty list if (albumData == null) { - return []; + return const []; } final isAscending = albumData.order == AlbumAssetOrder.asc; - final query = _db.remoteAssetEntity.select().join([ + final query = _db.remoteAssetEntity.select().addColumns([_db.localAssetEntity.id]).join([ innerJoin( _db.remoteAlbumAssetEntity, _db.remoteAlbumAssetEntity.assetId.equalsExp(_db.remoteAssetEntity.id), useColumns: false, ), + leftOuterJoin( + _db.localAssetEntity, + _db.remoteAssetEntity.checksum.equalsExp(_db.localAssetEntity.checksum), + useColumns: false, + ), ])..where(_db.remoteAssetEntity.deletedAt.isNull() & _db.remoteAlbumAssetEntity.albumId.equals(albumId)); if (isAscending) { @@ -239,18 +242,22 @@ class DriftTimelineRepository extends DriftDatabaseRepository { query.limit(count, offset: offset); - return query.map((row) => row.readTable(_db.remoteAssetEntity).toDto()).get(); + return query + .map((row) => row.readTable(_db.remoteAssetEntity).toDto(localId: row.read(_db.localAssetEntity.id))) + .get(); } - TimelineQuery fromAssets(List assets) => ( + TimelineQuery fromAssets(List assets, TimelineOrigin origin) => ( bucketSource: () => Stream.value(_generateBuckets(assets.length)), - assetSource: (offset, count) => Future.value(assets.skip(offset).take(count).toList()), + assetSource: (offset, count) => Future.value(assets.skip(offset).take(count).toList(growable: false)), + origin: origin, ); TimelineQuery remote(String ownerId, GroupAssetsBy groupBy) => _remoteQueryBuilder( filter: (row) => row.deletedAt.isNull() & row.visibility.equalsValue(AssetVisibility.timeline) & row.ownerId.equals(ownerId), groupBy: groupBy, + origin: TimelineOrigin.remoteAssets, ); TimelineQuery favorite(String userId, GroupAssetsBy groupBy) => _remoteQueryBuilder( @@ -260,11 +267,13 @@ class DriftTimelineRepository extends DriftDatabaseRepository { row.ownerId.equals(userId) & row.visibility.equalsValue(AssetVisibility.timeline), groupBy: groupBy, + origin: TimelineOrigin.favorite, ); TimelineQuery trash(String userId, GroupAssetsBy groupBy) => _remoteQueryBuilder( filter: (row) => row.deletedAt.isNotNull() & row.ownerId.equals(userId), groupBy: groupBy, + origin: TimelineOrigin.trash, joinLocal: true, ); @@ -272,11 +281,13 @@ class DriftTimelineRepository extends DriftDatabaseRepository { filter: (row) => row.deletedAt.isNull() & row.ownerId.equals(userId) & row.visibility.equalsValue(AssetVisibility.archive), groupBy: groupBy, + origin: TimelineOrigin.archive, ); TimelineQuery locked(String userId, GroupAssetsBy groupBy) => _remoteQueryBuilder( filter: (row) => row.deletedAt.isNull() & row.visibility.equalsValue(AssetVisibility.locked) & row.ownerId.equals(userId), + origin: TimelineOrigin.lockedFolder, groupBy: groupBy, ); @@ -286,17 +297,20 @@ class DriftTimelineRepository extends DriftDatabaseRepository { row.type.equalsValue(AssetType.video) & row.visibility.equalsValue(AssetVisibility.timeline) & row.ownerId.equals(userId), + origin: TimelineOrigin.video, groupBy: groupBy, ); TimelineQuery place(String place, GroupAssetsBy groupBy) => ( bucketSource: () => _watchPlaceBucket(place, groupBy: groupBy), assetSource: (offset, count) => _getPlaceBucketAssets(place, offset: offset, count: count), + origin: TimelineOrigin.place, ); TimelineQuery person(String userId, String personId, GroupAssetsBy groupBy) => ( bucketSource: () => _watchPersonBucket(userId, personId, groupBy: groupBy), assetSource: (offset, count) => _getPersonBucketAssets(userId, personId, offset: offset, count: count), + origin: TimelineOrigin.person, ); Stream> _watchPlaceBucket(String place, {GroupAssetsBy groupBy = GroupAssetsBy.day}) { @@ -326,7 +340,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository { ..orderBy([OrderingTerm.desc(dateExp)]); return query.map((row) { - final timeline = row.read(dateExp)!.dateFmt(groupBy); + final timeline = row.read(dateExp)!.truncateDate(groupBy); final assetCount = row.read(assetCountExp)!; return TimeBucket(date: timeline, assetCount: assetCount); }).watch(); @@ -397,7 +411,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository { ..orderBy([OrderingTerm.desc(dateExp)]); return query.map((row) { - final timeline = row.read(dateExp)!.dateFmt(groupBy); + final timeline = row.read(dateExp)!.truncateDate(groupBy); final assetCount = row.read(assetCountExp)!; return TimeBucket(date: timeline, assetCount: assetCount); }).watch(); @@ -429,12 +443,17 @@ class DriftTimelineRepository extends DriftDatabaseRepository { return query.map((row) => row.readTable(_db.remoteAssetEntity).toDto()).get(); } - TimelineQuery map(LatLngBounds bounds, GroupAssetsBy groupBy) => ( - bucketSource: () => _watchMapBucket(bounds, groupBy: groupBy), - assetSource: (offset, count) => _getMapBucketAssets(bounds, offset: offset, count: count), + TimelineQuery map(String userId, LatLngBounds bounds, GroupAssetsBy groupBy) => ( + bucketSource: () => _watchMapBucket(userId, bounds, groupBy: groupBy), + assetSource: (offset, count) => _getMapBucketAssets(userId, bounds, offset: offset, count: count), + origin: TimelineOrigin.map, ); - Stream> _watchMapBucket(LatLngBounds bounds, {GroupAssetsBy groupBy = GroupAssetsBy.day}) { + Stream> _watchMapBucket( + String userId, + LatLngBounds bounds, { + GroupAssetsBy groupBy = GroupAssetsBy.day, + }) { if (groupBy == GroupAssetsBy.none) { // TODO: Support GroupAssetsBy.none throw UnsupportedError("GroupAssetsBy.none is not supported for _watchMapBucket"); @@ -453,7 +472,8 @@ class DriftTimelineRepository extends DriftDatabaseRepository { ), ]) ..where( - _db.remoteExifEntity.inBounds(bounds) & + _db.remoteAssetEntity.ownerId.equals(userId) & + _db.remoteExifEntity.inBounds(bounds) & _db.remoteAssetEntity.visibility.equalsValue(AssetVisibility.timeline) & _db.remoteAssetEntity.deletedAt.isNull(), ) @@ -461,13 +481,18 @@ class DriftTimelineRepository extends DriftDatabaseRepository { ..orderBy([OrderingTerm.desc(dateExp)]); return query.map((row) { - final timeline = row.read(dateExp)!.dateFmt(groupBy); + final timeline = row.read(dateExp)!.truncateDate(groupBy); final assetCount = row.read(assetCountExp)!; return TimeBucket(date: timeline, assetCount: assetCount); }).watch(); } - Future> _getMapBucketAssets(LatLngBounds bounds, {required int offset, required int count}) { + Future> _getMapBucketAssets( + String userId, + LatLngBounds bounds, { + required int offset, + required int count, + }) { final query = _db.remoteAssetEntity.select().join([ innerJoin( @@ -477,7 +502,8 @@ class DriftTimelineRepository extends DriftDatabaseRepository { ), ]) ..where( - _db.remoteExifEntity.inBounds(bounds) & + _db.remoteAssetEntity.ownerId.equals(userId) & + _db.remoteExifEntity.inBounds(bounds) & _db.remoteAssetEntity.visibility.equalsValue(AssetVisibility.timeline) & _db.remoteAssetEntity.deletedAt.isNull(), ) @@ -486,8 +512,10 @@ class DriftTimelineRepository extends DriftDatabaseRepository { return query.map((row) => row.readTable(_db.remoteAssetEntity).toDto()).get(); } + @pragma('vm:prefer-inline') TimelineQuery _remoteQueryBuilder({ required Expression Function($RemoteAssetEntityTable row) filter, + required TimelineOrigin origin, GroupAssetsBy groupBy = GroupAssetsBy.day, bool joinLocal = false, }) { @@ -495,6 +523,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository { bucketSource: () => _watchRemoteBucket(filter: filter, groupBy: groupBy), assetSource: (offset, count) => _getRemoteAssets(filter: filter, offset: offset, count: count, joinLocal: joinLocal), + origin: origin, ); } @@ -517,12 +546,13 @@ class DriftTimelineRepository extends DriftDatabaseRepository { ..orderBy([OrderingTerm.desc(dateExp)]); return query.map((row) { - final timeline = row.read(dateExp)!.dateFmt(groupBy); + final timeline = row.read(dateExp)!.truncateDate(groupBy); final assetCount = row.read(assetCountExp)!; return TimeBucket(date: timeline, assetCount: assetCount); }).watch(); } + @pragma('vm:prefer-inline') Future> _getRemoteAssets({ required Expression Function($RemoteAssetEntityTable row) filter, required int offset, @@ -543,11 +573,9 @@ class DriftTimelineRepository extends DriftDatabaseRepository { ..orderBy([OrderingTerm.desc(_db.remoteAssetEntity.createdAt)]) ..limit(count, offset: offset); - return query.map((row) { - final asset = row.readTable(_db.remoteAssetEntity).toDto(); - final localId = row.read(_db.localAssetEntity.id); - return asset.copyWith(localId: localId); - }).get(); + return query + .map((row) => row.readTable(_db.remoteAssetEntity).toDto(localId: row.read(_db.localAssetEntity.id))) + .get(); } else { final query = _db.remoteAssetEntity.select() ..where(filter) @@ -560,12 +588,12 @@ class DriftTimelineRepository extends DriftDatabaseRepository { } List _generateBuckets(int count) { - final buckets = List.generate( - (count / kTimelineNoneSegmentSize).floor(), - (_) => const Bucket(assetCount: kTimelineNoneSegmentSize), + final buckets = List.filled( + (count / kTimelineNoneSegmentSize).ceil(), + const Bucket(assetCount: kTimelineNoneSegmentSize), ); if (count % kTimelineNoneSegmentSize != 0) { - buckets.add(Bucket(assetCount: count % kTimelineNoneSegmentSize)); + buckets[buckets.length - 1] = Bucket(assetCount: count % kTimelineNoneSegmentSize); } return buckets; } @@ -584,16 +612,12 @@ extension on Expression { } extension on String { - DateTime dateFmt(GroupAssetsBy groupBy) { + DateTime truncateDate(GroupAssetsBy groupBy) { final format = switch (groupBy) { GroupAssetsBy.day || GroupAssetsBy.auto => "y-M-d", GroupAssetsBy.month => "y-M", GroupAssetsBy.none => throw ArgumentError("GroupAssetsBy.none is not supported for date formatting"), }; - try { - return DateFormat(format, 'en').parse(this); - } catch (e) { - throw FormatException("Invalid date format: $this", e); - } + return DateFormat(format, 'en').parse(this); } } diff --git a/mobile/lib/infrastructure/repositories/trashed_local_asset.repository.dart b/mobile/lib/infrastructure/repositories/trashed_local_asset.repository.dart new file mode 100644 index 0000000000..498e4227b7 --- /dev/null +++ b/mobile/lib/infrastructure/repositories/trashed_local_asset.repository.dart @@ -0,0 +1,252 @@ +import 'package:collection/collection.dart'; +import 'package:drift/drift.dart'; +import 'package:immich_mobile/constants/constants.dart'; +import 'package:immich_mobile/domain/models/album/local_album.model.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; + +typedef TrashedAsset = ({String albumId, LocalAsset asset}); + +class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository { + final Drift _db; + + const DriftTrashedLocalAssetRepository(this._db) : super(_db); + + Future updateHashes(Map hashes) { + if (hashes.isEmpty) { + return Future.value(); + } + return _db.batch((batch) async { + for (final entry in hashes.entries) { + batch.update( + _db.trashedLocalAssetEntity, + TrashedLocalAssetEntityCompanion(checksum: Value(entry.value)), + where: (e) => e.id.equals(entry.key), + ); + } + }); + } + + Future> getAssetsToHash(Iterable albumIds) { + final query = _db.trashedLocalAssetEntity.select()..where((r) => r.albumId.isIn(albumIds) & r.checksum.isNull()); + return query.map((row) => row.toLocalAsset()).get(); + } + + Future> getToRestore() async { + final selectedAlbumIds = (_db.selectOnly(_db.localAlbumEntity) + ..addColumns([_db.localAlbumEntity.id]) + ..where(_db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.selected))); + + final rows = + await (_db.select(_db.trashedLocalAssetEntity).join([ + innerJoin( + _db.remoteAssetEntity, + _db.remoteAssetEntity.checksum.equalsExp(_db.trashedLocalAssetEntity.checksum), + ), + ])..where( + _db.trashedLocalAssetEntity.albumId.isInQuery(selectedAlbumIds) & + _db.remoteAssetEntity.deletedAt.isNull(), + )) + .get(); + + return rows.map((result) => result.readTable(_db.trashedLocalAssetEntity).toLocalAsset()); + } + + /// Applies resulted snapshot of trashed assets: + /// - upserts incoming rows + /// - deletes rows that are not present in the snapshot + Future processTrashSnapshot(Iterable trashedAssets) async { + if (trashedAssets.isEmpty) { + await _db.delete(_db.trashedLocalAssetEntity).go(); + return; + } + final assetIds = trashedAssets.map((e) => e.asset.id).toSet(); + Map localChecksumById = await _getCachedChecksums(assetIds); + + return _db.transaction(() async { + await _db.batch((batch) { + for (final item in trashedAssets) { + final effectiveChecksum = localChecksumById[item.asset.id] ?? item.asset.checksum; + final companion = TrashedLocalAssetEntityCompanion.insert( + id: item.asset.id, + albumId: item.albumId, + checksum: Value(effectiveChecksum), + name: item.asset.name, + type: item.asset.type, + createdAt: Value(item.asset.createdAt), + updatedAt: Value(item.asset.updatedAt), + width: Value(item.asset.width), + height: Value(item.asset.height), + durationInSeconds: Value(item.asset.durationInSeconds), + isFavorite: Value(item.asset.isFavorite), + orientation: Value(item.asset.orientation), + ); + + batch.insert<$TrashedLocalAssetEntityTable, TrashedLocalAssetEntityData>( + _db.trashedLocalAssetEntity, + companion, + onConflict: DoUpdate((_) => companion, where: (old) => old.updatedAt.isNotValue(item.asset.updatedAt)), + ); + } + }); + + if (assetIds.length <= kDriftMaxChunk) { + await (_db.delete(_db.trashedLocalAssetEntity)..where((row) => row.id.isNotIn(assetIds))).go(); + } else { + final existingIds = await (_db.selectOnly( + _db.trashedLocalAssetEntity, + )..addColumns([_db.trashedLocalAssetEntity.id])).map((r) => r.read(_db.trashedLocalAssetEntity.id)!).get(); + final idToDelete = existingIds.where((id) => !assetIds.contains(id)); + for (final slice in idToDelete.slices(kDriftMaxChunk)) { + await (_db.delete(_db.trashedLocalAssetEntity)..where((t) => t.id.isIn(slice))).go(); + } + } + }); + } + + Stream watchCount() { + return (_db.selectOnly(_db.trashedLocalAssetEntity)..addColumns([_db.trashedLocalAssetEntity.id.count()])) + .watchSingle() + .map((row) => row.read(_db.trashedLocalAssetEntity.id.count()) ?? 0); + } + + Stream watchHashedCount() { + return (_db.selectOnly(_db.trashedLocalAssetEntity) + ..addColumns([_db.trashedLocalAssetEntity.id.count()]) + ..where(_db.trashedLocalAssetEntity.checksum.isNotNull())) + .watchSingle() + .map((row) => row.read(_db.trashedLocalAssetEntity.id.count()) ?? 0); + } + + Future trashLocalAsset(Map> assetsByAlbums) async { + if (assetsByAlbums.isEmpty) { + return; + } + + final companions = []; + final idToDelete = {}; + + for (final entry in assetsByAlbums.entries) { + for (final asset in entry.value) { + idToDelete.add(asset.id); + companions.add( + TrashedLocalAssetEntityCompanion( + id: Value(asset.id), + name: Value(asset.name), + albumId: Value(entry.key), + checksum: Value(asset.checksum), + type: Value(asset.type), + width: Value(asset.width), + height: Value(asset.height), + durationInSeconds: Value(asset.durationInSeconds), + isFavorite: Value(asset.isFavorite), + orientation: Value(asset.orientation), + createdAt: Value(asset.createdAt), + updatedAt: Value(asset.updatedAt), + ), + ); + } + } + + await _db.transaction(() async { + for (final companion in companions) { + await _db.into(_db.trashedLocalAssetEntity).insertOnConflictUpdate(companion); + } + + for (final slice in idToDelete.slices(kDriftMaxChunk)) { + await (_db.delete(_db.localAssetEntity)..where((t) => t.id.isIn(slice))).go(); + } + }); + } + + Future applyRestoredAssets(List idList) async { + if (idList.isEmpty) { + return; + } + + final trashedAssets = []; + + for (final slice in idList.slices(kDriftMaxChunk)) { + final q = _db.select(_db.trashedLocalAssetEntity)..where((t) => t.id.isIn(slice)); + trashedAssets.addAll(await q.get()); + } + + if (trashedAssets.isEmpty) { + return; + } + + final companions = trashedAssets.map((e) { + return LocalAssetEntityCompanion.insert( + id: e.id, + name: e.name, + type: e.type, + createdAt: Value(e.createdAt), + updatedAt: Value(e.updatedAt), + width: Value(e.width), + height: Value(e.height), + durationInSeconds: Value(e.durationInSeconds), + checksum: Value(e.checksum), + isFavorite: Value(e.isFavorite), + orientation: Value(e.orientation), + ); + }); + + await _db.transaction(() async { + for (final companion in companions) { + await _db.into(_db.localAssetEntity).insertOnConflictUpdate(companion); + } + for (final slice in idList.slices(kDriftMaxChunk)) { + await (_db.delete(_db.trashedLocalAssetEntity)..where((t) => t.id.isIn(slice))).go(); + } + }); + } + + Future>> getToTrash() async { + final result = >{}; + + final rows = + await (_db.select(_db.localAlbumAssetEntity).join([ + innerJoin(_db.localAlbumEntity, _db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id)), + innerJoin(_db.localAssetEntity, _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id)), + leftOuterJoin( + _db.remoteAssetEntity, + _db.remoteAssetEntity.checksum.equalsExp(_db.localAssetEntity.checksum), + ), + ])..where( + _db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.selected) & + _db.remoteAssetEntity.deletedAt.isNotNull(), + )) + .get(); + + for (final row in rows) { + final albumId = row.readTable(_db.localAlbumAssetEntity).albumId; + final asset = row.readTable(_db.localAssetEntity).toDto(); + (result[albumId] ??= []).add(asset); + } + + return result; + } + + //attempt to reuse existing checksums + Future> _getCachedChecksums(Set assetIds) async { + final localChecksumById = {}; + + for (final slice in assetIds.slices(kDriftMaxChunk)) { + final rows = + await (_db.selectOnly(_db.localAssetEntity) + ..where(_db.localAssetEntity.id.isIn(slice) & _db.localAssetEntity.checksum.isNotNull()) + ..addColumns([_db.localAssetEntity.id, _db.localAssetEntity.checksum])) + .get(); + + for (final r in rows) { + localChecksumById[r.read(_db.localAssetEntity.id)!] = r.read(_db.localAssetEntity.checksum)!; + } + } + + return localChecksumById; + } +} diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 712ee0bd83..c3804d97f6 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:io'; +import 'dart:math'; import 'package:auto_route/auto_route.dart'; import 'package:background_downloader/background_downloader.dart'; @@ -15,7 +16,9 @@ import 'package:immich_mobile/constants/locales.dart'; import 'package:immich_mobile/domain/services/background_worker.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/generated/codegen_loader.g.dart'; +import 'package:immich_mobile/generated/intl_keys.g.dart'; import 'package:immich_mobile/platform/background_worker_lock_api.g.dart'; import 'package:immich_mobile/providers/app_life_cycle.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart'; @@ -38,10 +41,10 @@ import 'package:immich_mobile/utils/debug_print.dart'; import 'package:immich_mobile/utils/http_ssl_options.dart'; import 'package:immich_mobile/utils/licenses.dart'; import 'package:immich_mobile/utils/migration.dart'; +import 'package:immich_mobile/wm_executor.dart'; import 'package:intl/date_symbol_data_local.dart'; import 'package:logging/logging.dart'; import 'package:timezone/data/latest.dart'; -import 'package:worker_manager/worker_manager.dart'; void main() async { ImmichWidgetsBinding(); @@ -50,7 +53,7 @@ void main() async { await Bootstrap.initDomain(isar, drift, logDb); await initApp(); // Warm-up isolate pool for worker manager - await workerManager.init(dynamicSpawning: true); + await workerManagerPatch.init(dynamicSpawning: true, isolatesCount: max(Platform.numberOfProcessors - 1, 5)); await migrateDatabaseIfNeeded(isar, drift); HttpSSLOptions.apply(); @@ -156,7 +159,7 @@ class ImmichAppState extends ConsumerState with WidgetsBindingObserve WidgetsBinding.instance.addObserver(this); // Draw the app from edge to edge - SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + unawaited(SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge)); // Sets the navigation bar color SystemUiOverlayStyle overlayStyle = const SystemUiOverlayStyle(systemNavigationBarColor: Colors.transparent); @@ -210,6 +213,14 @@ class ImmichAppState extends ConsumerState with WidgetsBindingObserve if (Store.isBetaTimelineEnabled) { ref.read(backgroundServiceProvider).disableService(); ref.read(backgroundWorkerFgServiceProvider).enable(); + if (Platform.isAndroid) { + ref + .read(backgroundWorkerFgServiceProvider) + .saveNotificationMessage( + IntlKeys.uploading_media.t(), + IntlKeys.backup_background_service_default_notification.t(), + ); + } } else { ref.read(backgroundWorkerFgServiceProvider).disable(); ref.read(backgroundServiceProvider).resumeServiceIfEnabled(); diff --git a/mobile/lib/models/search/search_filter.model.dart b/mobile/lib/models/search/search_filter.model.dart index 7f27b4d333..93322f5031 100644 --- a/mobile/lib/models/search/search_filter.model.dart +++ b/mobile/lib/models/search/search_filter.model.dart @@ -176,7 +176,9 @@ class SearchFilter { String? context; String? filename; String? description; + String? ocr; String? language; + String? assetId; Set people; SearchLocationFilter location; SearchCameraFilter camera; @@ -190,7 +192,9 @@ class SearchFilter { this.context, this.filename, this.description, + this.ocr, this.language, + this.assetId, required this.people, required this.location, required this.camera, @@ -203,6 +207,8 @@ class SearchFilter { return (context == null || (context != null && context!.isEmpty)) && (filename == null || (filename!.isEmpty)) && (description == null || (description!.isEmpty)) && + (assetId == null || (assetId!.isEmpty)) && + (ocr == null || (ocr!.isEmpty)) && people.isEmpty && location.country == null && location.state == null && @@ -222,6 +228,8 @@ class SearchFilter { String? filename, String? description, String? language, + String? ocr, + String? assetId, Set? people, SearchLocationFilter? location, SearchCameraFilter? camera, @@ -234,6 +242,8 @@ class SearchFilter { filename: filename ?? this.filename, description: description ?? this.description, language: language ?? this.language, + ocr: ocr ?? this.ocr, + assetId: assetId ?? this.assetId, people: people ?? this.people, location: location ?? this.location, camera: camera ?? this.camera, @@ -245,7 +255,7 @@ class SearchFilter { @override String toString() { - return 'SearchFilter(context: $context, filename: $filename, description: $description, language: $language, people: $people, location: $location, camera: $camera, date: $date, display: $display, mediaType: $mediaType)'; + return 'SearchFilter(context: $context, filename: $filename, description: $description, language: $language, ocr: $ocr, people: $people, location: $location, camera: $camera, date: $date, display: $display, mediaType: $mediaType, assetId: $assetId)'; } @override @@ -256,6 +266,8 @@ class SearchFilter { other.filename == filename && other.description == description && other.language == language && + other.ocr == ocr && + other.assetId == assetId && other.people == people && other.location == location && other.camera == camera && @@ -270,6 +282,8 @@ class SearchFilter { filename.hashCode ^ description.hashCode ^ language.hashCode ^ + ocr.hashCode ^ + assetId.hashCode ^ people.hashCode ^ location.hashCode ^ camera.hashCode ^ diff --git a/mobile/lib/models/server_info/server_features.model.dart b/mobile/lib/models/server_info/server_features.model.dart index 20b9f29619..049628a8d2 100644 --- a/mobile/lib/models/server_info/server_features.model.dart +++ b/mobile/lib/models/server_info/server_features.model.dart @@ -5,33 +5,37 @@ class ServerFeatures { final bool map; final bool oauthEnabled; final bool passwordLogin; + final bool ocr; const ServerFeatures({ required this.trash, required this.map, required this.oauthEnabled, required this.passwordLogin, + this.ocr = false, }); - ServerFeatures copyWith({bool? trash, bool? map, bool? oauthEnabled, bool? passwordLogin}) { + ServerFeatures copyWith({bool? trash, bool? map, bool? oauthEnabled, bool? passwordLogin, bool? ocr}) { return ServerFeatures( trash: trash ?? this.trash, map: map ?? this.map, oauthEnabled: oauthEnabled ?? this.oauthEnabled, passwordLogin: passwordLogin ?? this.passwordLogin, + ocr: ocr ?? this.ocr, ); } @override String toString() { - return 'ServerFeatures(trash: $trash, map: $map, oauthEnabled: $oauthEnabled, passwordLogin: $passwordLogin)'; + return 'ServerFeatures(trash: $trash, map: $map, oauthEnabled: $oauthEnabled, passwordLogin: $passwordLogin, ocr: $ocr)'; } ServerFeatures.fromDto(ServerFeaturesDto dto) : trash = dto.trash, map = dto.map, oauthEnabled = dto.oauth, - passwordLogin = dto.passwordLogin; + passwordLogin = dto.passwordLogin, + ocr = dto.ocr; @override bool operator ==(covariant ServerFeatures other) { @@ -40,11 +44,12 @@ class ServerFeatures { return other.trash == trash && other.map == map && other.oauthEnabled == oauthEnabled && - other.passwordLogin == passwordLogin; + other.passwordLogin == passwordLogin && + other.ocr == ocr; } @override int get hashCode { - return trash.hashCode ^ map.hashCode ^ oauthEnabled.hashCode ^ passwordLogin.hashCode; + return trash.hashCode ^ map.hashCode ^ oauthEnabled.hashCode ^ passwordLogin.hashCode ^ ocr.hashCode; } } diff --git a/mobile/lib/models/server_info/server_info.model.dart b/mobile/lib/models/server_info/server_info.model.dart index 0fa80d45d8..a034960ddb 100644 --- a/mobile/lib/models/server_info/server_info.model.dart +++ b/mobile/lib/models/server_info/server_info.model.dart @@ -1,17 +1,30 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:immich_mobile/models/server_info/server_config.model.dart'; import 'package:immich_mobile/models/server_info/server_disk_info.model.dart'; import 'package:immich_mobile/models/server_info/server_features.model.dart'; import 'package:immich_mobile/models/server_info/server_version.model.dart'; +enum VersionStatus { + upToDate, + clientOutOfDate, + serverOutOfDate, + error; + + String get message => switch (this) { + VersionStatus.upToDate => "", + VersionStatus.clientOutOfDate => "app_update_available".tr(), + VersionStatus.serverOutOfDate => "server_update_available".tr(), + VersionStatus.error => "unable_to_check_version".tr(), + }; +} + class ServerInfo { final ServerVersion serverVersion; final ServerVersion latestVersion; final ServerFeatures serverFeatures; final ServerConfig serverConfig; final ServerDiskInfo serverDiskInfo; - final bool isVersionMismatch; - final bool isNewReleaseAvailable; - final String versionMismatchErrorMessage; + final VersionStatus versionStatus; const ServerInfo({ required this.serverVersion, @@ -19,9 +32,7 @@ class ServerInfo { required this.serverFeatures, required this.serverConfig, required this.serverDiskInfo, - required this.isVersionMismatch, - required this.isNewReleaseAvailable, - required this.versionMismatchErrorMessage, + required this.versionStatus, }); ServerInfo copyWith({ @@ -30,9 +41,7 @@ class ServerInfo { ServerFeatures? serverFeatures, ServerConfig? serverConfig, ServerDiskInfo? serverDiskInfo, - bool? isVersionMismatch, - bool? isNewReleaseAvailable, - String? versionMismatchErrorMessage, + VersionStatus? versionStatus, }) { return ServerInfo( serverVersion: serverVersion ?? this.serverVersion, @@ -40,15 +49,13 @@ class ServerInfo { serverFeatures: serverFeatures ?? this.serverFeatures, serverConfig: serverConfig ?? this.serverConfig, serverDiskInfo: serverDiskInfo ?? this.serverDiskInfo, - isVersionMismatch: isVersionMismatch ?? this.isVersionMismatch, - isNewReleaseAvailable: isNewReleaseAvailable ?? this.isNewReleaseAvailable, - versionMismatchErrorMessage: versionMismatchErrorMessage ?? this.versionMismatchErrorMessage, + versionStatus: versionStatus ?? this.versionStatus, ); } @override String toString() { - return 'ServerInfo(serverVersion: $serverVersion, latestVersion: $latestVersion, serverFeatures: $serverFeatures, serverConfig: $serverConfig, serverDiskInfo: $serverDiskInfo, isVersionMismatch: $isVersionMismatch, isNewReleaseAvailable: $isNewReleaseAvailable, versionMismatchErrorMessage: $versionMismatchErrorMessage)'; + return 'ServerInfo(serverVersion: $serverVersion, latestVersion: $latestVersion, serverFeatures: $serverFeatures, serverConfig: $serverConfig, serverDiskInfo: $serverDiskInfo, versionStatus: $versionStatus)'; } @override @@ -61,9 +68,7 @@ class ServerInfo { other.serverFeatures == serverFeatures && other.serverConfig == serverConfig && other.serverDiskInfo == serverDiskInfo && - other.isVersionMismatch == isVersionMismatch && - other.isNewReleaseAvailable == isNewReleaseAvailable && - other.versionMismatchErrorMessage == versionMismatchErrorMessage; + other.versionStatus == versionStatus; } @override @@ -73,8 +78,6 @@ class ServerInfo { serverFeatures.hashCode ^ serverConfig.hashCode ^ serverDiskInfo.hashCode ^ - isVersionMismatch.hashCode ^ - isNewReleaseAvailable.hashCode ^ - versionMismatchErrorMessage.hashCode; + versionStatus.hashCode; } } diff --git a/mobile/lib/models/server_info/server_version.model.dart b/mobile/lib/models/server_info/server_version.model.dart index 2cb41b0415..3aea98a80d 100644 --- a/mobile/lib/models/server_info/server_version.model.dart +++ b/mobile/lib/models/server_info/server_version.model.dart @@ -1,32 +1,13 @@ +import 'package:immich_mobile/utils/semver.dart'; import 'package:openapi/api.dart'; -class ServerVersion { - final int major; - final int minor; - final int patch; - - const ServerVersion({required this.major, required this.minor, required this.patch}); - - ServerVersion copyWith({int? major, int? minor, int? patch}) { - return ServerVersion(major: major ?? this.major, minor: minor ?? this.minor, patch: patch ?? this.patch); - } +class ServerVersion extends SemVer { + const ServerVersion({required super.major, required super.minor, required super.patch}); @override String toString() { return 'ServerVersion(major: $major, minor: $minor, patch: $patch)'; } - ServerVersion.fromDto(ServerVersionResponseDto dto) : major = dto.major, minor = dto.minor, patch = dto.patch_; - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is ServerVersion && other.major == major && other.minor == minor && other.patch == patch; - } - - @override - int get hashCode { - return major.hashCode ^ minor.hashCode ^ patch.hashCode; - } + ServerVersion.fromDto(ServerVersionResponseDto dto) : super(major: dto.major, minor: dto.minor, patch: dto.patch_); } diff --git a/mobile/lib/pages/album/album_options.page.dart b/mobile/lib/pages/album/album_options.page.dart index 20d4dbd325..b0f682ffed 100644 --- a/mobile/lib/pages/album/album_options.page.dart +++ b/mobile/lib/pages/album/album_options.page.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -51,7 +53,7 @@ class AlbumOptionsPage extends HookConsumerWidget { final isSuccess = await ref.read(albumProvider.notifier).leaveAlbum(album); if (isSuccess) { - context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()])); + unawaited(context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]))); } else { showErrorMessage(); } diff --git a/mobile/lib/pages/album/album_shared_user_selection.page.dart b/mobile/lib/pages/album/album_shared_user_selection.page.dart index 562f02a2ab..ec084b1859 100644 --- a/mobile/lib/pages/album/album_shared_user_selection.page.dart +++ b/mobile/lib/pages/album/album_shared_user_selection.page.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -29,8 +31,8 @@ class AlbumSharedUserSelectionPage extends HookConsumerWidget { if (newAlbum != null) { ref.watch(albumTitleProvider.notifier).clearAlbumTitle(); - context.maybePop(true); - context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()])); + unawaited(context.maybePop(true)); + unawaited(context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]))); } ScaffoldMessenger( @@ -109,8 +111,8 @@ class AlbumSharedUserSelectionPage extends HookConsumerWidget { centerTitle: false, leading: IconButton( icon: const Icon(Icons.close_rounded), - onPressed: () async { - context.maybePop(); + onPressed: () { + unawaited(context.maybePop()); }, ), actions: [ diff --git a/mobile/lib/pages/backup/backup_controller.page.dart b/mobile/lib/pages/backup/backup_controller.page.dart index 4f55d00ea0..1e008be1bb 100644 --- a/mobile/lib/pages/backup/backup_controller.page.dart +++ b/mobile/lib/pages/backup/backup_controller.page.dart @@ -155,7 +155,7 @@ class BackupControllerPage extends HookConsumerWidget { // waited until returning from selection await ref.read(backupProvider.notifier).backupAlbumSelectionDone(); // waited until backup albums are stored in DB - ref.read(albumProvider.notifier).refreshDeviceAlbums(); + await ref.read(albumProvider.notifier).refreshDeviceAlbums(); }, child: const Text("select", style: TextStyle(fontWeight: FontWeight.bold)).tr(), ), diff --git a/mobile/lib/pages/backup/drift_backup.page.dart b/mobile/lib/pages/backup/drift_backup.page.dart index 30782726e2..47052ea436 100644 --- a/mobile/lib/pages/backup/drift_backup.page.dart +++ b/mobile/lib/pages/backup/drift_backup.page.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -8,6 +10,7 @@ import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/generated/intl_keys.g.dart'; import 'package:immich_mobile/presentation/widgets/backup/backup_toggle_button.widget.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/backup/backup_album.provider.dart'; @@ -16,8 +19,7 @@ import 'package:immich_mobile/providers/sync_status.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/widgets/backup/backup_info_card.dart'; -import 'dart:async'; - +import 'package:logging/logging.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; @RoutePage() @@ -29,6 +31,8 @@ class DriftBackupPage extends ConsumerStatefulWidget { } class _DriftBackupPageState extends ConsumerState { + bool? syncSuccess; + @override void initState() { super.initState(); @@ -42,7 +46,10 @@ class _DriftBackupPageState extends ConsumerState { WidgetsBinding.instance.addPostFrameCallback((_) async { await ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id); - await ref.read(backgroundSyncProvider).syncRemote(); + + ref.read(driftBackupProvider.notifier).updateSyncing(true); + syncSuccess = await ref.read(backgroundSyncProvider).syncRemote(); + ref.read(driftBackupProvider.notifier).updateSyncing(false); if (mounted) { await ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id); @@ -63,7 +70,10 @@ class _DriftBackupPageState extends ConsumerState { .where((album) => album.backupSelection == BackupSelection.selected) .toList(); + final error = ref.watch(driftBackupProvider.select((p) => p.error)); + final backupNotifier = ref.read(driftBackupProvider.notifier); + final backupSyncManager = ref.read(backgroundSyncProvider); Future startBackup() async { final currentUser = Store.tryGet(StoreKey.currentUser); @@ -71,7 +81,18 @@ class _DriftBackupPageState extends ConsumerState { return; } + if (syncSuccess == null) { + ref.read(driftBackupProvider.notifier).updateSyncing(true); + syncSuccess = await backupSyncManager.syncRemote(); + ref.read(driftBackupProvider.notifier).updateSyncing(false); + } + await backupNotifier.getBackupStatus(currentUser.id); + + if (syncSuccess == false) { + Logger("DriftBackupPage").warning("Remote sync did not complete successfully, skipping backup"); + return; + } await backupNotifier.startBackup(currentUser.id); } @@ -113,7 +134,33 @@ class _DriftBackupPageState extends ConsumerState { const _BackupCard(), const _RemainderCard(), const Divider(), - BackupToggleButton(onStart: () async => await startBackup(), onStop: () async => await stopBackup()), + BackupToggleButton( + onStart: () async => await startBackup(), + onStop: () async { + syncSuccess = null; + await stopBackup(); + }, + ), + switch (error) { + BackupError.none => const SizedBox.shrink(), + BackupError.syncFailed => Padding( + padding: const EdgeInsets.only(top: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + Icon(Icons.warning_rounded, color: context.colorScheme.error, fill: 1), + const SizedBox(width: 8), + Text( + IntlKeys.backup_error_sync_failed.t(), + style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.error), + textAlign: TextAlign.center, + ), + ], + ), + ), + }, TextButton.icon( icon: const Icon(Icons.info_outline_rounded), onPressed: () => context.pushRoute(const DriftUploadDetailRoute()), @@ -223,7 +270,7 @@ class _BackupAlbumSelectionCard extends ConsumerWidget { if (currentUser == null) { return; } - ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id); + unawaited(ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id)); }, child: const Text("select", style: TextStyle(fontWeight: FontWeight.bold)).tr(), ), diff --git a/mobile/lib/pages/backup/drift_backup_album_selection.page.dart b/mobile/lib/pages/backup/drift_backup_album_selection.page.dart index 368341f24a..cae9f0a408 100644 --- a/mobile/lib/pages/backup/drift_backup_album_selection.page.dart +++ b/mobile/lib/pages/backup/drift_backup_album_selection.page.dart @@ -18,6 +18,7 @@ import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/widgets/backup/drift_album_info_list_tile.dart'; import 'package:immich_mobile/widgets/common/search_field.dart'; +import 'package:logging/logging.dart'; @RoutePage() class DriftBackupAlbumSelectionPage extends ConsumerStatefulWidget { @@ -112,7 +113,17 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState backgroundSync.hashAssets())); if (isBackupEnabled) { - unawaited(backupNotifier.cancel().whenComplete(() => backupNotifier.startBackup(user.id))); + unawaited( + backupNotifier.cancel().whenComplete( + () => backgroundSync.syncRemote().then((success) { + if (success) { + return backupNotifier.startBackup(user.id); + } else { + Logger('DriftBackupAlbumSelectionPage').warning('Background sync failed, not starting backup'); + } + }), + ), + ); } } diff --git a/mobile/lib/pages/backup/drift_backup_asset_detail.page.dart b/mobile/lib/pages/backup/drift_backup_asset_detail.page.dart index 3cc675c4ad..f3fdccc329 100644 --- a/mobile/lib/pages/backup/drift_backup_asset_detail.page.dart +++ b/mobile/lib/pages/backup/drift_backup_asset_detail.page.dart @@ -11,6 +11,7 @@ import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/pages/common/large_leading_tile.dart'; import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; +import 'package:immich_mobile/repositories/asset_media.repository.dart'; import 'package:immich_mobile/routing/router.dart'; @RoutePage() @@ -31,55 +32,66 @@ class DriftBackupAssetDetailPage extends ConsumerWidget { itemBuilder: (context, index) { final asset = candidates[index]; final albumsAsyncValue = ref.watch(driftCandidateBackupAlbumInfoProvider(asset.id)); - return LargeLeadingTile( - title: Text( - asset.name, - style: context.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.w500, fontSize: 16), - ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - asset.createdAt.toString(), - style: TextStyle(fontSize: 13.0, color: context.colorScheme.onSurfaceSecondary), + final assetMediaRepository = ref.watch(assetMediaRepositoryProvider); + return FutureBuilder( + future: assetMediaRepository.getOriginalFilename(asset.id), + builder: (context, snapshot) { + final displayName = snapshot.data ?? asset.name; + return LargeLeadingTile( + title: Text( + displayName, + style: context.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.w500, fontSize: 16), ), - Text( - asset.checksum ?? "N/A", - style: TextStyle(fontSize: 13.0, color: context.colorScheme.onSurfaceSecondary), - overflow: TextOverflow.ellipsis, - ), - albumsAsyncValue.when( - data: (albums) { - if (albums.isEmpty) { - return const SizedBox.shrink(); - } - return Text( - albums.map((a) => a.name).join(', '), - style: context.textTheme.labelLarge?.copyWith(color: context.primaryColor), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + asset.createdAt.toString(), + style: TextStyle(fontSize: 13.0, color: context.colorScheme.onSurfaceSecondary), + ), + Text( + asset.checksum ?? "N/A", + style: TextStyle(fontSize: 13.0, color: context.colorScheme.onSurfaceSecondary), overflow: TextOverflow.ellipsis, - ); - }, - error: (error, stackTrace) => Text( - 'error_saving_image'.tr(args: [error.toString()]), - style: TextStyle(color: context.colorScheme.error), - ), - loading: () => const SizedBox(height: 16, width: 16, child: CircularProgressIndicator.adaptive()), + ), + albumsAsyncValue.when( + data: (albums) { + if (albums.isEmpty) { + return const SizedBox.shrink(); + } + return Text( + albums.map((a) => a.name).join(', '), + style: context.textTheme.labelLarge?.copyWith(color: context.primaryColor), + overflow: TextOverflow.ellipsis, + ); + }, + error: (error, stackTrace) => Text( + 'error_saving_image'.tr(args: [error.toString()]), + style: TextStyle(color: context.colorScheme.error), + ), + loading: () => + const SizedBox(height: 16, width: 16, child: CircularProgressIndicator.adaptive()), + ), + ], ), - ], - ), - leading: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(12)), - child: SizedBox( - width: 64, - height: 64, - child: Thumbnail.fromAsset(asset: asset, size: const Size(64, 64), fit: BoxFit.cover), - ), - ), - trailing: const Padding(padding: EdgeInsets.only(right: 24, left: 8), child: Icon(Icons.image_search)), - onTap: () async { - await context.maybePop(); - await context.navigateTo(const TabShellRoute(children: [MainTimelineRoute()])); - EventStream.shared.emit(ScrollToDateEvent(asset.createdAt)); + leading: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(12)), + child: SizedBox( + width: 64, + height: 64, + child: Thumbnail.fromAsset(asset: asset, size: const Size(64, 64), fit: BoxFit.cover), + ), + ), + trailing: const Padding( + padding: EdgeInsets.only(right: 24, left: 8), + child: Icon(Icons.image_search), + ), + onTap: () async { + await context.maybePop(); + await context.navigateTo(const TabShellRoute(children: [MainTimelineRoute()])); + EventStream.shared.emit(ScrollToDateEvent(asset.createdAt)); + }, + ); }, ); }, diff --git a/mobile/lib/pages/backup/drift_backup_options.page.dart b/mobile/lib/pages/backup/drift_backup_options.page.dart index 92f911ae1e..1e5c326478 100644 --- a/mobile/lib/pages/backup/drift_backup_options.page.dart +++ b/mobile/lib/pages/backup/drift_backup_options.page.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -5,10 +7,12 @@ import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/widgets/settings/backup_settings/drift_backup_settings.dart'; +import 'package:logging/logging.dart'; @RoutePage() class DriftBackupOptionsPage extends ConsumerWidget { @@ -54,9 +58,18 @@ class DriftBackupOptionsPage extends ConsumerWidget { ); final backupNotifier = ref.read(driftBackupProvider.notifier); - backupNotifier.cancel().then((_) { - backupNotifier.startBackup(currentUser.id); - }); + final backgroundSync = ref.read(backgroundSyncProvider); + unawaited( + backupNotifier.cancel().whenComplete( + () => backgroundSync.syncRemote().then((success) { + if (success) { + return backupNotifier.startBackup(currentUser.id); + } else { + Logger('DriftBackupOptionsPage').warning('Background sync failed, not starting backup'); + } + }), + ), + ); } }, child: Scaffold( diff --git a/mobile/lib/pages/backup/drift_upload_detail.page.dart b/mobile/lib/pages/backup/drift_upload_detail.page.dart index bececddc7f..1b8aa57eaa 100644 --- a/mobile/lib/pages/backup/drift_upload_detail.page.dart +++ b/mobile/lib/pages/backup/drift_upload_detail.page.dart @@ -1,12 +1,12 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; -import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; +import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/utils/bytes_units.dart'; import 'package:path/path.dart' as path; @@ -82,6 +82,7 @@ class DriftUploadDetailPage extends ConsumerWidget { Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, + spacing: 4, children: [ Text( path.basename(item.filename), @@ -89,7 +90,13 @@ class DriftUploadDetailPage extends ConsumerWidget { maxLines: 1, overflow: TextOverflow.ellipsis, ), - const SizedBox(height: 4), + if (item.error != null) + Text( + item.error!, + style: context.textTheme.bodySmall?.copyWith( + color: context.colorScheme.onErrorContainer.withValues(alpha: 0.6), + ), + ), Text( 'Tap for more details', style: context.textTheme.bodySmall?.copyWith( @@ -163,8 +170,8 @@ class DriftUploadDetailPage extends ConsumerWidget { ); } - Future _showFileDetailDialog(BuildContext context, DriftUploadStatus item) async { - showDialog( + Future _showFileDetailDialog(BuildContext context, DriftUploadStatus item) { + return showDialog( context: context, builder: (context) => FileDetailDialog(uploadStatus: item), ); diff --git a/mobile/lib/pages/common/activities.page.dart b/mobile/lib/pages/common/activities.page.dart index 1a1955af40..9d1123dbca 100644 --- a/mobile/lib/pages/common/activities.page.dart +++ b/mobile/lib/pages/common/activities.page.dart @@ -33,7 +33,7 @@ class ActivitiesPage extends HookConsumerWidget { Future onAddComment(String comment) async { await activityNotifier.addComment(comment); // Scroll to the end of the list to show the newly added activity - listViewScrollController.animateTo( + await listViewScrollController.animateTo( listViewScrollController.position.maxScrollExtent + 200, duration: const Duration(milliseconds: 600), curve: Curves.fastOutSlowIn, diff --git a/mobile/lib/pages/common/change_experience.page.dart b/mobile/lib/pages/common/change_experience.page.dart index 2e78b79232..2cc3dede1e 100644 --- a/mobile/lib/pages/common/change_experience.page.dart +++ b/mobile/lib/pages/common/change_experience.page.dart @@ -18,6 +18,7 @@ import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/background.service.dart'; import 'package:immich_mobile/utils/migration.dart'; import 'package:logging/logging.dart'; @@ -93,6 +94,10 @@ class _ChangeExperiencePageState extends ConsumerState { await ref.read(driftProvider).reset(); await Store.put(StoreKey.shouldResetSync, true); + final delay = Store.get(StoreKey.backupTriggerDelay, AppSettingsEnum.backupTriggerDelay.defaultValue); + if (delay >= 1000) { + await Store.put(StoreKey.backupTriggerDelay, (delay / 1000).toInt()); + } final permission = await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission(); if (permission.isGranted) { diff --git a/mobile/lib/pages/common/create_album.page.dart b/mobile/lib/pages/common/create_album.page.dart index 5a0d4154f8..0a28dfeb5a 100644 --- a/mobile/lib/pages/common/create_album.page.dart +++ b/mobile/lib/pages/common/create_album.page.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -170,11 +172,11 @@ class CreateAlbumPage extends HookConsumerWidget { .createAlbum(ref.read(albumTitleProvider), selectedAssets.value); if (newAlbum != null) { - ref.read(albumProvider.notifier).refreshRemoteAlbums(); + await ref.read(albumProvider.notifier).refreshRemoteAlbums(); selectedAssets.value = {}; ref.read(albumTitleProvider.notifier).clearAlbumTitle(); ref.read(albumViewerProvider.notifier).disableEditAlbum(); - context.replaceRoute(AlbumViewerRoute(albumId: newAlbum.id)); + unawaited(context.replaceRoute(AlbumViewerRoute(albumId: newAlbum.id))); } } diff --git a/mobile/lib/pages/common/gallery_viewer.page.dart b/mobile/lib/pages/common/gallery_viewer.page.dart index 3c279dfcd2..9a7e78ddb8 100644 --- a/mobile/lib/pages/common/gallery_viewer.page.dart +++ b/mobile/lib/pages/common/gallery_viewer.page.dart @@ -95,7 +95,7 @@ class GalleryViewerPage extends HookConsumerWidget { } catch (e) { // swallow error silently log.severe('Error precaching next image: $e'); - context.maybePop(); + await context.maybePop(); } } diff --git a/mobile/lib/pages/common/headers_settings.page.dart b/mobile/lib/pages/common/headers_settings.page.dart index 4cf683b4d9..1cfab355d6 100644 --- a/mobile/lib/pages/common/headers_settings.page.dart +++ b/mobile/lib/pages/common/headers_settings.page.dart @@ -7,6 +7,7 @@ import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/generated/intl_keys.g.dart'; class SettingsHeader { String key = ""; @@ -60,7 +61,7 @@ class HeaderSettingsPage extends HookConsumerWidget { return Scaffold( appBar: AppBar( - title: const Text('advanced_settings_proxy_headers_title').tr(), + title: const Text(IntlKeys.headers_settings_tile_title).tr(), centerTitle: false, actions: [ IconButton( diff --git a/mobile/lib/pages/common/native_video_viewer.page.dart b/mobile/lib/pages/common/native_video_viewer.page.dart index d8b6db2276..9cd9f6bd5e 100644 --- a/mobile/lib/pages/common/native_video_viewer.page.dart +++ b/mobile/lib/pages/common/native_video_viewer.page.dart @@ -26,6 +26,7 @@ import 'package:wakelock_plus/wakelock_plus.dart'; @RoutePage() class NativeVideoViewerPage extends HookConsumerWidget { + static final log = Logger('NativeVideoViewer'); final Asset asset; final bool showControls; final int playbackDelayFactor; @@ -59,8 +60,6 @@ class NativeVideoViewerPage extends HookConsumerWidget { // Used to show the placeholder during hero animations for remote videos to avoid a stutter final isVisible = useState(Platform.isIOS && asset.isLocal); - final log = Logger('NativeVideoViewerPage'); - final isCasting = ref.watch(castProvider.select((c) => c.isCasting)); final isVideoReady = useState(false); @@ -142,7 +141,7 @@ class NativeVideoViewerPage extends HookConsumerWidget { interval: const Duration(milliseconds: 100), maxWaitTime: const Duration(milliseconds: 200), ); - ref.listen(videoPlayerControlsProvider, (oldControls, newControls) async { + ref.listen(videoPlayerControlsProvider, (oldControls, newControls) { final playerController = controller.value; if (playerController == null) { return; @@ -153,28 +152,14 @@ class NativeVideoViewerPage extends HookConsumerWidget { return; } - final oldSeek = (oldControls?.position ?? 0) ~/ 1; - final newSeek = newControls.position ~/ 1; + final oldSeek = oldControls?.position.inMilliseconds; + final newSeek = newControls.position.inMilliseconds; if (oldSeek != newSeek || newControls.restarted) { seekDebouncer.run(() => playerController.seekTo(newSeek)); } if (oldControls?.pause != newControls.pause || newControls.restarted) { - // Make sure the last seek is complete before pausing or playing - // Otherwise, `onPlaybackPositionChanged` can receive outdated events - if (seekDebouncer.isActive) { - await seekDebouncer.drain(); - } - - try { - if (newControls.pause) { - await playerController.pause(); - } else { - await playerController.play(); - } - } catch (error) { - log.severe('Error pausing or playing video: $error'); - } + unawaited(_onPauseChange(context, playerController, seekDebouncer, newControls.pause)); } }); @@ -190,7 +175,10 @@ class NativeVideoViewerPage extends HookConsumerWidget { isVideoReady.value = true; try { - await videoController.play(); + final autoPlayVideo = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.autoPlayVideo); + if (autoPlayVideo) { + await videoController.play(); + } await videoController.setVolume(0.9); } catch (error) { log.severe('Error playing video: $error'); @@ -231,7 +219,7 @@ class NativeVideoViewerPage extends HookConsumerWidget { return; } - ref.read(videoPlaybackValueProvider.notifier).position = Duration(seconds: playbackInfo.position); + ref.read(videoPlaybackValueProvider.notifier).position = Duration(milliseconds: playbackInfo.position); // Check if the video is buffering if (playbackInfo.status == PlaybackStatus.playing) { @@ -279,11 +267,13 @@ class NativeVideoViewerPage extends HookConsumerWidget { nc.onPlaybackReady.addListener(onPlaybackReady); nc.onPlaybackEnded.addListener(onPlaybackEnded); - nc.loadVideoSource(source).catchError((error) { - log.severe('Error loading video source: $error'); - }); + unawaited( + nc.loadVideoSource(source).catchError((error) { + log.severe('Error loading video source: $error'); + }), + ); final loopVideo = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.loopVideo); - nc.setLoop(loopVideo); + unawaited(nc.setLoop(loopVideo)); controller.value = nc; Timer(const Duration(milliseconds: 200), checkIfBuffering); @@ -354,12 +344,12 @@ class NativeVideoViewerPage extends HookConsumerWidget { useOnAppLifecycleStateChange((_, state) async { if (state == AppLifecycleState.resumed && shouldPlayOnForeground.value) { - controller.value?.play(); + await controller.value?.play(); } else if (state == AppLifecycleState.paused) { final videoPlaying = await controller.value?.isPlaying(); if (videoPlaying ?? true) { shouldPlayOnForeground.value = true; - controller.value?.pause(); + await controller.value?.pause(); } else { shouldPlayOnForeground.value = false; } @@ -388,4 +378,35 @@ class NativeVideoViewerPage extends HookConsumerWidget { ], ); } + + Future _onPauseChange( + BuildContext context, + NativeVideoPlayerController controller, + Debouncer seekDebouncer, + bool isPaused, + ) async { + if (!context.mounted) { + return; + } + + // Make sure the last seek is complete before pausing or playing + // Otherwise, `onPlaybackPositionChanged` can receive outdated events + if (seekDebouncer.isActive) { + await seekDebouncer.drain(); + } + + if (!context.mounted) { + return; + } + + try { + if (isPaused) { + await controller.pause(); + } else { + await controller.play(); + } + } catch (error) { + log.severe('Error pausing or playing video: $error'); + } + } } diff --git a/mobile/lib/pages/common/settings.page.dart b/mobile/lib/pages/common/settings.page.dart index b23c420971..0fe2ccec09 100644 --- a/mobile/lib/pages/common/settings.page.dart +++ b/mobile/lib/pages/common/settings.page.dart @@ -141,14 +141,9 @@ class SettingsSubPage extends StatelessWidget { @override Widget build(BuildContext context) { context.locale; - return SafeArea( - bottom: true, - top: false, - right: true, - child: Scaffold( - appBar: AppBar(centerTitle: false, title: Text(section.title).tr()), - body: section.widget, - ), + return Scaffold( + appBar: AppBar(centerTitle: false, title: Text(section.title).tr()), + body: section.widget, ); } } diff --git a/mobile/lib/pages/common/splash_screen.page.dart b/mobile/lib/pages/common/splash_screen.page.dart index aa4d142381..79db33104d 100644 --- a/mobile/lib/pages/common/splash_screen.page.dart +++ b/mobile/lib/pages/common/splash_screen.page.dart @@ -55,39 +55,50 @@ class SplashScreenPageState extends ConsumerState { final backgroundManager = ref.read(backgroundSyncProvider); final backupProvider = ref.read(driftBackupProvider.notifier); - ref.read(authProvider.notifier).saveAuthInfo(accessToken: accessToken).then( - (_) async { - try { - wsProvider.connect(); - infoProvider.getServerInfo(); + unawaited( + ref.read(authProvider.notifier).saveAuthInfo(accessToken: accessToken).then( + (_) async { + try { + wsProvider.connect(); + unawaited(infoProvider.getServerInfo()); - if (Store.isBetaTimelineEnabled) { - await Future.wait([backgroundManager.syncLocal(), backgroundManager.syncRemote()]); - await Future.wait([ - backgroundManager.hashAssets().then((_) { - _resumeBackup(backupProvider); - }), - _resumeBackup(backupProvider), - ]); + if (Store.isBetaTimelineEnabled) { + bool syncSuccess = false; + await Future.wait([ + backgroundManager.syncLocal(full: true), + backgroundManager.syncRemote().then((success) => syncSuccess = success), + ]); - if (Store.get(StoreKey.syncAlbums, false)) { - await backgroundManager.syncLinkedAlbum(); + if (syncSuccess) { + await Future.wait([ + backgroundManager.hashAssets().then((_) { + _resumeBackup(backupProvider); + }), + _resumeBackup(backupProvider), + ]); + } else { + await backgroundManager.hashAssets(); + } + + if (Store.get(StoreKey.syncAlbums, false)) { + await backgroundManager.syncLinkedAlbum(); + } } + } catch (e) { + log.severe('Failed establishing connection to the server: $e'); } - } catch (e) { - log.severe('Failed establishing connection to the server: $e'); - } - }, - onError: (exception) => { - log.severe('Failed to update auth info with access token: $accessToken'), - ref.read(authProvider.notifier).logout(), - context.replaceRoute(const LoginRoute()), - }, + }, + onError: (exception) => { + log.severe('Failed to update auth info with access token: $accessToken'), + ref.read(authProvider.notifier).logout(), + context.replaceRoute(const LoginRoute()), + }, + ), ); } else { log.severe('Missing crucial offline login info - Logging out completely'); - ref.read(authProvider.notifier).logout(); - context.replaceRoute(const LoginRoute()); + unawaited(ref.read(authProvider.notifier).logout()); + unawaited(context.replaceRoute(const LoginRoute())); return; } @@ -97,11 +108,11 @@ class SplashScreenPageState extends ConsumerState { final needBetaMigration = Store.get(StoreKey.needBetaMigration, false); if (needBetaMigration) { await Store.put(StoreKey.needBetaMigration, false); - context.router.replaceAll([ChangeExperienceRoute(switchingToBeta: true)]); + unawaited(context.router.replaceAll([ChangeExperienceRoute(switchingToBeta: true)])); return; } - context.replaceRoute(Store.isBetaTimelineEnabled ? const TabShellRoute() : const TabControllerRoute()); + unawaited(context.replaceRoute(Store.isBetaTimelineEnabled ? const TabShellRoute() : const TabControllerRoute())); } if (Store.isBetaTimelineEnabled) { @@ -111,7 +122,7 @@ class SplashScreenPageState extends ConsumerState { final hasPermission = await ref.read(galleryPermissionNotifier.notifier).hasPermission; if (hasPermission) { // Resume backup (if enable) then navigate - ref.watch(backupProvider.notifier).resumeBackup(); + await ref.watch(backupProvider.notifier).resumeBackup(); } } @@ -121,7 +132,7 @@ class SplashScreenPageState extends ConsumerState { if (isEnableBackup) { final currentUser = Store.tryGet(StoreKey.currentUser); if (currentUser != null) { - notifier.handleBackupResume(currentUser.id); + unawaited(notifier.handleBackupResume(currentUser.id)); } } } diff --git a/mobile/lib/pages/common/tab_shell.page.dart b/mobile/lib/pages/common/tab_shell.page.dart index b60fe1ddc1..bbb567bd3b 100644 --- a/mobile/lib/pages/common/tab_shell.page.dart +++ b/mobile/lib/pages/common/tab_shell.page.dart @@ -4,9 +4,11 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/presentation/pages/search/paginated_search.provider.dart'; import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/memory.provider.dart'; @@ -77,7 +79,7 @@ class _TabShellPageState extends ConsumerState { } return AutoTabsRouter( - routes: [const MainTimelineRoute(), DriftSearchRoute(), const DriftAlbumsRoute(), const DriftLibraryRoute()], + routes: const [MainTimelineRoute(), DriftSearchRoute(), DriftAlbumsRoute(), DriftLibraryRoute()], duration: const Duration(milliseconds: 600), transitionBuilder: (context, child, animation) => FadeTransition(opacity: animation, child: child), builder: (context, child) { @@ -106,26 +108,30 @@ class _TabShellPageState extends ConsumerState { void _onNavigationSelected(TabsRouter router, int index, WidgetRef ref) { // On Photos page menu tapped - if (router.activeIndex == 0 && index == 0) { + if (router.activeIndex == kPhotoTabIndex && index == kPhotoTabIndex) { EventStream.shared.emit(const ScrollToTopEvent()); } - if (index == 0) { + if (index == kPhotoTabIndex) { ref.invalidate(driftMemoryFutureProvider); } + if (router.activeIndex != kSearchTabIndex && index == kSearchTabIndex) { + ref.read(searchPreFilterProvider.notifier).clear(); + } + // On Search page tapped - if (router.activeIndex == 1 && index == 1) { + if (router.activeIndex == kSearchTabIndex && index == kSearchTabIndex) { ref.read(searchInputFocusProvider).requestFocus(); } // Album page - if (index == 2) { + if (index == kAlbumTabIndex) { ref.read(remoteAlbumProvider.notifier).refresh(); } // Library page - if (index == 3) { + if (index == kLibraryTabIndex) { ref.invalidate(localAlbumProvider); ref.invalidate(driftGetAllPeopleProvider); } diff --git a/mobile/lib/pages/editing/crop.page.dart b/mobile/lib/pages/editing/crop.page.dart index 35fd615800..8cd13fed64 100644 --- a/mobile/lib/pages/editing/crop.page.dart +++ b/mobile/lib/pages/editing/crop.page.dart @@ -1,13 +1,16 @@ -import 'package:flutter/material.dart'; +import 'dart:async'; + +import 'package:auto_route/auto_route.dart'; import 'package:crop_image/crop_image.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/utils/hooks/crop_controller_hook.dart'; -import 'package:immich_mobile/entities/asset.entity.dart'; + import 'edit.page.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:auto_route/auto_route.dart'; /// A widget for cropping an image. /// This widget uses [HookWidget] to manage its lifecycle and state. It allows @@ -35,7 +38,7 @@ class CropImagePage extends HookWidget { icon: Icon(Icons.done_rounded, color: context.primaryColor, size: 24), onPressed: () async { final croppedImage = await cropController.croppedImage(); - context.pushRoute(EditImageRoute(asset: asset, image: croppedImage, isEdited: true)); + unawaited(context.pushRoute(EditImageRoute(asset: asset, image: croppedImage, isEdited: true))); }, ), ], diff --git a/mobile/lib/pages/editing/filter.page.dart b/mobile/lib/pages/editing/filter.page.dart index 6d41b4c5b8..f8b144bb96 100644 --- a/mobile/lib/pages/editing/filter.page.dart +++ b/mobile/lib/pages/editing/filter.page.dart @@ -1,12 +1,13 @@ import 'dart:async'; import 'dart:ui' as ui; + +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/constants/filters.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:auto_route/auto_route.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/routing/router.dart'; /// A widget for filtering an image. @@ -74,7 +75,7 @@ class FilterImagePage extends HookWidget { icon: Icon(Icons.done_rounded, color: context.primaryColor, size: 24), onPressed: () async { final filteredImage = await applyFilterAndConvert(colorFilter.value); - context.pushRoute(EditImageRoute(asset: asset, image: filteredImage, isEdited: true)); + unawaited(context.pushRoute(EditImageRoute(asset: asset, image: filteredImage, isEdited: true))); }, ), ], diff --git a/mobile/lib/pages/library/locked/pin_auth.page.dart b/mobile/lib/pages/library/locked/pin_auth.page.dart index 36befa0016..a39c91871b 100644 --- a/mobile/lib/pages/library/locked/pin_auth.page.dart +++ b/mobile/lib/pages/library/locked/pin_auth.page.dart @@ -1,14 +1,16 @@ +import 'dart:async'; + import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart' show useState; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/providers/local_auth.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/widgets/forms/pin_registration_form.dart'; import 'package:immich_mobile/widgets/forms/pin_verification_form.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; @RoutePage() class PinAuthPage extends HookConsumerWidget { @@ -35,9 +37,9 @@ class PinAuthPage extends HookConsumerWidget { ); if (isBetaTimeline) { - context.replaceRoute(const DriftLockedFolderRoute()); + unawaited(context.replaceRoute(const DriftLockedFolderRoute())); } else { - context.replaceRoute(const LockedRoute()); + unawaited(context.replaceRoute(const LockedRoute())); } } } diff --git a/mobile/lib/pages/library/shared_link/shared_link_edit.page.dart b/mobile/lib/pages/library/shared_link/shared_link_edit.page.dart index 8b66bb231f..1d7eaef080 100644 --- a/mobile/lib/pages/library/shared_link/shared_link_edit.page.dart +++ b/mobile/lib/pages/library/shared_link/shared_link_edit.page.dart @@ -333,7 +333,7 @@ class SharedLinkEditPage extends HookConsumerWidget { changeExpiry: changeExpiry, ); ref.invalidate(sharedLinksStateProvider); - context.maybePop(); + await context.maybePop(); } return Scaffold( diff --git a/mobile/lib/pages/photos/photos.page.dart b/mobile/lib/pages/photos/photos.page.dart index 957c32b224..7f57247ec4 100644 --- a/mobile/lib/pages/photos/photos.page.dart +++ b/mobile/lib/pages/photos/photos.page.dart @@ -82,10 +82,12 @@ class PhotosPage extends HookConsumerWidget { final fullRefresh = refreshCount.value > 0; if (fullRefresh) { - Future.wait([ - ref.read(assetProvider.notifier).getAllAsset(clear: true), - ref.read(albumProvider.notifier).refreshRemoteAlbums(), - ]); + unawaited( + Future.wait([ + ref.read(assetProvider.notifier).getAllAsset(clear: true), + ref.read(albumProvider.notifier).refreshRemoteAlbums(), + ]), + ); // refresh was forced: user requested another refresh within 2 seconds refreshCount.value = 0; diff --git a/mobile/lib/pages/search/map/map.page.dart b/mobile/lib/pages/search/map/map.page.dart index 34522f0f04..a93b826f03 100644 --- a/mobile/lib/pages/search/map/map.page.dart +++ b/mobile/lib/pages/search/map/map.page.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:math'; import 'package:auto_route/auto_route.dart'; @@ -83,7 +84,7 @@ class MapPage extends HookConsumerWidget { isLoading.value = true; markers.value = await ref.read(mapMarkersProvider.future); assetsDebouncer.run(updateAssetsInBounds); - reloadLayers(); + await reloadLayers(); } finally { isLoading.value = false; } @@ -128,7 +129,7 @@ class MapPage extends HookConsumerWidget { ); if (marker != null) { - updateAssetMarkerPosition(marker); + await updateAssetMarkerPosition(marker); } else { // If no asset was previously selected and no new asset is available, close the bottom sheet if (selectedMarker.value == null) { @@ -165,7 +166,7 @@ class MapPage extends HookConsumerWidget { if (asset.isVideo) { ref.read(showControlsProvider.notifier).show = false; } - context.pushRoute(GalleryViewerRoute(initialIndex: 0, heroOffset: 0, renderList: renderList)); + unawaited(context.pushRoute(GalleryViewerRoute(initialIndex: 0, heroOffset: 0, renderList: renderList))); } /// BOTTOM SHEET CALLBACKS @@ -209,7 +210,7 @@ class MapPage extends HookConsumerWidget { } if (mapController.value != null && location != null) { - mapController.value!.animateCamera( + await mapController.value!.animateCamera( CameraUpdate.newLatLngZoom(LatLng(location.latitude, location.longitude), mapZoomToAssetLevel), duration: const Duration(milliseconds: 800), ); diff --git a/mobile/lib/pages/search/map/map_location_picker.page.dart b/mobile/lib/pages/search/map/map_location_picker.page.dart index 0fe8b769f5..94e6627c98 100644 --- a/mobile/lib/pages/search/map/map_location_picker.page.dart +++ b/mobile/lib/pages/search/map/map_location_picker.page.dart @@ -8,9 +8,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/maplibrecontroller_extensions.dart'; +import 'package:immich_mobile/utils/map_utils.dart'; import 'package:immich_mobile/widgets/map/map_theme_override.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; -import 'package:immich_mobile/utils/map_utils.dart'; @RoutePage() class MapLocationPickerPage extends HookConsumerWidget { @@ -30,7 +30,7 @@ class MapLocationPickerPage extends HookConsumerWidget { Future onMapClick(Point point, LatLng centre) async { selectedLatLng.value = centre; - controller.value?.animateCamera(CameraUpdate.newLatLng(centre)); + await controller.value?.animateCamera(CameraUpdate.newLatLng(centre)); if (marker.value != null) { await controller.value?.updateSymbol(marker.value!, SymbolOptions(geometry: centre)); } @@ -49,7 +49,7 @@ class MapLocationPickerPage extends HookConsumerWidget { var currentLatLng = LatLng(currentLocation.latitude, currentLocation.longitude); selectedLatLng.value = currentLatLng; - controller.value?.animateCamera(CameraUpdate.newLatLng(currentLatLng)); + await controller.value?.animateCamera(CameraUpdate.newLatLng(currentLatLng)); } return MapThemeOverride( diff --git a/mobile/lib/pages/search/search.page.dart b/mobile/lib/pages/search/search.page.dart index acfa2fd59f..902110f6a8 100644 --- a/mobile/lib/pages/search/search.page.dart +++ b/mobile/lib/pages/search/search.page.dart @@ -266,7 +266,7 @@ class SearchPage extends HookConsumerWidget { filter.value = filter.value.copyWith(date: SearchDateFilter()); dateRangeCurrentFilterWidget.value = null; - search(); + unawaited(search()); return; } @@ -295,7 +295,7 @@ class SearchPage extends HookConsumerWidget { ); } - search(); + unawaited(search()); } // MEDIA PICKER @@ -389,15 +389,18 @@ class SearchPage extends HookConsumerWidget { handleTextSubmitted(String value) { switch (textSearchType.value) { case TextSearchType.context: - filter.value = filter.value.copyWith(filename: '', context: value, description: ''); + filter.value = filter.value.copyWith(filename: '', context: value, description: '', ocr: ''); break; case TextSearchType.filename: - filter.value = filter.value.copyWith(filename: value, context: '', description: ''); + filter.value = filter.value.copyWith(filename: value, context: '', description: '', ocr: ''); break; case TextSearchType.description: - filter.value = filter.value.copyWith(filename: '', context: '', description: value); + filter.value = filter.value.copyWith(filename: '', context: '', description: value, ocr: ''); + break; + case TextSearchType.ocr: + filter.value = filter.value.copyWith(filename: '', context: '', description: '', ocr: value); break; } @@ -408,6 +411,7 @@ class SearchPage extends HookConsumerWidget { TextSearchType.context => Icons.image_search_rounded, TextSearchType.filename => Icons.abc_rounded, TextSearchType.description => Icons.text_snippet_outlined, + TextSearchType.ocr => Icons.document_scanner_outlined, }; return Scaffold( @@ -493,6 +497,24 @@ class SearchPage extends HookConsumerWidget { searchHintText.value = 'search_by_description_example'.tr(); }, ), + MenuItemButton( + child: ListTile( + leading: const Icon(Icons.document_scanner_outlined), + title: Text( + 'search_filter_ocr'.tr(), + style: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w500, + color: textSearchType.value == TextSearchType.ocr ? context.colorScheme.primary : null, + ), + ), + selectedColor: context.colorScheme.primary, + selected: textSearchType.value == TextSearchType.ocr, + ), + onPressed: () { + textSearchType.value = TextSearchType.ocr; + searchHintText.value = 'search_by_ocr_example'.tr(); + }, + ), ], ), ), diff --git a/mobile/lib/platform/background_worker_api.g.dart b/mobile/lib/platform/background_worker_api.g.dart index af7c78fd4b..e8c87aa1a4 100644 --- a/mobile/lib/platform/background_worker_api.g.dart +++ b/mobile/lib/platform/background_worker_api.g.dart @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v26.0.0), do not edit directly. +// Autogenerated from Pigeon (v26.0.2), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers @@ -138,6 +138,29 @@ class BackgroundWorkerFgHostApi { } } + Future saveNotificationMessage(String title, String body) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.saveNotificationMessage$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([title, body]); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + Future configure(BackgroundWorkerSettings settings) async { final String pigeonVar_channelName = 'dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.configure$pigeonVar_messageChannelSuffix'; @@ -221,29 +244,6 @@ class BackgroundWorkerBgHostApi { } } - Future showNotification(String title, String content) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.showNotification$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send([title, content]); - final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return; - } - } - Future close() async { final String pigeonVar_channelName = 'dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.close$pigeonVar_messageChannelSuffix'; diff --git a/mobile/lib/platform/background_worker_lock_api.g.dart b/mobile/lib/platform/background_worker_lock_api.g.dart index 9f00017dc8..93852d2564 100644 --- a/mobile/lib/platform/background_worker_lock_api.g.dart +++ b/mobile/lib/platform/background_worker_lock_api.g.dart @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v26.0.0), do not edit directly. +// Autogenerated from Pigeon (v26.0.2), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers diff --git a/mobile/lib/platform/connectivity_api.g.dart b/mobile/lib/platform/connectivity_api.g.dart index c348356f81..0422d87438 100644 --- a/mobile/lib/platform/connectivity_api.g.dart +++ b/mobile/lib/platform/connectivity_api.g.dart @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v26.0.0), do not edit directly. +// Autogenerated from Pigeon (v26.0.2), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers diff --git a/mobile/lib/platform/native_sync_api.g.dart b/mobile/lib/platform/native_sync_api.g.dart index 01237f8c19..34ed7a5e2b 100644 --- a/mobile/lib/platform/native_sync_api.g.dart +++ b/mobile/lib/platform/native_sync_api.g.dart @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v26.0.0), do not edit directly. +// Autogenerated from Pigeon (v26.0.2), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers @@ -562,4 +562,32 @@ class NativeSyncApi { return; } } + + Future>> getTrashedAssets() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as Map?)!.cast>(); + } + } } diff --git a/mobile/lib/platform/thumbnail_api.g.dart b/mobile/lib/platform/thumbnail_api.g.dart index 2b4add7482..53d7b10fc3 100644 --- a/mobile/lib/platform/thumbnail_api.g.dart +++ b/mobile/lib/platform/thumbnail_api.g.dart @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v26.0.0), do not edit directly. +// Autogenerated from Pigeon (v26.0.2), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers diff --git a/mobile/lib/presentation/pages/drift_activities.page.dart b/mobile/lib/presentation/pages/drift_activities.page.dart index 8e67d85884..b92d429aa1 100644 --- a/mobile/lib/presentation/pages/drift_activities.page.dart +++ b/mobile/lib/presentation/pages/drift_activities.page.dart @@ -1,41 +1,30 @@ import 'package:auto_route/auto_route.dart'; -import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/models/activities/activity.model.dart'; +import 'package:immich_mobile/widgets/activities/comment_bubble.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/like_activity_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/album/drift_activity_text_field.dart'; import 'package:immich_mobile/providers/activity.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart'; -import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/widgets/activities/activity_tile.dart'; -import 'package:immich_mobile/widgets/activities/dismissible_activity.dart'; @RoutePage() class DriftActivitiesPage extends HookConsumerWidget { - const DriftActivitiesPage({super.key}); + final RemoteAlbum album; + + const DriftActivitiesPage({super.key, required this.album}); @override Widget build(BuildContext context, WidgetRef ref) { - final album = ref.watch(currentRemoteAlbumProvider)!; - final asset = ref.watch(currentAssetNotifier) as RemoteAsset?; - final user = ref.watch(currentUserProvider); - - final activityNotifier = ref.read(albumActivityProvider(album.id, asset?.id).notifier); - final activities = ref.watch(albumActivityProvider(album.id, asset?.id)); + final activityNotifier = ref.read(albumActivityProvider(album.id).notifier); + final activities = ref.watch(albumActivityProvider(album.id)); final listViewScrollController = useScrollController(); void scrollToBottom() { - listViewScrollController.animateTo( - listViewScrollController.position.maxScrollExtent + 80, - duration: const Duration(milliseconds: 600), - curve: Curves.fastOutSlowIn, - ); + listViewScrollController.animateTo(0, duration: const Duration(milliseconds: 300), curve: Curves.fastOutSlowIn); } Future onAddComment(String comment) async { @@ -43,62 +32,52 @@ class DriftActivitiesPage extends HookConsumerWidget { scrollToBottom(); } - return Scaffold( - appBar: AppBar( - title: asset == null ? Text(album.name) : null, - actions: [const LikeActivityActionButton(menuItem: true)], - actionsPadding: const EdgeInsets.only(right: 8), - ), - body: activities.widgetWhen( - onData: (data) { - final liked = data.firstWhereOrNull( - (a) => a.type == ActivityType.like && a.user.id == user?.id && a.assetId == asset?.id, - ); - - return SafeArea( - child: Stack( - children: [ - ListView.builder( - controller: listViewScrollController, - itemCount: data.length + 1, - itemBuilder: (context, index) { - if (index == data.length) { - return const SizedBox(height: 80); - } - final activity = data[index]; - final canDelete = activity.user.id == user?.id || album.ownerId == user?.id; - return Padding( - padding: const EdgeInsets.all(5), - child: DismissibleActivity( - activity.id, - ActivityTile(activity), - onDismiss: canDelete - ? (activityId) async => await activityNotifier.removeActivity(activity.id) - : null, - ), - ); - }, + return ProviderScope( + overrides: [currentRemoteAlbumScopedProvider.overrideWithValue(album)], + child: Scaffold( + appBar: AppBar( + title: Text(album.name), + actions: [const LikeActivityActionButton(menuItem: true)], + actionsPadding: const EdgeInsets.only(right: 8), + ), + body: activities.widgetWhen( + onData: (data) { + final List activityWidgets = []; + for (final activity in data.reversed) { + activityWidgets.add( + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), + child: CommentBubble(activity: activity), ), - Align( - alignment: Alignment.bottomCenter, - child: Container( - decoration: BoxDecoration( - color: context.scaffoldBackgroundColor, - border: Border(top: BorderSide(color: context.colorScheme.secondaryContainer, width: 1)), - ), - child: DriftActivityTextField( - isEnabled: album.isActivityEnabled, - likeId: liked?.id, - onSubmit: onAddComment, + ); + } + + return SafeArea( + child: Stack( + children: [ + ListView( + controller: listViewScrollController, + padding: const EdgeInsets.only(top: 8, bottom: 80), + reverse: true, + children: activityWidgets, + ), + Align( + alignment: Alignment.bottomCenter, + child: Container( + decoration: BoxDecoration( + color: context.scaffoldBackgroundColor, + border: Border(top: BorderSide(color: context.colorScheme.secondaryContainer, width: 1)), + ), + child: DriftActivityTextField(isEnabled: album.isActivityEnabled, onSubmit: onAddComment), ), ), - ), - ], - ), - ); - }, + ], + ), + ); + }, + ), + resizeToAvoidBottomInset: true, ), - resizeToAvoidBottomInset: true, ); } } diff --git a/mobile/lib/presentation/pages/drift_album.page.dart b/mobile/lib/presentation/pages/drift_album.page.dart index 0835c741ad..a159c6c54a 100644 --- a/mobile/lib/presentation/pages/drift_album.page.dart +++ b/mobile/lib/presentation/pages/drift_album.page.dart @@ -5,7 +5,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/widgets/common/immich_sliver_app_bar.dart'; @@ -43,7 +42,6 @@ class _DriftAlbumsPageState extends ConsumerState { ), AlbumSelector( onAlbumSelected: (album) { - ref.read(currentRemoteAlbumProvider.notifier).setAlbum(album); context.router.push(RemoteAlbumRoute(album: album)); }, ), diff --git a/mobile/lib/presentation/pages/drift_album_options.page.dart b/mobile/lib/presentation/pages/drift_album_options.page.dart index 7f49a1ff79..9db6e98613 100644 --- a/mobile/lib/presentation/pages/drift_album_options.page.dart +++ b/mobile/lib/presentation/pages/drift_album_options.page.dart @@ -1,9 +1,12 @@ +import 'dart:async'; + import 'package:auto_route/auto_route.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; @@ -20,15 +23,11 @@ import 'package:immich_mobile/widgets/common/user_circle_avatar.dart'; @RoutePage() class DriftAlbumOptionsPage extends HookConsumerWidget { - const DriftAlbumOptionsPage({super.key}); + final RemoteAlbum album; + const DriftAlbumOptionsPage({super.key, required this.album}); @override Widget build(BuildContext context, WidgetRef ref) { - final album = ref.watch(currentRemoteAlbumProvider); - if (album == null) { - return const SizedBox(); - } - final sharedUsersAsync = ref.watch(remoteAlbumSharedUsersProvider(album.id)); final userId = ref.watch(authProvider).userId; final activityEnabled = useState(album.isActivityEnabled); @@ -47,7 +46,7 @@ class DriftAlbumOptionsPage extends HookConsumerWidget { void leaveAlbum() async { try { await ref.read(remoteAlbumProvider.notifier).leaveAlbum(album.id, userId: userId); - context.navigateTo(const DriftAlbumsRoute()); + unawaited(context.navigateTo(const DriftAlbumsRoute())); } catch (_) { showErrorMessage(); } @@ -189,48 +188,51 @@ class DriftAlbumOptionsPage extends HookConsumerWidget { ); } - return Scaffold( - appBar: AppBar( - leading: IconButton( - icon: const Icon(Icons.arrow_back_ios_new_rounded), - onPressed: () => context.maybePop(null), + return ProviderScope( + overrides: [currentRemoteAlbumScopedProvider.overrideWithValue(album)], + child: Scaffold( + appBar: AppBar( + leading: IconButton( + icon: const Icon(Icons.arrow_back_ios_new_rounded), + onPressed: () => context.maybePop(null), + ), + centerTitle: true, + title: Text("options".t(context: context)), ), - centerTitle: true, - title: Text("options".t(context: context)), - ), - body: ListView( - children: [ - const SizedBox(height: 8), - if (isOwner) - SwitchListTile.adaptive( - value: activityEnabled.value, - onChanged: (bool value) async { - activityEnabled.value = value; - await ref.read(remoteAlbumProvider.notifier).setActivityStatus(album.id, value); - }, - activeThumbColor: activityEnabled.value ? context.primaryColor : context.themeData.disabledColor, - dense: true, - title: Text( - "comments_and_likes", - style: context.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w500), - ).t(context: context), - subtitle: Text( - "let_others_respond", - style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceSecondary), - ).t(context: context), - ), - buildSectionTitle("shared_album_section_people_title".t(context: context)), - if (isOwner) ...[ - ListTile( - leading: const Icon(Icons.person_add_rounded), - title: Text("invite_people".t(context: context)), - onTap: () async => addUsers(), - ), - const Divider(indent: 16), + body: ListView( + children: [ + const SizedBox(height: 8), + if (isOwner) + SwitchListTile.adaptive( + value: activityEnabled.value, + onChanged: (bool value) async { + activityEnabled.value = value; + await ref.read(remoteAlbumProvider.notifier).setActivityStatus(album.id, value); + }, + activeThumbColor: activityEnabled.value ? context.primaryColor : context.themeData.disabledColor, + dense: true, + title: Text( + "comments_and_likes", + style: context.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w500), + ).t(context: context), + subtitle: Text( + "let_others_respond", + style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceSecondary), + ).t(context: context), + ), + buildSectionTitle("shared_album_section_people_title".t(context: context)), + if (isOwner) ...[ + ListTile( + leading: const Icon(Icons.person_add_rounded), + title: Text("invite_people".t(context: context)), + onTap: () async => addUsers(), + ), + const Divider(indent: 16), + ], + buildOwnerInfo(), + buildSharedUsersList(), ], - buildOwnerInfo(), - buildSharedUsersList(), - ], + ), ), ); } diff --git a/mobile/lib/presentation/pages/drift_create_album.page.dart b/mobile/lib/presentation/pages/drift_create_album.page.dart index c70c4a0bd7..57e5cb09a9 100644 --- a/mobile/lib/presentation/pages/drift_create_album.page.dart +++ b/mobile/lib/presentation/pages/drift_create_album.page.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -6,7 +8,6 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/widgets/album/album_action_filled_button.dart'; @@ -178,8 +179,7 @@ class _DriftCreateAlbumPageState extends ConsumerState { ); if (album != null) { - ref.read(currentRemoteAlbumProvider.notifier).setAlbum(album); - context.replaceRoute(RemoteAlbumRoute(album: album)); + unawaited(context.replaceRoute(RemoteAlbumRoute(album: album))); } } diff --git a/mobile/lib/presentation/pages/drift_map.page.dart b/mobile/lib/presentation/pages/drift_map.page.dart index 30da6410b5..de8dde7714 100644 --- a/mobile/lib/presentation/pages/drift_map.page.dart +++ b/mobile/lib/presentation/pages/drift_map.page.dart @@ -25,9 +25,10 @@ class DriftMapPage extends StatelessWidget { onPressed: () => context.pop(), icon: const Icon(Icons.arrow_back_ios_new_rounded), style: IconButton.styleFrom( - shape: const CircleBorder(side: BorderSide(width: 1, color: Colors.black26)), padding: const EdgeInsets.all(8), - backgroundColor: Colors.indigo.withValues(alpha: 0.7), + backgroundColor: Colors.indigo, + shadowColor: Colors.black26, + elevation: 4, ), ), ), diff --git a/mobile/lib/presentation/pages/drift_remote_album.page.dart b/mobile/lib/presentation/pages/drift_remote_album.page.dart index e0fe5ee62b..9a52f28deb 100644 --- a/mobile/lib/presentation/pages/drift_remote_album.page.dart +++ b/mobile/lib/presentation/pages/drift_remote_album.page.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -139,7 +141,7 @@ class _RemoteAlbumPageState extends ConsumerState { toastType: ToastType.success, ); - context.pushRoute(const DriftAlbumsRoute()); + unawaited(context.pushRoute(const DriftAlbumsRoute())); } catch (e) { ImmichToast.show( context: context, @@ -161,94 +163,98 @@ class _RemoteAlbumPageState extends ConsumerState { setState(() { _album = _album.copyWith(name: result.name, description: result.description ?? ''); }); - HapticFeedback.mediumImpact(); + unawaited(HapticFeedback.mediumImpact()); } } Future showActivity(BuildContext context) async { - context.pushRoute(const DriftActivitiesRoute()); + unawaited(context.pushRoute(DriftActivitiesRoute(album: _album))); } - void showOptionSheet(BuildContext context) { + Future showOptionSheet(BuildContext context) async { final user = ref.watch(currentUserProvider); final isOwner = user != null ? user.id == _album.ownerId : false; + final canAddPhotos = + await ref.read(remoteAlbumServiceProvider).getUserRole(_album.id, user?.id ?? '') == AlbumUserRole.editor; - showModalBottomSheet( - context: context, - backgroundColor: context.colorScheme.surface, - isScrollControlled: false, - builder: (context) { - return DriftRemoteAlbumOption( - onDeleteAlbum: isOwner - ? () async { - await deleteAlbum(context); - if (context.mounted) { + unawaited( + showModalBottomSheet( + context: context, + backgroundColor: context.colorScheme.surface, + isScrollControlled: false, + builder: (context) { + return DriftRemoteAlbumOption( + onDeleteAlbum: isOwner + ? () async { + await deleteAlbum(context); + if (context.mounted) { + context.pop(); + } + } + : null, + onAddUsers: isOwner + ? () async { + await addUsers(context); context.pop(); } - } - : null, - onAddUsers: isOwner - ? () async { - await addUsers(context); - context.pop(); - } - : null, - onAddPhotos: () async { - await addAssets(context); - context.pop(); - }, - onToggleAlbumOrder: () async { - await toggleAlbumOrder(); - context.pop(); - }, - onEditAlbum: () async { - context.pop(); - await showEditTitleAndDescription(context); - }, - onCreateSharedLink: () async { - context.pop(); - context.pushRoute(SharedLinkEditRoute(albumId: _album.id)); - }, - onShowOptions: () { - context.pop(); - context.pushRoute(const DriftAlbumOptionsRoute()); - }, - ); - }, + : null, + onAddPhotos: isOwner || canAddPhotos + ? () async { + await addAssets(context); + context.pop(); + } + : null, + onToggleAlbumOrder: isOwner + ? () async { + await toggleAlbumOrder(); + context.pop(); + } + : null, + onEditAlbum: isOwner + ? () async { + context.pop(); + await showEditTitleAndDescription(context); + } + : null, + onCreateSharedLink: isOwner + ? () async { + context.pop(); + unawaited(context.pushRoute(SharedLinkEditRoute(albumId: _album.id))); + } + : null, + onShowOptions: () { + context.pop(); + context.pushRoute(DriftAlbumOptionsRoute(album: _album)); + }, + ); + }, + ), ); } @override Widget build(BuildContext context) { - return PopScope( - onPopInvokedWithResult: (didPop, _) { - if (didPop) { - Future.microtask(() { - if (mounted) { - ref.read(currentRemoteAlbumProvider.notifier).dispose(); - ref.read(remoteAlbumProvider.notifier).refresh(); - } - }); - } - }, - child: ProviderScope( - overrides: [ - timelineServiceProvider.overrideWith((ref) { - final timelineService = ref.watch(timelineFactoryProvider).remoteAlbum(albumId: _album.id); - ref.onDispose(timelineService.dispose); - return timelineService; - }), - ], - child: Timeline( - appBar: RemoteAlbumSliverAppBar( - icon: Icons.photo_album_outlined, - onShowOptions: () => showOptionSheet(context), - onToggleAlbumOrder: () => toggleAlbumOrder(), - onEditTitle: () => showEditTitleAndDescription(context), - onActivity: () => showActivity(context), - ), - bottomSheet: RemoteAlbumBottomSheet(album: _album), + final user = ref.watch(currentUserProvider); + final isOwner = user != null ? user.id == _album.ownerId : false; + + return ProviderScope( + overrides: [ + timelineServiceProvider.overrideWith((ref) { + final timelineService = ref.watch(timelineFactoryProvider).remoteAlbum(albumId: _album.id); + ref.onDispose(timelineService.dispose); + return timelineService; + }), + currentRemoteAlbumScopedProvider.overrideWithValue(_album), + ], + child: Timeline( + appBar: RemoteAlbumSliverAppBar( + icon: Icons.photo_album_outlined, + onShowOptions: () => showOptionSheet(context), + onToggleAlbumOrder: isOwner ? () => toggleAlbumOrder() : null, + onEditTitle: isOwner ? () => showEditTitleAndDescription(context) : null, + onActivity: () => showActivity(context), ), + bottomSheet: RemoteAlbumBottomSheet(album: _album), ), ); } @@ -300,7 +306,7 @@ class _EditAlbumDialogState extends ConsumerState<_EditAlbumDialog> { await ref .read(remoteAlbumProvider.notifier) - .updateAlbum(widget.album.id, name: newTitle, description: newDescription.isEmpty ? null : newDescription); + .updateAlbum(widget.album.id, name: newTitle, description: newDescription); if (mounted) { Navigator.of( diff --git a/mobile/lib/presentation/pages/drift_trash.page.dart b/mobile/lib/presentation/pages/drift_trash.page.dart index 43e9217cdf..8713166027 100644 --- a/mobile/lib/presentation/pages/drift_trash.page.dart +++ b/mobile/lib/presentation/pages/drift_trash.page.dart @@ -44,10 +44,7 @@ class DriftTrashPage extends StatelessWidget { return SliverPadding( padding: const EdgeInsets.all(16.0), sliver: SliverToBoxAdapter( - child: SizedBox( - height: 24.0, - child: const Text("trash_page_info").t(context: context, args: {"days": "$trashDays"}), - ), + child: const Text("trash_page_info").t(context: context, args: {"days": "$trashDays"}), ), ); }, diff --git a/mobile/lib/presentation/pages/editing/drift_crop.page.dart b/mobile/lib/presentation/pages/editing/drift_crop.page.dart index 5b14292aa2..d8219e3b3c 100644 --- a/mobile/lib/presentation/pages/editing/drift_crop.page.dart +++ b/mobile/lib/presentation/pages/editing/drift_crop.page.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:auto_route/auto_route.dart'; import 'package:crop_image/crop_image.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -34,7 +36,7 @@ class DriftCropImagePage extends HookWidget { icon: Icon(Icons.done_rounded, color: context.primaryColor, size: 24), onPressed: () async { final croppedImage = await cropController.croppedImage(); - context.pushRoute(DriftEditImageRoute(asset: asset, image: croppedImage, isEdited: true)); + unawaited(context.pushRoute(DriftEditImageRoute(asset: asset, image: croppedImage, isEdited: true))); }, ), ], diff --git a/mobile/lib/presentation/pages/editing/drift_edit.page.dart b/mobile/lib/presentation/pages/editing/drift_edit.page.dart index da62d49b49..f9903b6b94 100644 --- a/mobile/lib/presentation/pages/editing/drift_edit.page.dart +++ b/mobile/lib/presentation/pages/editing/drift_edit.page.dart @@ -50,6 +50,11 @@ class DriftEditImagePage extends ConsumerWidget { return completer.future; } + void _exitEditing(BuildContext context) { + // this assumes that the only way to get to this page is from the AssetViewerRoute + context.navigator.popUntil((route) => route.data?.name == AssetViewerRoute.name); + } + Future _saveEditedImage(BuildContext context, BaseAsset asset, Image image, WidgetRef ref) async { try { final Uint8List imageData = await _imageToUint8List(image); @@ -65,8 +70,8 @@ class DriftEditImagePage extends ConsumerWidget { Logger("SaveEditedImage").warning("Failed to retrieve the saved image back from OS", e); } - ref.read(backgroundSyncProvider).syncLocal(full: true); - context.navigator.popUntil((route) => route.isFirst); + unawaited(ref.read(backgroundSyncProvider).syncLocal(full: true)); + _exitEditing(context); ImmichToast.show(durationInSecond: 3, context: context, msg: 'Image Saved!'); if (localAsset == null) { @@ -91,7 +96,7 @@ class DriftEditImagePage extends ConsumerWidget { backgroundColor: context.scaffoldBackgroundColor, leading: IconButton( icon: Icon(Icons.close_rounded, color: context.primaryColor, size: 24), - onPressed: () => context.navigator.popUntil((route) => route.isFirst), + onPressed: () => _exitEditing(context), ), actions: [ TextButton( diff --git a/mobile/lib/presentation/pages/editing/drift_filter.page.dart b/mobile/lib/presentation/pages/editing/drift_filter.page.dart index 75c3f81de2..8198a41bbe 100644 --- a/mobile/lib/presentation/pages/editing/drift_filter.page.dart +++ b/mobile/lib/presentation/pages/editing/drift_filter.page.dart @@ -75,7 +75,7 @@ class DriftFilterImagePage extends HookWidget { icon: Icon(Icons.done_rounded, color: context.primaryColor, size: 24), onPressed: () async { final filteredImage = await applyFilterAndConvert(colorFilter.value); - context.pushRoute(DriftEditImageRoute(asset: asset, image: filteredImage, isEdited: true)); + unawaited(context.pushRoute(DriftEditImageRoute(asset: asset, image: filteredImage, isEdited: true))); }, ), ], diff --git a/mobile/lib/presentation/pages/search/drift_search.page.dart b/mobile/lib/presentation/pages/search/drift_search.page.dart index 7e70ebf8ff..58ca892f5f 100644 --- a/mobile/lib/presentation/pages/search/drift_search.page.dart +++ b/mobile/lib/presentation/pages/search/drift_search.page.dart @@ -8,16 +8,19 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/person.model.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart'; +import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/models/search/search_filter.model.dart'; import 'package:immich_mobile/presentation/pages/search/paginated_search.provider.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart'; +import 'package:immich_mobile/presentation/widgets/search/quick_date_picker.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/search/search_input_focus.provider.dart'; import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/widgets/common/feature_check.dart'; import 'package:immich_mobile/widgets/common/search_field.dart'; import 'package:immich_mobile/widgets/search/search_filter/camera_picker.dart'; import 'package:immich_mobile/widgets/search/search_filter/display_option_picker.dart'; @@ -30,15 +33,14 @@ import 'package:immich_mobile/widgets/search/search_filter/search_filter_utils.d @RoutePage() class DriftSearchPage extends HookConsumerWidget { - const DriftSearchPage({super.key, this.preFilter}); - - final SearchFilter? preFilter; + const DriftSearchPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final textSearchType = useState(TextSearchType.context); final searchHintText = useState('sunrise_on_the_beach'.t(context: context)); final textSearchController = useTextEditingController(); + final preFilter = ref.watch(searchPreFilterProvider); final filter = useState( SearchFilter( people: preFilter?.people ?? {}, @@ -48,10 +50,12 @@ class DriftSearchPage extends HookConsumerWidget { display: preFilter?.display ?? SearchDisplayFilters(isNotInAlbum: false, isArchive: false, isFavorite: false), mediaType: preFilter?.mediaType ?? AssetType.other, language: "${context.locale.languageCode}-${context.locale.countryCode}", + assetId: preFilter?.assetId, ), ); final previousFilter = useState(null); + final dateInputFilter = useState(null); final peopleCurrentFilterWidget = useState(null); final dateRangeCurrentFilterWidget = useState(null); @@ -71,27 +75,29 @@ class DriftSearchPage extends HookConsumerWidget { ); } - search() async { - if (filter.value.isEmpty) { + searchFilter(SearchFilter filter) async { + if (filter.isEmpty) { return; } - if (preFilter == null && filter.value == previousFilter.value) { + if (preFilter == null && filter == previousFilter.value) { return; } isSearching.value = true; ref.watch(paginatedSearchProvider.notifier).clear(); - final hasResult = await ref.watch(paginatedSearchProvider.notifier).search(filter.value); + final hasResult = await ref.watch(paginatedSearchProvider.notifier).search(filter); if (!hasResult) { context.showSnackBar(searchInfoSnackBar('search_no_result'.t(context: context))); } - previousFilter.value = filter.value; + previousFilter.value = filter; isSearching.value = false; } + search() => searchFilter(filter.value); + loadMoreSearchResult() async { isSearching.value = true; final hasResult = await ref.watch(paginatedSearchProvider.notifier).search(filter.value); @@ -106,10 +112,10 @@ class DriftSearchPage extends HookConsumerWidget { searchPreFilter() { if (preFilter != null) { Future.delayed(Duration.zero, () { - search(); + searchFilter(preFilter); - if (preFilter!.location.city != null) { - locationCurrentFilterWidget.value = Text(preFilter!.location.city!, style: context.textTheme.labelLarge); + if (preFilter.location.city != null) { + locationCurrentFilterWidget.value = Text(preFilter.location.city!, style: context.textTheme.labelLarge); } }); } @@ -120,7 +126,7 @@ class DriftSearchPage extends HookConsumerWidget { searchPreFilter(); return null; - }, []); + }, [preFilter]); showPeoplePicker() { handleOnSelect(Set value) { @@ -241,19 +247,54 @@ class DriftSearchPage extends HookConsumerWidget { ); } + datePicked(DateFilterInputModel? selectedDate) { + dateInputFilter.value = selectedDate; + if (selectedDate == null) { + filter.value = filter.value.copyWith(date: SearchDateFilter()); + + dateRangeCurrentFilterWidget.value = null; + unawaited(search()); + return; + } + + final date = selectedDate.asDateTimeRange(); + + filter.value = filter.value.copyWith( + date: SearchDateFilter( + takenAfter: date.start, + takenBefore: date.end.add(const Duration(hours: 23, minutes: 59, seconds: 59)), + ), + ); + + dateRangeCurrentFilterWidget.value = Text( + selectedDate.asHumanReadable(context), + style: context.textTheme.labelLarge, + ); + + unawaited(search()); + } + showDatePicker() async { final firstDate = DateTime(1900); final lastDate = DateTime.now(); + var dateRange = DateTimeRange( + start: filter.value.date.takenAfter ?? lastDate, + end: filter.value.date.takenBefore ?? lastDate, + ); + + // datePicked() may increase the date, this will make the date picker fail an assertion + // Fixup the end date to be at most now. + if (dateRange.end.isAfter(lastDate)) { + dateRange = DateTimeRange(start: dateRange.start, end: lastDate); + } + final date = await showDateRangePicker( context: context, firstDate: firstDate, lastDate: lastDate, currentDate: DateTime.now(), - initialDateRange: DateTimeRange( - start: filter.value.date.takenAfter ?? lastDate, - end: filter.value.date.takenBefore ?? lastDate, - ), + initialDateRange: dateRange, helpText: 'search_filter_date_title'.t(context: context), cancelText: 'cancel'.t(context: context), confirmText: 'select'.t(context: context), @@ -267,40 +308,32 @@ class DriftSearchPage extends HookConsumerWidget { ); if (date == null) { - filter.value = filter.value.copyWith(date: SearchDateFilter()); - - dateRangeCurrentFilterWidget.value = null; - search(); - return; - } - - filter.value = filter.value.copyWith( - date: SearchDateFilter( - takenAfter: date.start, - takenBefore: date.end.add(const Duration(hours: 23, minutes: 59, seconds: 59)), - ), - ); - - // If date range is less than 24 hours, set the end date to the end of the day - if (date.end.difference(date.start).inHours < 24) { - dateRangeCurrentFilterWidget.value = Text( - DateFormat.yMMMd().format(date.start.toLocal()), - style: context.textTheme.labelLarge, - ); + datePicked(null); } else { - dateRangeCurrentFilterWidget.value = Text( - 'search_filter_date_interval'.t( - context: context, - args: { - "start": DateFormat.yMMMd().format(date.start.toLocal()), - "end": DateFormat.yMMMd().format(date.end.toLocal()), + datePicked(CustomDateFilter.fromRange(date)); + } + } + + showQuickDatePicker() { + showFilterBottomSheet( + context: context, + child: FilterBottomSheetScaffold( + title: "pick_date_range".tr(), + expanded: true, + onClear: () => datePicked(null), + child: QuickDatePicker( + currentInput: dateInputFilter.value, + onRequestPicker: () { + context.pop(); + showDatePicker(); + }, + onSelect: (date) { + context.pop(); + datePicked(date); }, ), - style: context.textTheme.labelLarge, - ); - } - - search(); + ), + ); } // MEDIA PICKER @@ -394,15 +427,18 @@ class DriftSearchPage extends HookConsumerWidget { handleTextSubmitted(String value) { switch (textSearchType.value) { case TextSearchType.context: - filter.value = filter.value.copyWith(filename: '', context: value, description: ''); + filter.value = filter.value.copyWith(filename: '', context: value, description: '', ocr: ''); break; case TextSearchType.filename: - filter.value = filter.value.copyWith(filename: value, context: '', description: ''); + filter.value = filter.value.copyWith(filename: value, context: '', description: '', ocr: ''); break; case TextSearchType.description: - filter.value = filter.value.copyWith(filename: '', context: '', description: value); + filter.value = filter.value.copyWith(filename: '', context: '', description: value, ocr: ''); + break; + case TextSearchType.ocr: + filter.value = filter.value.copyWith(filename: '', context: '', description: '', ocr: value); break; } @@ -413,6 +449,7 @@ class DriftSearchPage extends HookConsumerWidget { TextSearchType.context => Icons.image_search_rounded, TextSearchType.filename => Icons.abc_rounded, TextSearchType.description => Icons.text_snippet_outlined, + TextSearchType.ocr => Icons.document_scanner_outlined, }; return Scaffold( @@ -498,6 +535,27 @@ class DriftSearchPage extends HookConsumerWidget { searchHintText.value = 'search_by_description_example'.t(context: context); }, ), + FeatureCheck( + feature: (features) => features.ocr, + child: MenuItemButton( + child: ListTile( + leading: const Icon(Icons.document_scanner_outlined), + title: Text( + 'search_by_ocr'.t(context: context), + style: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w500, + color: textSearchType.value == TextSearchType.ocr ? context.colorScheme.primary : null, + ), + ), + selectedColor: context.colorScheme.primary, + selected: textSearchType.value == TextSearchType.ocr, + ), + onPressed: () { + textSearchType.value = TextSearchType.ocr; + searchHintText.value = 'search_by_ocr_example'.t(context: context); + }, + ), + ), ], ), ), @@ -560,7 +618,7 @@ class DriftSearchPage extends HookConsumerWidget { ), SearchFilterChip( icon: Icons.date_range_outlined, - onTap: showDatePicker, + onTap: showQuickDatePicker, label: 'search_filter_date'.t(context: context), currentFilter: dateRangeCurrentFilterWidget.value, ), @@ -599,9 +657,9 @@ class _SearchResultGrid extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final searchResult = ref.watch(paginatedSearchProvider); + final assets = ref.watch(paginatedSearchProvider.select((s) => s.assets)); - if (searchResult.totalAssets == 0) { + if (assets.isEmpty) { return const _SearchEmptyContent(); } @@ -615,6 +673,7 @@ class _SearchResultGrid extends ConsumerWidget { if (metrics.pixels >= metrics.maxScrollExtent && isVerticalScroll && !isBottomSheetNotification) { onScrollEnd(); + ref.read(paginatedSearchProvider.notifier).setScrollOffset(metrics.maxScrollExtent); } return true; @@ -623,17 +682,18 @@ class _SearchResultGrid extends ConsumerWidget { child: ProviderScope( overrides: [ timelineServiceProvider.overrideWith((ref) { - final timelineService = ref.watch(timelineFactoryProvider).fromAssets(searchResult.assets); + final timelineService = ref.watch(timelineFactoryProvider).fromAssets(assets, TimelineOrigin.search); ref.onDispose(timelineService.dispose); return timelineService; }), ], child: Timeline( - key: ValueKey(searchResult.totalAssets), + key: ValueKey(assets.length), groupBy: GroupAssetsBy.none, appBar: null, bottomSheet: const GeneralBottomSheet(minChildSize: 0.20), snapToMonth: false, + initialScrollOffset: ref.read(paginatedSearchProvider.select((s) => s.scrollOffset)), ), ), ), diff --git a/mobile/lib/presentation/pages/search/paginated_search.provider.dart b/mobile/lib/presentation/pages/search/paginated_search.provider.dart index 718a241ba4..e37aa7e0af 100644 --- a/mobile/lib/presentation/pages/search/paginated_search.provider.dart +++ b/mobile/lib/presentation/pages/search/paginated_search.provider.dart @@ -4,6 +4,23 @@ import 'package:immich_mobile/domain/services/search.service.dart'; import 'package:immich_mobile/models/search/search_filter.model.dart'; import 'package:immich_mobile/providers/infrastructure/search.provider.dart'; +final searchPreFilterProvider = NotifierProvider(SearchFilterProvider.new); + +class SearchFilterProvider extends Notifier { + @override + SearchFilter? build() { + return null; + } + + void setFilter(SearchFilter? filter) { + state = filter; + } + + void clear() { + state = null; + } +} + final paginatedSearchProvider = StateNotifierProvider( (ref) => PaginatedSearchNotifier(ref.watch(searchServiceProvider)), ); @@ -24,12 +41,20 @@ class PaginatedSearchNotifier extends StateNotifier { return false; } - state = SearchResult(assets: [...state.assets, ...result.assets], nextPage: result.nextPage); + state = SearchResult( + assets: [...state.assets, ...result.assets], + nextPage: result.nextPage, + scrollOffset: state.scrollOffset, + ); return true; } + void setScrollOffset(double offset) { + state = state.copyWith(scrollOffset: offset); + } + clear() { - state = const SearchResult(assets: [], nextPage: 1); + state = const SearchResult(assets: [], nextPage: 1, scrollOffset: 0.0); } } diff --git a/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart new file mode 100644 index 0000000000..9155d82753 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart @@ -0,0 +1,191 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/unarchive_action_button.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart'; +import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; +import 'package:immich_mobile/providers/routes.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; + +import 'package:immich_mobile/domain/models/album/album.model.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; + +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; + +enum AddToMenuItem { album, archive, unarchive, lockedFolder } + +class AddActionButton extends ConsumerWidget { + const AddActionButton({super.key}); + + Future _showAddOptions(BuildContext context, WidgetRef ref) async { + final asset = ref.read(currentAssetNotifier); + if (asset == null) return; + + final user = ref.read(currentUserProvider); + final isOwner = asset is RemoteAsset && asset.ownerId == user?.id; + final isInLockedView = ref.watch(inLockedViewProvider); + final isArchived = asset is RemoteAsset && asset.visibility == AssetVisibility.archive; + final hasRemote = asset is RemoteAsset; + final showArchive = isOwner && !isInLockedView && hasRemote && !isArchived; + final showUnarchive = isOwner && !isInLockedView && hasRemote && isArchived; + final menuItemHeight = 30.0; + + final List> items = [ + PopupMenuItem( + enabled: false, + textStyle: context.textTheme.labelMedium, + height: 40, + child: Text("add_to_bottom_bar".tr()), + ), + PopupMenuItem( + height: menuItemHeight, + value: AddToMenuItem.album, + child: ListTile(leading: const Icon(Icons.photo_album_outlined), title: Text("album".tr())), + ), + const PopupMenuDivider(), + PopupMenuItem(enabled: false, textStyle: context.textTheme.labelMedium, height: 40, child: Text("move_to".tr())), + if (isOwner) ...[ + if (showArchive) + PopupMenuItem( + height: menuItemHeight, + value: AddToMenuItem.archive, + child: ListTile(leading: const Icon(Icons.archive_outlined), title: Text("archive".tr())), + ), + if (showUnarchive) + PopupMenuItem( + height: menuItemHeight, + value: AddToMenuItem.unarchive, + child: ListTile(leading: const Icon(Icons.unarchive_outlined), title: Text("unarchive".tr())), + ), + PopupMenuItem( + height: menuItemHeight, + value: AddToMenuItem.lockedFolder, + child: ListTile(leading: const Icon(Icons.lock_outline), title: Text("locked_folder".tr())), + ), + ], + ]; + + final AddToMenuItem? selected = await showMenu( + context: context, + color: context.themeData.scaffoldBackgroundColor, + position: _menuPosition(context), + items: items, + ); + + if (selected == null) { + return; + } + + switch (selected) { + case AddToMenuItem.album: + _openAlbumSelector(context, ref); + break; + case AddToMenuItem.archive: + await performArchiveAction(context, ref, source: ActionSource.viewer); + break; + case AddToMenuItem.unarchive: + await performUnArchiveAction(context, ref, source: ActionSource.viewer); + break; + case AddToMenuItem.lockedFolder: + await performMoveToLockFolderAction(context, ref, source: ActionSource.viewer); + break; + } + } + + RelativeRect _menuPosition(BuildContext context) { + final renderObject = context.findRenderObject(); + if (renderObject is! RenderBox) { + return RelativeRect.fill; + } + + final size = renderObject.size; + final position = renderObject.localToGlobal(Offset.zero); + + return RelativeRect.fromLTRB(position.dx, position.dy - size.height - 200, position.dx + size.width, position.dy); + } + + void _openAlbumSelector(BuildContext context, WidgetRef ref) { + final currentAsset = ref.read(currentAssetNotifier); + if (currentAsset == null) { + ImmichToast.show(context: context, msg: "Cannot load asset information.", toastType: ToastType.error); + return; + } + + final List slivers = [ + AlbumSelector(onAlbumSelected: (album) => _addCurrentAssetToAlbum(context, ref, album)), + ]; + + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (_) { + return BaseBottomSheet( + actions: const [], + slivers: slivers, + initialChildSize: 0.6, + minChildSize: 0.3, + maxChildSize: 0.95, + expand: false, + backgroundColor: context.isDarkTheme ? Colors.black : Colors.white, + ); + }, + ); + } + + Future _addCurrentAssetToAlbum(BuildContext context, WidgetRef ref, RemoteAlbum album) async { + final latest = ref.read(currentAssetNotifier); + + if (latest == null) { + ImmichToast.show(context: context, msg: "Cannot load asset information.", toastType: ToastType.error); + return; + } + + final addedCount = await ref.read(remoteAlbumProvider.notifier).addAssets(album.id, [latest.remoteId!]); + + if (!context.mounted) { + return; + } + + if (addedCount == 0) { + ImmichToast.show( + context: context, + msg: 'add_to_album_bottom_sheet_already_exists'.tr(namedArgs: {'album': album.name}), + ); + } else { + ImmichToast.show( + context: context, + msg: 'add_to_album_bottom_sheet_added'.tr(namedArgs: {'album': album.name}), + ); + } + + if (!context.mounted) { + return; + } + await Navigator.of(context).maybePop(); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + final asset = ref.watch(currentAssetNotifier); + if (asset == null) { + return const SizedBox.shrink(); + } + return Builder( + builder: (buttonContext) { + return BaseActionButton( + iconData: Icons.add, + label: "add_to_bottom_bar".tr(), + onPressed: () => _showAddOptions(buttonContext, ref), + ); + }, + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/advanced_info_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/advanced_info_action_button.widget.dart index 170f827fdb..cb2581bc6d 100644 --- a/mobile/lib/presentation/widgets/action_buttons/advanced_info_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/advanced_info_action_button.widget.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; @@ -15,7 +17,7 @@ class AdvancedInfoActionButton extends ConsumerWidget { return; } - ref.read(actionProvider.notifier).troubleshoot(source, context); + unawaited(ref.read(actionProvider.notifier).troubleshoot(source, context)); } @override diff --git a/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart index d30ba07d0c..290a19f584 100644 --- a/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart @@ -10,33 +10,36 @@ import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; +// used to allow performing archive action from different sources (without duplicating code) +Future performArchiveAction(BuildContext context, WidgetRef ref, {required ActionSource source}) async { + if (!context.mounted) return; + + final result = await ref.read(actionProvider.notifier).archive(source); + ref.read(multiSelectProvider.notifier).reset(); + + if (source == ActionSource.viewer) { + EventStream.shared.emit(const ViewerReloadAssetEvent()); + } + + final successMessage = 'archive_action_prompt'.t(context: context, args: {'count': result.count.toString()}); + + if (context.mounted) { + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); + } +} + class ArchiveActionButton extends ConsumerWidget { final ActionSource source; const ArchiveActionButton({super.key, required this.source}); - void _onTap(BuildContext context, WidgetRef ref) async { - if (!context.mounted) { - return; - } - - final result = await ref.read(actionProvider.notifier).archive(source); - ref.read(multiSelectProvider.notifier).reset(); - - if (source == ActionSource.viewer) { - EventStream.shared.emit(const ViewerReloadAssetEvent()); - } - - final successMessage = 'archive_action_prompt'.t(context: context, args: {'count': result.count.toString()}); - - if (context.mounted) { - ImmichToast.show( - context: context, - msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), - gravity: ToastGravity.BOTTOM, - toastType: result.success ? ToastType.success : ToastType.error, - ); - } + Future _onTap(BuildContext context, WidgetRef ref) async { + await performArchiveAction(context, ref, source: source); } @override diff --git a/mobile/lib/presentation/widgets/action_buttons/delete_local_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/delete_local_action_button.widget.dart index cccdee9b3a..3cd939aeb6 100644 --- a/mobile/lib/presentation/widgets/action_buttons/delete_local_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/delete_local_action_button.widget.dart @@ -22,7 +22,11 @@ class DeleteLocalActionButton extends ConsumerWidget { return; } - final result = await ref.read(actionProvider.notifier).deleteLocal(source); + final result = await ref.read(actionProvider.notifier).deleteLocal(source, context); + if (result == null) { + return; + } + ref.read(multiSelectProvider.notifier).reset(); if (source == ActionSource.viewer) { diff --git a/mobile/lib/presentation/widgets/action_buttons/edit_image_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/edit_image_action_button.widget.dart index cde0db8e18..4c7b6ffbdc 100644 --- a/mobile/lib/presentation/widgets/action_buttons/edit_image_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/edit_image_action_button.widget.dart @@ -1,11 +1,11 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; -import 'package:immich_mobile/presentation/pages/editing/drift_edit.page.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/images/image_provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; class EditImageActionButton extends ConsumerWidget { const EditImageActionButton({super.key}); @@ -20,12 +20,7 @@ class EditImageActionButton extends ConsumerWidget { } final image = Image(image: getFullImageProvider(currentAsset)); - - context.navigator.push( - MaterialPageRoute( - builder: (context) => DriftEditImagePage(asset: currentAsset, image: image, isEdited: false), - ), - ); + context.pushRoute(DriftEditImageRoute(asset: currentAsset, image: image, isEdited: false)); } return BaseActionButton( diff --git a/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart index 78b9e3cde6..ddc83cb383 100644 --- a/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart @@ -10,36 +10,39 @@ import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; +// Reusable helper: move to locked folder from any source (e.g called from menu) +Future performMoveToLockFolderAction(BuildContext context, WidgetRef ref, {required ActionSource source}) async { + if (!context.mounted) return; + + final result = await ref.read(actionProvider.notifier).moveToLockFolder(source); + ref.read(multiSelectProvider.notifier).reset(); + + if (source == ActionSource.viewer) { + EventStream.shared.emit(const ViewerReloadAssetEvent()); + } + + final successMessage = 'move_to_lock_folder_action_prompt'.t( + context: context, + args: {'count': result.count.toString()}, + ); + + if (context.mounted) { + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); + } +} + class MoveToLockFolderActionButton extends ConsumerWidget { final ActionSource source; const MoveToLockFolderActionButton({super.key, required this.source}); - void _onTap(BuildContext context, WidgetRef ref) async { - if (!context.mounted) { - return; - } - - final result = await ref.read(actionProvider.notifier).moveToLockFolder(source); - ref.read(multiSelectProvider.notifier).reset(); - - if (source == ActionSource.viewer) { - EventStream.shared.emit(const ViewerReloadAssetEvent()); - } - - final successMessage = 'move_to_lock_folder_action_prompt'.t( - context: context, - args: {'count': result.count.toString()}, - ); - - if (context.mounted) { - ImmichToast.show( - context: context, - msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), - gravity: ToastGravity.BOTTOM, - toastType: result.success ? ToastType.success : ToastType.error, - ); - } + Future _onTap(BuildContext context, WidgetRef ref) async { + await performMoveToLockFolderAction(context, ref, source: source); } @override diff --git a/mobile/lib/presentation/widgets/action_buttons/share_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/share_action_button.widget.dart index 740ac528b0..6bcf099487 100644 --- a/mobile/lib/presentation/widgets/action_buttons/share_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/share_action_button.widget.dart @@ -39,7 +39,7 @@ class ShareActionButton extends ConsumerWidget { return; } - showDialog( + await showDialog( context: context, builder: (BuildContext buildContext) { ref.read(actionProvider.notifier).shareAssets(source, context).then((ActionResult result) { diff --git a/mobile/lib/presentation/widgets/action_buttons/similar_photos_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/similar_photos_action_button.widget.dart new file mode 100644 index 0000000000..4cbc0f0bb8 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/similar_photos_action_button.widget.dart @@ -0,0 +1,51 @@ +import 'dart:async'; + +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/models/search/search_filter.model.dart'; +import 'package:immich_mobile/presentation/pages/search/paginated_search.provider.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart'; +import 'package:immich_mobile/routing/router.dart'; + +class SimilarPhotosActionButton extends ConsumerWidget { + final String assetId; + + const SimilarPhotosActionButton({super.key, required this.assetId}); + + void _onTap(BuildContext context, WidgetRef ref) async { + if (!context.mounted) { + return; + } + + ref.invalidate(assetViewerProvider); + ref + .read(searchPreFilterProvider.notifier) + .setFilter( + SearchFilter( + assetId: assetId, + people: {}, + location: SearchLocationFilter(), + camera: SearchCameraFilter(), + date: SearchDateFilter(), + display: SearchDisplayFilters(isNotInAlbum: false, isArchive: false, isFavorite: false), + mediaType: AssetType.image, + ), + ); + + unawaited(context.navigateTo(const DriftSearchRoute())); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + iconData: Icons.compare, + label: "view_similar_photos".t(context: context), + onPressed: () => _onTap(context, ref), + maxWidth: 100, + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart index b457a1b4ca..8b04a1b05d 100644 --- a/mobile/lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart @@ -1,3 +1,5 @@ +// dart +// File: `lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart` import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -7,30 +9,39 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_bu import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; +import 'package:immich_mobile/domain/utils/event_stream.dart'; +import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart'; + +// used to allow performing unarchive action from different sources (without duplicating code) +Future performUnArchiveAction(BuildContext context, WidgetRef ref, {required ActionSource source}) async { + if (!context.mounted) return; + + final result = await ref.read(actionProvider.notifier).unArchive(source); + ref.read(multiSelectProvider.notifier).reset(); + + if (source == ActionSource.viewer) { + EventStream.shared.emit(const ViewerReloadAssetEvent()); + } + + final successMessage = 'unarchive_action_prompt'.t(context: context, args: {'count': result.count.toString()}); + + if (context.mounted) { + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); + } +} class UnArchiveActionButton extends ConsumerWidget { final ActionSource source; const UnArchiveActionButton({super.key, required this.source}); - void _onTap(BuildContext context, WidgetRef ref) async { - if (!context.mounted) { - return; - } - - final result = await ref.read(actionProvider.notifier).unArchive(source); - ref.read(multiSelectProvider.notifier).reset(); - - final successMessage = 'unarchive_action_prompt'.t(context: context, args: {'count': result.count.toString()}); - - if (context.mounted) { - ImmichToast.show( - context: context, - msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), - gravity: ToastGravity.BOTTOM, - toastType: result.success ? ToastType.success : ToastType.error, - ); - } + Future _onTap(BuildContext context, WidgetRef ref) async { + await performUnArchiveAction(context, ref, source: source); } @override diff --git a/mobile/lib/presentation/widgets/action_buttons/unstack_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/unstack_action_button.widget.dart index ecc8a39c74..a07803ace5 100644 --- a/mobile/lib/presentation/widgets/action_buttons/unstack_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/unstack_action_button.widget.dart @@ -36,7 +36,7 @@ class UnStackActionButton extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { return BaseActionButton( - iconData: Icons.filter_none_rounded, + iconData: Icons.layers_clear_outlined, label: "unstack".t(context: context), onPressed: () => _onTap(context, ref), ); diff --git a/mobile/lib/presentation/widgets/album/album_selector.widget.dart b/mobile/lib/presentation/widgets/album/album_selector.widget.dart index 680dd04ed2..ee546c6525 100644 --- a/mobile/lib/presentation/widgets/album/album_selector.widget.dart +++ b/mobile/lib/presentation/widgets/album/album_selector.widget.dart @@ -12,10 +12,9 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/models/albums/album_search.model.dart'; -import 'package:immich_mobile/pages/common/large_leading_tile.dart'; +import 'package:immich_mobile/presentation/widgets/album/album_tile.dart'; import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; @@ -144,7 +143,7 @@ class _AlbumSelectorState extends ConsumerState { // we need to re-filter the albums after sorting // so shownAlbums gets updated - filterAlbums(); + unawaited(filterAlbums()); } Future filterAlbums() async { @@ -571,42 +570,10 @@ class _AlbumList extends ConsumerWidget { } return SliverPadding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), + padding: const EdgeInsets.only(left: 16.0, right: 16, bottom: 64), sliver: SliverList.builder( itemBuilder: (_, index) { final album = albums[index]; - final albumTile = LargeLeadingTile( - title: Text( - album.name, - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600), - ), - subtitle: Text( - '${'items_count'.t(context: context, args: {'count': album.assetCount})} • ${album.ownerId != userId ? 'shared_by_user'.t(context: context, args: {'user': album.ownerName}) : 'owned'.t(context: context)}', - overflow: TextOverflow.ellipsis, - style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary), - ), - onTap: () => onAlbumSelected(album), - leadingPadding: const EdgeInsets.only(right: 16), - leading: album.thumbnailAssetId != null - ? ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(15)), - child: SizedBox(width: 80, height: 80, child: Thumbnail.remote(remoteId: album.thumbnailAssetId!)), - ) - : SizedBox( - width: 80, - height: 80, - child: Container( - decoration: BoxDecoration( - color: context.colorScheme.surfaceContainer, - borderRadius: const BorderRadius.all(Radius.circular(16)), - border: Border.all(color: context.colorScheme.outline.withAlpha(50), width: 1), - ), - child: const Icon(Icons.photo_album_rounded, size: 24, color: Colors.grey), - ), - ), - ); final isOwner = album.ownerId == userId; if (isOwner) { @@ -635,11 +602,14 @@ class _AlbumList extends ConsumerWidget { onDismissed: (direction) async { await ref.read(remoteAlbumProvider.notifier).deleteAlbum(album.id); }, - child: albumTile, + child: AlbumTile(album: album, isOwner: isOwner, onAlbumSelected: onAlbumSelected), ), ); } else { - return Padding(padding: const EdgeInsets.only(bottom: 8.0), child: albumTile); + return Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: AlbumTile(album: album, isOwner: isOwner, onAlbumSelected: onAlbumSelected), + ); } }, itemCount: albums.length, @@ -768,9 +738,8 @@ class AddToAlbumHeader extends ConsumerWidget { return; } - ref.read(currentRemoteAlbumProvider.notifier).setAlbum(newAlbum); ref.read(multiSelectProvider.notifier).reset(); - context.pushRoute(RemoteAlbumRoute(album: newAlbum)); + unawaited(context.pushRoute(RemoteAlbumRoute(album: newAlbum))); } return SliverPadding( diff --git a/mobile/lib/presentation/widgets/album/album_tile.dart b/mobile/lib/presentation/widgets/album/album_tile.dart new file mode 100644 index 0000000000..561b018ef8 --- /dev/null +++ b/mobile/lib/presentation/widgets/album/album_tile.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:immich_mobile/domain/models/album/album.model.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/theme_extensions.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/pages/common/large_leading_tile.dart'; +import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; + +class AlbumTile extends StatelessWidget { + const AlbumTile({super.key, required this.album, required this.isOwner, this.onAlbumSelected}); + + final RemoteAlbum album; + final bool isOwner; + final Function(RemoteAlbum)? onAlbumSelected; + + @override + Widget build(BuildContext context) { + return LargeLeadingTile( + title: Text( + album.name, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600), + ), + subtitle: Text( + '${'items_count'.t(context: context, args: {'count': album.assetCount})} • ${isOwner ? 'owned'.t(context: context) : 'shared_by_user'.t(context: context, args: {'user': album.ownerName})}', + overflow: TextOverflow.ellipsis, + style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary), + ), + onTap: () => onAlbumSelected?.call(album), + leadingPadding: const EdgeInsets.only(right: 16), + leading: album.thumbnailAssetId != null + ? ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(15)), + child: SizedBox(width: 80, height: 80, child: Thumbnail.remote(remoteId: album.thumbnailAssetId!)), + ) + : SizedBox( + width: 80, + height: 80, + child: Container( + decoration: BoxDecoration( + color: context.colorScheme.surfaceContainer, + borderRadius: const BorderRadius.all(Radius.circular(16)), + border: Border.all(color: context.colorScheme.outline.withAlpha(50), width: 1), + ), + child: const Icon(Icons.photo_album_rounded, size: 24, color: Colors.grey), + ), + ), + ); + } +} diff --git a/mobile/lib/presentation/widgets/album/drift_activity_text_field.dart b/mobile/lib/presentation/widgets/album/drift_activity_text_field.dart index a49ac9551a..fe5c763ec5 100644 --- a/mobile/lib/presentation/widgets/album/drift_activity_text_field.dart +++ b/mobile/lib/presentation/widgets/album/drift_activity_text_field.dart @@ -7,6 +7,7 @@ import 'package:immich_mobile/widgets/common/user_circle_avatar.dart'; class DriftActivityTextField extends ConsumerStatefulWidget { final bool isEnabled; + final bool isBottomSheet; final String? likeId; final Function(String) onSubmit; final Function()? onKeyboardFocus; @@ -16,6 +17,7 @@ class DriftActivityTextField extends ConsumerStatefulWidget { this.isEnabled = true, this.likeId, this.onKeyboardFocus, + this.isBottomSheet = false, super.key, }); @@ -34,8 +36,6 @@ class _DriftActivityTextFieldState extends ConsumerState inputController = TextEditingController(); inputFocusNode = FocusNode(); - inputFocusNode.requestFocus(); - inputFocusNode.addListener(() { if (inputFocusNode.hasFocus) { widget.onKeyboardFocus?.call(); @@ -72,7 +72,7 @@ class _DriftActivityTextFieldState extends ConsumerState } return Padding( - padding: const EdgeInsets.symmetric(vertical: 10), + padding: EdgeInsets.symmetric(vertical: widget.isBottomSheet ? 0 : 10), child: TextField( controller: inputController, enabled: widget.isEnabled, diff --git a/mobile/lib/presentation/widgets/asset_viewer/activities_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/activities_bottom_sheet.widget.dart new file mode 100644 index 0000000000..63669495b9 --- /dev/null +++ b/mobile/lib/presentation/widgets/asset_viewer/activities_bottom_sheet.widget.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/widgets/activities/comment_bubble.dart'; +import 'package:immich_mobile/presentation/widgets/album/drift_activity_text_field.dart'; +import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; +import 'package:immich_mobile/providers/activity.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart'; + +class ActivitiesBottomSheet extends HookConsumerWidget { + final DraggableScrollableController controller; + final double initialChildSize; + final bool scrollToBottomInitially; + + const ActivitiesBottomSheet({ + required this.controller, + this.initialChildSize = 0.35, + this.scrollToBottomInitially = true, + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final album = ref.watch(currentRemoteAlbumProvider)!; + final asset = ref.watch(currentAssetNotifier) as RemoteAsset?; + + final activityNotifier = ref.read(albumActivityProvider(album.id, asset?.id).notifier); + final activities = ref.watch(albumActivityProvider(album.id, asset?.id)); + + Future onAddComment(String comment) async { + await activityNotifier.addComment(comment); + } + + Widget buildActivitiesSliver() { + return activities.widgetWhen( + onLoading: () => const SliverToBoxAdapter(child: SizedBox.shrink()), + onData: (data) { + return SliverList( + delegate: SliverChildBuilderDelegate((context, index) { + if (index == data.length) { + return const SizedBox.shrink(); + } + final activity = data[data.length - 1 - index]; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + child: CommentBubble(activity: activity, isAssetActivity: true), + ); + }, childCount: data.length + 1), + ); + }, + ); + } + + return BaseBottomSheet( + actions: [], + slivers: [buildActivitiesSliver()], + footer: Padding( + // TODO: avoid fixed padding, use context.padding.bottom + padding: const EdgeInsets.only(bottom: 32), + child: Column( + children: [ + const Divider(indent: 16, endIndent: 16), + DriftActivityTextField( + isEnabled: album.isActivityEnabled, + isBottomSheet: true, + // likeId: likedId, + onSubmit: onAddComment, + ), + ], + ), + ), + controller: controller, + initialChildSize: initialChildSize, + minChildSize: 0.1, + maxChildSize: 0.88, + expand: false, + shouldCloseOnMinExtent: false, + resizeOnScroll: false, + backgroundColor: context.isDarkTheme ? Colors.black : Colors.white, + ); + } +} diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart index 7ac8cf34d5..50c4347301 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart @@ -5,6 +5,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart'; import 'package:immich_mobile/domain/services/timeline.service.dart'; @@ -13,6 +14,7 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/extensions/scroll_extensions.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/download_status_floating_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/asset_viewer/activities_bottom_sheet.widget.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.provider.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.widget.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart'; @@ -27,6 +29,7 @@ import 'package:immich_mobile/providers/asset_viewer/video_player_controls_provi import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart'; import 'package:immich_mobile/providers/cast.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart'; @@ -38,15 +41,25 @@ class AssetViewerPage extends StatelessWidget { final int initialIndex; final TimelineService timelineService; final int? heroOffset; + final RemoteAlbum? currentAlbum; - const AssetViewerPage({super.key, required this.initialIndex, required this.timelineService, this.heroOffset}); + const AssetViewerPage({ + super.key, + required this.initialIndex, + required this.timelineService, + this.heroOffset, + this.currentAlbum, + }); @override Widget build(BuildContext context) { // This is necessary to ensure that the timeline service is available // since the Timeline and AssetViewer are on different routes / Widget subtrees. return ProviderScope( - overrides: [timelineServiceProvider.overrideWithValue(timelineService)], + overrides: [ + timelineServiceProvider.overrideWithValue(timelineService), + currentRemoteAlbumScopedProvider.overrideWithValue(currentAlbum), + ], child: AssetViewer(initialIndex: initialIndex, heroOffset: heroOffset), ); } @@ -221,10 +234,8 @@ class _AssetViewerState extends ConsumerState { context.scaffoldMessenger.hideCurrentSnackBar(); // send image to casting if the server has it - if (asset.hasRemote) { - final remoteAsset = asset as RemoteAsset; - - ref.read(castProvider.notifier).loadMedia(remoteAsset, false); + if (asset is RemoteAsset) { + ref.read(castProvider.notifier).loadMedia(asset, false); } else { // casting cannot show local assets context.scaffoldMessenger.clearSnackBars(); @@ -420,7 +431,7 @@ class _AssetViewerState extends ConsumerState { if (event is ViewerOpenBottomSheetEvent) { final extent = _kBottomSheetMinimumExtent + 0.3; - _openBottomSheet(scaffoldContext!, extent: extent); + _openBottomSheet(scaffoldContext!, extent: extent, activitiesMode: event.activitiesMode); final offset = _getVerticalOffsetForBottomSheet(extent); viewController?.position = Offset(0, -offset); return; @@ -462,7 +473,7 @@ class _AssetViewerState extends ConsumerState { }); } - void _openBottomSheet(BuildContext ctx, {double extent = _kBottomSheetMinimumExtent}) { + void _openBottomSheet(BuildContext ctx, {double extent = _kBottomSheetMinimumExtent, bool activitiesMode = false}) { ref.read(assetViewerProvider.notifier).setBottomSheet(true); initialScale = viewController?.scale; // viewController?.updateMultiple(scale: (viewController?.scale ?? 1.0) + 0.01); @@ -476,7 +487,9 @@ class _AssetViewerState extends ConsumerState { builder: (_) { return NotificationListener( onNotification: _onNotification, - child: AssetDetailBottomSheet(controller: bottomSheetController, initialChildSize: extent), + child: activitiesMode + ? ActivitiesBottomSheet(controller: bottomSheetController, initialChildSize: extent) + : AssetDetailBottomSheet(controller: bottomSheetController, initialChildSize: extent), ); }, ); @@ -614,10 +627,10 @@ class _AssetViewerState extends ConsumerState { // Rebuild the widget when the asset viewer state changes // Using multiple selectors to avoid unnecessary rebuilds for other state changes ref.watch(assetViewerProvider.select((s) => s.showingBottomSheet)); - ref.watch(assetViewerProvider.select((s) => s.showingControls)); ref.watch(assetViewerProvider.select((s) => s.backgroundOpacity)); ref.watch(assetViewerProvider.select((s) => s.stackIndex)); ref.watch(isPlayingMotionVideoProvider); + final showingControls = ref.watch(assetViewerProvider.select((s) => s.showingControls)); // Listen for casting changes and send initial asset to the cast provider ref.listen(castProvider.select((value) => value.isCasting), (_, isCasting) async { @@ -634,9 +647,9 @@ class _AssetViewerState extends ConsumerState { // Listen for control visibility changes and change system UI mode accordingly ref.listen(assetViewerProvider.select((value) => value.showingControls), (_, showingControls) async { if (showingControls) { - SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + unawaited(SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge)); } else { - SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); + unawaited(SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky)); } }); @@ -650,7 +663,14 @@ class _AssetViewerState extends ConsumerState { appBar: const ViewerTopAppBar(), extendBody: true, extendBodyBehindAppBar: true, - floatingActionButton: const DownloadStatusFloatingButton(), + floatingActionButton: IgnorePointer( + ignoring: !showingControls, + child: AnimatedOpacity( + opacity: showingControls ? 1.0 : 0.0, + duration: Durations.short2, + child: const DownloadStatusFloatingButton(), + ), + ), body: Stack( children: [ PhotoViewGallery.builder( diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.state.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.state.dart index 354902c9d7..d0fb1f8ba0 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.state.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.state.dart @@ -4,7 +4,8 @@ import 'package:immich_mobile/providers/asset_viewer/video_player_controls_provi import 'package:riverpod_annotation/riverpod_annotation.dart'; class ViewerOpenBottomSheetEvent extends Event { - const ViewerOpenBottomSheetEvent(); + final bool activitiesMode; + const ViewerOpenBottomSheetEvent({this.activitiesMode = false}); } class ViewerReloadAssetEvent extends Event { diff --git a/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart index 3111512823..14c03ad637 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart @@ -3,13 +3,12 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/edit_image_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart'; -import 'package:immich_mobile/presentation/widgets/action_buttons/unarchive_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/add_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart'; import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; @@ -34,7 +33,6 @@ class ViewerBottomBar extends ConsumerWidget { int opacity = ref.watch(assetViewerProvider.select((state) => state.backgroundOpacity)); final showControls = ref.watch(assetViewerProvider.select((s) => s.showingControls)); final isInLockedView = ref.watch(inLockedViewProvider); - final isArchived = asset is RemoteAsset && asset.visibility == AssetVisibility.archive; if (!showControls) { opacity = 0; @@ -44,11 +42,9 @@ class ViewerBottomBar extends ConsumerWidget { const ShareActionButton(source: ActionSource.viewer), if (asset.isLocalOnly) const UploadActionButton(source: ActionSource.viewer), if (asset.type == AssetType.image) const EditImageActionButton(), + if (asset.hasRemote) const AddActionButton(), + if (isOwner) ...[ - if (asset.hasRemote && isOwner && isArchived) - const UnArchiveActionButton(source: ActionSource.viewer) - else - const ArchiveActionButton(source: ActionSource.viewer), asset.isLocalOnly ? const DeleteLocalActionButton(source: ActionSource.viewer) : const DeleteActionButton(source: ActionSource.viewer, showConfirmation: true), diff --git a/mobile/lib/presentation/widgets/asset_viewer/bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/bottom_sheet.widget.dart index 7431290ad8..d29e09a247 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/bottom_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/bottom_sheet.widget.dart @@ -1,3 +1,7 @@ +import 'dart:async'; + +import 'package:auto_route/auto_route.dart'; +import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -8,17 +12,22 @@ import 'package:immich_mobile/domain/models/exif.model.dart'; import 'package:immich_mobile/domain/models/setting.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/album/album_tile.dart'; +import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_sheet/sheet_location_details.widget.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_sheet/sheet_people_details.widget.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; import 'package:immich_mobile/providers/routes.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/repositories/asset_media.repository.dart'; +import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/utils/action_button.utils.dart'; import 'package:immich_mobile/utils/bytes_units.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; @@ -51,6 +60,7 @@ class AssetDetailBottomSheet extends ConsumerWidget { isArchived: isArchived, isTrashEnabled: isTrashEnable, isInLockedView: isInLockedView, + isStacked: asset is RemoteAsset && asset.stackId != null, currentAlbum: currentAlbum, advancedTroubleshooting: advancedTroubleshooting, source: ActionSource.viewer, @@ -117,19 +127,92 @@ class _AssetDetailBottomSheet extends ConsumerWidget { if (exifInfo == null) { return null; } - - final fNumber = exifInfo.fNumber.isNotEmpty ? 'ƒ/${exifInfo.fNumber}' : null; final exposureTime = exifInfo.exposureTime.isNotEmpty ? exifInfo.exposureTime : null; - final focalLength = exifInfo.focalLength.isNotEmpty ? '${exifInfo.focalLength} mm' : null; final iso = exifInfo.iso != null ? 'ISO ${exifInfo.iso}' : null; + return [exposureTime, iso].where((spec) => spec != null && spec.isNotEmpty).join(_kSeparator); + } - return [fNumber, exposureTime, focalLength, iso].where((spec) => spec != null && spec.isNotEmpty).join(_kSeparator); + String? _getLensInfoSubtitle(ExifInfo? exifInfo) { + if (exifInfo == null) { + return null; + } + final fNumber = exifInfo.fNumber.isNotEmpty ? 'ƒ/${exifInfo.fNumber}' : null; + final focalLength = exifInfo.focalLength.isNotEmpty ? '${exifInfo.focalLength} mm' : null; + return [fNumber, focalLength].where((spec) => spec != null && spec.isNotEmpty).join(_kSeparator); } Future _editDateTime(BuildContext context, WidgetRef ref) async { await ref.read(actionProvider.notifier).editDateTime(ActionSource.viewer, context); } + Widget _buildAppearsInList(WidgetRef ref, BuildContext context) { + final asset = ref.watch(currentAssetNotifier); + if (asset == null) { + return const SizedBox.shrink(); + } + + if (!asset.hasRemote) { + return const SizedBox.shrink(); + } + + String? remoteAssetId; + if (asset is RemoteAsset) { + remoteAssetId = asset.id; + } else if (asset is LocalAsset) { + remoteAssetId = asset.remoteAssetId; + } + + if (remoteAssetId == null) { + return const SizedBox.shrink(); + } + + final userId = ref.watch(currentUserProvider)?.id; + final assetAlbums = ref.watch(albumsContainingAssetProvider(remoteAssetId)); + + return assetAlbums.when( + data: (albums) { + if (albums.isEmpty) { + return const SizedBox.shrink(); + } + + albums.sortBy((a) => a.name); + + return Column( + spacing: 12, + children: [ + if (albums.isNotEmpty) + _SheetTile( + title: 'appears_in'.t(context: context).toUpperCase(), + titleStyle: context.textTheme.labelMedium?.copyWith( + color: context.textTheme.labelMedium?.color?.withAlpha(200), + fontWeight: FontWeight.w600, + ), + ), + Padding( + padding: const EdgeInsets.only(left: 24), + child: Column( + spacing: 12, + children: albums.map((album) { + final isOwner = album.ownerId == userId; + return AlbumTile( + album: album, + isOwner: isOwner, + onAlbumSelected: (album) async { + ref.invalidate(assetViewerProvider); + unawaited(context.router.popAndPush(RemoteAlbumRoute(album: album))); + }, + ); + }).toList(), + ), + ), + ], + ); + }, + loading: () => const SizedBox.shrink(), + error: (_, __) => const SizedBox.shrink(), + ); + } + @override Widget build(BuildContext context, WidgetRef ref) { final asset = ref.watch(currentAssetNotifier); @@ -139,29 +222,35 @@ class _AssetDetailBottomSheet extends ConsumerWidget { final exifInfo = ref.watch(currentAssetExifProvider).valueOrNull; final cameraTitle = _getCameraInfoTitle(exifInfo); + final lensTitle = exifInfo?.lens != null && exifInfo!.lens!.isNotEmpty ? exifInfo.lens : null; + final isOwner = ref.watch(currentUserProvider)?.id == (asset is RemoteAsset ? asset.ownerId : null); - return SliverList.list( - children: [ - // Asset Date and Time - _SheetTile( - title: _getDateTime(context, asset), - titleStyle: context.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600), - trailing: asset.hasRemote ? const Icon(Icons.edit, size: 18) : null, - onTap: asset.hasRemote ? () async => await _editDateTime(context, ref) : null, - ), - if (exifInfo != null) _SheetAssetDescription(exif: exifInfo), - const SheetPeopleDetails(), - const SheetLocationDetails(), - // Details header - _SheetTile( - title: 'exif_bottom_sheet_details'.t(context: context), - titleStyle: context.textTheme.labelMedium?.copyWith( - color: context.textTheme.labelMedium?.color?.withAlpha(200), - fontWeight: FontWeight.w600, - ), - ), - // File info - _SheetTile( + // Build file info tile based on asset type + Widget buildFileInfoTile() { + if (asset is LocalAsset) { + final assetMediaRepository = ref.watch(assetMediaRepositoryProvider); + return FutureBuilder( + future: assetMediaRepository.getOriginalFilename(asset.id), + builder: (context, snapshot) { + final displayName = snapshot.data ?? asset.name; + return _SheetTile( + title: displayName, + titleStyle: context.textTheme.labelLarge, + leading: Icon( + asset.isImage ? Icons.image_outlined : Icons.videocam_outlined, + size: 24, + color: context.textTheme.labelLarge?.color, + ), + subtitle: _getFileInfo(asset, exifInfo), + subtitleStyle: context.textTheme.bodyMedium?.copyWith( + color: context.textTheme.bodyMedium?.color?.withAlpha(155), + ), + ); + }, + ); + } else { + // For remote assets, use the name directly + return _SheetTile( title: asset.name, titleStyle: context.textTheme.labelLarge, leading: Icon( @@ -173,18 +262,58 @@ class _AssetDetailBottomSheet extends ConsumerWidget { subtitleStyle: context.textTheme.bodyMedium?.copyWith( color: context.textTheme.bodyMedium?.color?.withAlpha(155), ), + ); + } + } + + return SliverList.list( + children: [ + // Asset Date and Time + _SheetTile( + title: _getDateTime(context, asset), + titleStyle: context.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600), + trailing: asset.hasRemote && isOwner ? const Icon(Icons.edit, size: 18) : null, + onTap: asset.hasRemote && isOwner ? () async => await _editDateTime(context, ref) : null, ), + if (exifInfo != null) _SheetAssetDescription(exif: exifInfo, isEditable: isOwner), + const SheetPeopleDetails(), + const SheetLocationDetails(), + // Details header + _SheetTile( + title: 'exif_bottom_sheet_details'.t(context: context), + titleStyle: context.textTheme.labelMedium?.copyWith( + color: context.textTheme.labelMedium?.color?.withAlpha(200), + fontWeight: FontWeight.w600, + ), + ), + // File info + buildFileInfoTile(), // Camera info if (cameraTitle != null) _SheetTile( title: cameraTitle, titleStyle: context.textTheme.labelLarge, - leading: Icon(Icons.camera_outlined, size: 24, color: context.textTheme.labelLarge?.color), + leading: Icon(Icons.camera_alt_outlined, size: 24, color: context.textTheme.labelLarge?.color), subtitle: _getCameraInfoSubtitle(exifInfo), subtitleStyle: context.textTheme.bodyMedium?.copyWith( color: context.textTheme.bodyMedium?.color?.withAlpha(155), ), ), + // Lens info + if (lensTitle != null) + _SheetTile( + title: lensTitle, + titleStyle: context.textTheme.labelLarge, + leading: Icon(Icons.camera_outlined, size: 24, color: context.textTheme.labelLarge?.color), + subtitle: _getLensInfoSubtitle(exifInfo), + subtitleStyle: context.textTheme.bodyMedium?.copyWith( + color: context.textTheme.bodyMedium?.color?.withAlpha(155), + ), + ), + // Appears in (Albums) + _buildAppearsInList(ref, context), + // padding at the bottom to avoid cut-off + const SizedBox(height: 100), ], ); } @@ -263,8 +392,9 @@ class _SheetTile extends ConsumerWidget { class _SheetAssetDescription extends ConsumerStatefulWidget { final ExifInfo exif; + final bool isEditable; - const _SheetAssetDescription({required this.exif}); + const _SheetAssetDescription({required this.exif, this.isEditable = true}); @override ConsumerState<_SheetAssetDescription> createState() => _SheetAssetDescriptionState(); @@ -310,27 +440,33 @@ class _SheetAssetDescriptionState extends ConsumerState<_SheetAssetDescription> // Update controller text when EXIF data changes final currentDescription = currentExifInfo?.description ?? ''; + final hintText = (widget.isEditable ? 'exif_bottom_sheet_description' : 'exif_bottom_sheet_no_description').t( + context: context, + ); if (_controller.text != currentDescription && !_descriptionFocus.hasFocus) { _controller.text = currentDescription; } return Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8), - child: TextField( - controller: _controller, - keyboardType: TextInputType.multiline, - focusNode: _descriptionFocus, - maxLines: null, // makes it grow as text is added - decoration: InputDecoration( - hintText: 'exif_bottom_sheet_description'.t(context: context), - border: InputBorder.none, - enabledBorder: InputBorder.none, - focusedBorder: InputBorder.none, - disabledBorder: InputBorder.none, - errorBorder: InputBorder.none, - focusedErrorBorder: InputBorder.none, + child: IgnorePointer( + ignoring: !widget.isEditable, + child: TextField( + controller: _controller, + keyboardType: TextInputType.multiline, + focusNode: _descriptionFocus, + maxLines: null, // makes it grow as text is added + decoration: InputDecoration( + hintText: hintText, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + ), + onTapOutside: (_) => saveDescription(currentExifInfo?.description), ), - onTapOutside: (_) => saveDescription(currentExifInfo?.description), ), ); } diff --git a/mobile/lib/presentation/widgets/asset_viewer/bottom_sheet/sheet_people_details.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/bottom_sheet/sheet_people_details.widget.dart index fee34bca1b..64f22eca92 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/bottom_sheet/sheet_people_details.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/bottom_sheet/sheet_people_details.widget.dart @@ -11,8 +11,8 @@ import 'package:immich_mobile/providers/infrastructure/people.provider.dart'; import 'package:immich_mobile/providers/routes.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/services/api.service.dart'; -import 'package:immich_mobile/utils/people.utils.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; +import 'package:immich_mobile/utils/people.utils.dart'; class SheetPeopleDetails extends ConsumerStatefulWidget { const SheetPeopleDetails({super.key}); @@ -158,11 +158,14 @@ class _PeopleAvatar extends StatelessWidget { maxLines: 1, ), if (person.birthDate != null) - Text( - formatAge(person.birthDate!, assetFileCreatedAt), - textAlign: TextAlign.center, - style: context.textTheme.bodyMedium?.copyWith( - color: context.textTheme.bodyMedium?.color?.withAlpha(175), + FittedBox( + fit: BoxFit.scaleDown, + child: Text( + formatAge(person.birthDate!, assetFileCreatedAt), + textAlign: TextAlign.center, + style: context.textTheme.bodyMedium?.copyWith( + color: context.textTheme.bodyMedium?.color?.withAlpha(175), + ), ), ), ], diff --git a/mobile/lib/presentation/widgets/asset_viewer/top_app_bar.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/top_app_bar.widget.dart index 0159e04c4e..ab88dffab4 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/top_app_bar.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/top_app_bar.widget.dart @@ -4,6 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart'; +import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; @@ -13,12 +14,13 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/favorite_actio import 'package:immich_mobile/presentation/widgets/action_buttons/motion_photo_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart'; +import 'package:immich_mobile/providers/activity.provider.dart'; import 'package:immich_mobile/providers/cast.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/routes.provider.dart'; -import 'package:immich_mobile/providers/tab.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/routing/router.dart'; @@ -39,17 +41,23 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget { final isInLockedView = ref.watch(inLockedViewProvider); final isReadonlyModeEnabled = ref.watch(readonlyModeProvider); - final previousRouteName = ref.watch(previousRouteNameProvider); - final tabRoute = ref.watch(tabProvider); + final timelineOrigin = ref.read(timelineServiceProvider).origin; final showViewInTimelineButton = - (previousRouteName != TabShellRoute.name || tabRoute == TabEnum.search) && - previousRouteName != AssetViewerRoute.name && - previousRouteName != null; + timelineOrigin != TimelineOrigin.main && + timelineOrigin != TimelineOrigin.deepLink && + timelineOrigin != TimelineOrigin.trash && + timelineOrigin != TimelineOrigin.archive && + timelineOrigin != TimelineOrigin.localAlbum && + isOwner; final isShowingSheet = ref.watch(assetViewerProvider.select((state) => state.showingBottomSheet)); int opacity = ref.watch(assetViewerProvider.select((state) => state.backgroundOpacity)); final showControls = ref.watch(assetViewerProvider.select((s) => s.showingControls)); + if (album != null && album.isActivityEnabled && album.isShared && asset is RemoteAsset) { + ref.watch(albumActivityProvider(album.id, asset.id)); + } + if (!showControls) { opacity = 0; } @@ -57,13 +65,13 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget { final isCasting = ref.watch(castProvider.select((c) => c.isCasting)); final actions = [ - if (asset.hasRemote) const DownloadActionButton(source: ActionSource.viewer, menuItem: true), + if (asset.isRemoteOnly) const DownloadActionButton(source: ActionSource.viewer, menuItem: true), if (isCasting || (asset.hasRemote)) const CastActionButton(menuItem: true), if (album != null && album.isActivityEnabled && album.isShared) IconButton( icon: const Icon(Icons.chat_outlined), onPressed: () { - context.navigateTo(const DriftActivitiesRoute()); + EventStream.shared.emit(const ViewerOpenBottomSheetEvent(activitiesMode: true)); }, ), if (showViewInTimelineButton) diff --git a/mobile/lib/presentation/widgets/asset_viewer/video_viewer.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/video_viewer.widget.dart index fa7f204596..08b5b25343 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/video_viewer.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/video_viewer.widget.dart @@ -7,6 +7,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/setting.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/services/setting.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart'; @@ -45,6 +46,7 @@ bool _isCurrentAsset(BaseAsset asset, BaseAsset? currentAsset) { } class NativeVideoViewer extends HookConsumerWidget { + static final log = Logger('NativeVideoViewer'); final BaseAsset asset; final bool showControls; final int playbackDelayFactor; @@ -78,8 +80,6 @@ class NativeVideoViewer extends HookConsumerWidget { // Used to show the placeholder during hero animations for remote videos to avoid a stutter final isVisible = useState(Platform.isIOS && asset.hasLocal); - final log = Logger('NativeVideoViewerPage'); - final isCasting = ref.watch(castProvider.select((c) => c.isCasting)); Future createSource() async { @@ -88,10 +88,18 @@ class NativeVideoViewer extends HookConsumerWidget { } final videoAsset = await ref.read(assetServiceProvider).getAsset(asset) ?? asset; + if (!context.mounted) { + return null; + } + try { if (videoAsset.hasLocal && videoAsset.livePhotoVideoId == null) { final id = videoAsset is LocalAsset ? videoAsset.id : (videoAsset as RemoteAsset).localId!; final file = await const StorageRepository().getFileForAsset(id); + if (!context.mounted) { + return null; + } + if (file == null) { throw Exception('No file found for the video'); } @@ -160,7 +168,7 @@ class NativeVideoViewer extends HookConsumerWidget { interval: const Duration(milliseconds: 100), maxWaitTime: const Duration(milliseconds: 200), ); - ref.listen(videoPlayerControlsProvider, (oldControls, newControls) async { + ref.listen(videoPlayerControlsProvider, (oldControls, newControls) { final playerController = controller.value; if (playerController == null) { return; @@ -171,28 +179,14 @@ class NativeVideoViewer extends HookConsumerWidget { return; } - final oldSeek = (oldControls?.position ?? 0) ~/ 1; - final newSeek = newControls.position ~/ 1; + final oldSeek = oldControls?.position.inMilliseconds; + final newSeek = newControls.position.inMilliseconds; if (oldSeek != newSeek || newControls.restarted) { seekDebouncer.run(() => playerController.seekTo(newSeek)); } if (oldControls?.pause != newControls.pause || newControls.restarted) { - // Make sure the last seek is complete before pausing or playing - // Otherwise, `onPlaybackPositionChanged` can receive outdated events - if (seekDebouncer.isActive) { - await seekDebouncer.drain(); - } - - try { - if (newControls.pause) { - await playerController.pause(); - } else { - await playerController.play(); - } - } catch (error) { - log.severe('Error pausing or playing video: $error'); - } + unawaited(_onPauseChange(context, playerController, seekDebouncer, newControls.pause)); } }); @@ -210,7 +204,10 @@ class NativeVideoViewer extends HookConsumerWidget { } try { - await videoController.play(); + final autoPlayVideo = AppSetting.get(Setting.autoPlayVideo); + if (autoPlayVideo) { + await videoController.play(); + } await videoController.setVolume(0.9); } catch (error) { log.severe('Error playing video: $error'); @@ -251,7 +248,7 @@ class NativeVideoViewer extends HookConsumerWidget { return; } - ref.read(videoPlaybackValueProvider.notifier).position = Duration(seconds: playbackInfo.position); + ref.read(videoPlaybackValueProvider.notifier).position = Duration(milliseconds: playbackInfo.position); // Check if the video is buffering if (playbackInfo.status == PlaybackStatus.playing) { @@ -289,7 +286,7 @@ class NativeVideoViewer extends HookConsumerWidget { ref.read(videoPlaybackValueProvider.notifier).reset(); final source = await videoSource; - if (source == null) { + if (source == null || !context.mounted) { return; } @@ -298,11 +295,13 @@ class NativeVideoViewer extends HookConsumerWidget { nc.onPlaybackReady.addListener(onPlaybackReady); nc.onPlaybackEnded.addListener(onPlaybackEnded); - nc.loadVideoSource(source).catchError((error) { - log.severe('Error loading video source: $error'); - }); + unawaited( + nc.loadVideoSource(source).catchError((error) { + log.severe('Error loading video source: $error'); + }), + ); final loopVideo = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.loopVideo); - nc.setLoop(!asset.isMotionPhoto && loopVideo); + unawaited(nc.setLoop(!asset.isMotionPhoto && loopVideo)); controller.value = nc; Timer(const Duration(milliseconds: 200), checkIfBuffering); @@ -314,6 +313,9 @@ class NativeVideoViewer extends HookConsumerWidget { removeListeners(playerController); } + if (value != null) { + isVisible.value = _isCurrentAsset(value, asset); + } final curAsset = currentAsset.value; if (curAsset == asset) { return; @@ -373,12 +375,12 @@ class NativeVideoViewer extends HookConsumerWidget { useOnAppLifecycleStateChange((_, state) async { if (state == AppLifecycleState.resumed && shouldPlayOnForeground.value) { - controller.value?.play(); + await controller.value?.play(); } else if (state == AppLifecycleState.paused) { final videoPlaying = await controller.value?.isPlaying(); if (videoPlaying ?? true) { shouldPlayOnForeground.value = true; - controller.value?.pause(); + await controller.value?.pause(); } else { shouldPlayOnForeground.value = false; } @@ -407,4 +409,31 @@ class NativeVideoViewer extends HookConsumerWidget { ], ); } + + Future _onPauseChange( + BuildContext context, + NativeVideoPlayerController controller, + Debouncer seekDebouncer, + bool isPaused, + ) async { + if (!context.mounted) { + return; + } + + // Make sure the last seek is complete before pausing or playing + // Otherwise, `onPlaybackPositionChanged` can receive outdated events + if (seekDebouncer.isActive) { + await seekDebouncer.drain(); + } + + try { + if (isPaused) { + await controller.pause(); + } else { + await controller.play(); + } + } catch (error) { + log.severe('Error pausing or playing video: $error'); + } + } } diff --git a/mobile/lib/presentation/widgets/backup/backup_toggle_button.widget.dart b/mobile/lib/presentation/widgets/backup/backup_toggle_button.widget.dart index a74c169224..8d374f74ff 100644 --- a/mobile/lib/presentation/widgets/backup/backup_toggle_button.widget.dart +++ b/mobile/lib/presentation/widgets/backup/backup_toggle_button.widget.dart @@ -65,7 +65,9 @@ class BackupToggleButtonState extends ConsumerState with Sin final uploadTasks = ref.watch(driftBackupProvider.select((state) => state.uploadItems)); - final isUploading = uploadTasks.isNotEmpty; + final isSyncing = ref.watch(driftBackupProvider.select((state) => state.isSyncing)); + + final isProcessing = uploadTasks.isNotEmpty || isSyncing; return AnimatedBuilder( animation: _animationController, @@ -129,7 +131,7 @@ class BackupToggleButtonState extends ConsumerState with Sin ], ), ), - child: isUploading + child: isProcessing ? const SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2)) : Icon(Icons.cloud_upload_outlined, color: context.primaryColor, size: 24), ), diff --git a/mobile/lib/presentation/widgets/bottom_sheet/archive_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/archive_bottom_sheet.widget.dart index 558df4e496..f40e189e18 100644 --- a/mobile/lib/presentation/widgets/bottom_sheet/archive_bottom_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_sheet/archive_bottom_sheet.widget.dart @@ -13,6 +13,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_act import 'package:immich_mobile/presentation/widgets/action_buttons/stack_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/unarchive_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; @@ -44,6 +45,7 @@ class ArchiveBottomSheet extends ConsumerWidget { const EditLocationActionButton(source: ActionSource.timeline), const MoveToLockFolderActionButton(source: ActionSource.timeline), if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline), + if (multiselect.hasStacked) const UnStackActionButton(source: ActionSource.timeline), ], if (multiselect.hasLocal) ...[ const DeleteLocalActionButton(source: ActionSource.timeline), diff --git a/mobile/lib/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart index 0549bceb9c..2f2847543f 100644 --- a/mobile/lib/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart @@ -8,6 +8,7 @@ class BaseBottomSheet extends ConsumerStatefulWidget { final List actions; final DraggableScrollableController? controller; final List? slivers; + final Widget? footer; final double initialChildSize; final double minChildSize; final double maxChildSize; @@ -20,6 +21,7 @@ class BaseBottomSheet extends ConsumerStatefulWidget { super.key, required this.actions, this.slivers, + this.footer, this.controller, this.initialChildSize = 0.35, double? minChildSize, @@ -73,24 +75,31 @@ class _BaseDraggableScrollableSheetState extends ConsumerState elevation: 3.0, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(18))), margin: const EdgeInsets.symmetric(horizontal: 0), - child: CustomScrollView( - controller: scrollController, - slivers: [ - const SliverPersistentHeader(delegate: _DragHandleDelegate(), pinned: true), - if (widget.actions.isNotEmpty) - SliverToBoxAdapter( - child: Column( - children: [ - SizedBox( - height: 115, - child: ListView(shrinkWrap: true, scrollDirection: Axis.horizontal, children: widget.actions), + child: Column( + children: [ + Expanded( + child: CustomScrollView( + controller: scrollController, + slivers: [ + const SliverPersistentHeader(delegate: _DragHandleDelegate(), pinned: true), + if (widget.actions.isNotEmpty) + SliverToBoxAdapter( + child: Column( + children: [ + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: widget.actions), + ), + const Divider(indent: 16, endIndent: 16), + const SizedBox(height: 16), + ], + ), ), - const Divider(indent: 16, endIndent: 16), - const SizedBox(height: 16), - ], - ), + if (widget.slivers != null) ...widget.slivers!, + ], ), - if (widget.slivers != null) ...widget.slivers!, + ), + if (widget.footer != null) widget.footer!, ], ), ); diff --git a/mobile/lib/presentation/widgets/bottom_sheet/favorite_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/favorite_bottom_sheet.widget.dart index a162dbbfb2..b2502127d4 100644 --- a/mobile/lib/presentation/widgets/bottom_sheet/favorite_bottom_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_sheet/favorite_bottom_sheet.widget.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/album/album.model.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart'; @@ -13,10 +16,14 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_act import 'package:immich_mobile/presentation/widgets/action_buttons/stack_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; class FavoriteBottomSheet extends ConsumerWidget { const FavoriteBottomSheet({super.key}); @@ -26,9 +33,42 @@ class FavoriteBottomSheet extends ConsumerWidget { final multiselect = ref.watch(multiSelectProvider); final isTrashEnable = ref.watch(serverInfoProvider.select((state) => state.serverFeatures.trash)); + Future addAssetsToAlbum(RemoteAlbum album) async { + final selectedAssets = multiselect.selectedAssets; + if (selectedAssets.isEmpty) { + return; + } + + final remoteAssets = selectedAssets.whereType(); + final addedCount = await ref + .read(remoteAlbumProvider.notifier) + .addAssets(album.id, remoteAssets.map((e) => e.id).toList()); + + if (selectedAssets.length != remoteAssets.length) { + ImmichToast.show( + context: context, + msg: 'add_to_album_bottom_sheet_some_local_assets'.t(context: context), + ); + } + + if (addedCount != remoteAssets.length) { + ImmichToast.show( + context: context, + msg: 'add_to_album_bottom_sheet_already_exists'.t(args: {"album": album.name}), + ); + } else { + ImmichToast.show( + context: context, + msg: 'add_to_album_bottom_sheet_added'.t(args: {"album": album.name}), + ); + } + + ref.read(multiSelectProvider.notifier).reset(); + } + return BaseBottomSheet( - initialChildSize: 0.25, - maxChildSize: 0.4, + initialChildSize: 0.4, + maxChildSize: 0.7, shouldCloseOnMinExtent: false, actions: [ const ShareActionButton(source: ActionSource.timeline), @@ -44,12 +84,16 @@ class FavoriteBottomSheet extends ConsumerWidget { const EditLocationActionButton(source: ActionSource.timeline), const MoveToLockFolderActionButton(source: ActionSource.timeline), if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline), + if (multiselect.hasStacked) const UnStackActionButton(source: ActionSource.timeline), ], if (multiselect.hasLocal) ...[ const DeleteLocalActionButton(source: ActionSource.timeline), const UploadActionButton(source: ActionSource.timeline), ], ], + slivers: multiselect.hasRemote + ? [const AddToAlbumHeader(), AlbumSelector(onAlbumSelected: addAssetsToAlbum)] + : [], ); } } diff --git a/mobile/lib/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart index 73ebf60067..9436707c84 100644 --- a/mobile/lib/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart @@ -5,6 +5,7 @@ import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/setting.model.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/advanced_info_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart'; @@ -19,6 +20,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_b import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/stack_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; @@ -62,11 +64,19 @@ class _GeneralBottomSheetState extends ConsumerState { return; } + final remoteAssets = selectedAssets.whereType(); final addedCount = await ref .read(remoteAlbumProvider.notifier) - .addAssets(album.id, selectedAssets.map((e) => (e as RemoteAsset).id).toList()); + .addAssets(album.id, remoteAssets.map((e) => e.id).toList()); - if (addedCount != selectedAssets.length) { + if (selectedAssets.length != remoteAssets.length) { + ImmichToast.show( + context: context, + msg: 'add_to_album_bottom_sheet_some_local_assets'.t(context: context), + ); + } + + if (addedCount != remoteAssets.length) { ImmichToast.show( context: context, msg: 'add_to_album_bottom_sheet_already_exists'.tr(namedArgs: {"album": album.name}), @@ -108,15 +118,18 @@ class _GeneralBottomSheetState extends ConsumerState { const EditLocationActionButton(source: ActionSource.timeline), const MoveToLockFolderActionButton(source: ActionSource.timeline), if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline), + if (multiselect.hasStacked) const UnStackActionButton(source: ActionSource.timeline), const DeleteActionButton(source: ActionSource.timeline), ], if (multiselect.hasLocal || multiselect.hasMerged) const DeleteLocalActionButton(source: ActionSource.timeline), if (multiselect.hasLocal) const UploadActionButton(source: ActionSource.timeline), ], - slivers: [ - const AddToAlbumHeader(), - AlbumSelector(onAlbumSelected: addAssetsToAlbum, onKeyboardExpanded: onKeyboardExpand), - ], + slivers: multiselect.hasRemote + ? [ + const AddToAlbumHeader(), + AlbumSelector(onAlbumSelected: addAssetsToAlbum, onKeyboardExpanded: onKeyboardExpand), + ] + : [], ); } } diff --git a/mobile/lib/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart index 19cce3392f..ac3772a02b 100644 --- a/mobile/lib/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart @@ -1,22 +1,25 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; import 'package:immich_mobile/presentation/widgets/map/map.state.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; class MapBottomSheet extends StatelessWidget { const MapBottomSheet({super.key}); @override Widget build(BuildContext context) { - return const BaseBottomSheet( + return BaseBottomSheet( initialChildSize: 0.25, maxChildSize: 0.9, shouldCloseOnMinExtent: false, resizeOnScroll: false, actions: [], - slivers: [SliverFillRemaining(hasScrollBody: false, child: _ScopedMapTimeline())], + backgroundColor: context.themeData.colorScheme.surface, + slivers: [const SliverFillRemaining(hasScrollBody: false, child: _ScopedMapTimeline())], ); } } @@ -30,8 +33,13 @@ class _ScopedMapTimeline extends StatelessWidget { return ProviderScope( overrides: [ timelineServiceProvider.overrideWith((ref) { + final user = ref.watch(currentUserProvider); + if (user == null) { + throw Exception('User must be logged in to access archive'); + } + final bounds = ref.watch(mapStateProvider).bounds; - final timelineService = ref.watch(timelineFactoryProvider).map(bounds); + final timelineService = ref.watch(timelineFactoryProvider).map(user.id, bounds); ref.onDispose(timelineService.dispose); return timelineService; }), diff --git a/mobile/lib/presentation/widgets/bottom_sheet/remote_album_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/remote_album_bottom_sheet.widget.dart index 1dcc52f349..7db8a80af2 100644 --- a/mobile/lib/presentation/widgets/bottom_sheet/remote_album_bottom_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_sheet/remote_album_bottom_sheet.widget.dart @@ -17,12 +17,14 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_b import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/stack_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; class RemoteAlbumBottomSheet extends ConsumerStatefulWidget { @@ -52,6 +54,7 @@ class _RemoteAlbumBottomSheetState extends ConsumerState Widget build(BuildContext context) { final multiselect = ref.watch(multiSelectProvider); final isTrashEnable = ref.watch(serverInfoProvider.select((state) => state.serverFeatures.trash)); + final ownsAlbum = ref.watch(currentUserProvider)?.id == widget.album.ownerId; Future addAssetsToAlbum(RemoteAlbum album) async { final selectedAssets = multiselect.selectedAssets; @@ -92,27 +95,35 @@ class _RemoteAlbumBottomSheetState extends ConsumerState const ShareActionButton(source: ActionSource.timeline), if (multiselect.hasRemote) ...[ const ShareLinkActionButton(source: ActionSource.timeline), - const ArchiveActionButton(source: ActionSource.timeline), - const FavoriteActionButton(source: ActionSource.timeline), + + if (ownsAlbum) ...[ + const ArchiveActionButton(source: ActionSource.timeline), + const FavoriteActionButton(source: ActionSource.timeline), + ], const DownloadActionButton(source: ActionSource.timeline), - isTrashEnable - ? const TrashActionButton(source: ActionSource.timeline) - : const DeletePermanentActionButton(source: ActionSource.timeline), - const EditDateTimeActionButton(source: ActionSource.timeline), - const EditLocationActionButton(source: ActionSource.timeline), - const MoveToLockFolderActionButton(source: ActionSource.timeline), - if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline), + if (ownsAlbum) ...[ + isTrashEnable + ? const TrashActionButton(source: ActionSource.timeline) + : const DeletePermanentActionButton(source: ActionSource.timeline), + const EditDateTimeActionButton(source: ActionSource.timeline), + const EditLocationActionButton(source: ActionSource.timeline), + const MoveToLockFolderActionButton(source: ActionSource.timeline), + if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline), + if (multiselect.hasStacked) const UnStackActionButton(source: ActionSource.timeline), + ], ], if (multiselect.hasLocal) ...[ const DeleteLocalActionButton(source: ActionSource.timeline), const UploadActionButton(source: ActionSource.timeline), ], - RemoveFromAlbumActionButton(source: ActionSource.timeline, albumId: widget.album.id), - ], - slivers: [ - const AddToAlbumHeader(), - AlbumSelector(onAlbumSelected: addAssetsToAlbum, onKeyboardExpanded: onKeyboardExpand), + if (ownsAlbum) RemoveFromAlbumActionButton(source: ActionSource.timeline, albumId: widget.album.id), ], + slivers: ownsAlbum + ? [ + const AddToAlbumHeader(), + AlbumSelector(onAlbumSelected: addAssetsToAlbum, onKeyboardExpanded: onKeyboardExpand), + ] + : null, ); } } diff --git a/mobile/lib/presentation/widgets/images/image_provider.dart b/mobile/lib/presentation/widgets/images/image_provider.dart index ab5ead5ca5..e77803c206 100644 --- a/mobile/lib/presentation/widgets/images/image_provider.dart +++ b/mobile/lib/presentation/widgets/images/image_provider.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:async/async.dart'; import 'package:flutter/widgets.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; @@ -51,14 +53,14 @@ mixin CancellableImageProviderMixin on CancellableImageProvide Stream loadRequest(ImageRequest request, ImageDecoderCallback decode) async* { if (isCancelled) { this.request = null; - evict(); + unawaited(evict()); return; } try { final image = await request.load(decode); if (image == null || isCancelled) { - evict(); + unawaited(evict()); return; } yield image; @@ -123,28 +125,14 @@ ImageProvider getFullImageProvider(BaseAsset asset, {Size size = const Size(1080 return provider; } -ImageProvider getThumbnailImageProvider({BaseAsset? asset, String? remoteId, Size size = kThumbnailResolution}) { - assert(asset != null || remoteId != null, 'Either asset or remoteId must be provided'); - - if (remoteId != null) { - return RemoteThumbProvider(assetId: remoteId); - } - - if (_shouldUseLocalAsset(asset!)) { +ImageProvider? getThumbnailImageProvider(BaseAsset asset, {Size size = kThumbnailResolution}) { + if (_shouldUseLocalAsset(asset)) { final id = asset is LocalAsset ? asset.id : (asset as RemoteAsset).localId!; return LocalThumbProvider(id: id, size: size, assetType: asset.type); } - final String assetId; - if (asset is LocalAsset && asset.hasRemote) { - assetId = asset.remoteId!; - } else if (asset is RemoteAsset) { - assetId = asset.id; - } else { - throw ArgumentError("Unsupported asset type: ${asset.runtimeType}"); - } - - return RemoteThumbProvider(assetId: assetId); + final assetId = asset is RemoteAsset ? asset.id : (asset as LocalAsset).remoteId; + return assetId != null ? RemoteThumbProvider(assetId: assetId) : null; } bool _shouldUseLocalAsset(BaseAsset asset) => diff --git a/mobile/lib/presentation/widgets/images/local_image_provider.dart b/mobile/lib/presentation/widgets/images/local_image_provider.dart index f90961ea5a..c5dca57f9c 100644 --- a/mobile/lib/presentation/widgets/images/local_image_provider.dart +++ b/mobile/lib/presentation/widgets/images/local_image_provider.dart @@ -85,7 +85,7 @@ class LocalFullImageProvider extends CancellableImageProvider null, }, - imageProvider = switch (asset) { - RemoteAsset() => - asset.localId == null - ? RemoteThumbProvider(assetId: asset.id) - : LocalThumbProvider(id: asset.localId!, size: size, assetType: asset.type), - LocalAsset() => LocalThumbProvider(id: asset.id, size: size, assetType: asset.type), - _ => null, - }; + imageProvider = asset == null ? null : getThumbnailImageProvider(asset, size: size); @override State createState() => _ThumbnailState(); diff --git a/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart b/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart index cfcb7a8985..c7628cb472 100644 --- a/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart +++ b/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart @@ -16,7 +16,7 @@ class ThumbnailTile extends ConsumerWidget { this.asset, { this.size = kThumbnailResolution, this.fit = BoxFit.cover, - this.showStorageIndicator, + this.showStorageIndicator = false, this.lockSelection = false, this.heroOffset, super.key, @@ -25,7 +25,7 @@ class ThumbnailTile extends ConsumerWidget { final BaseAsset? asset; final Size size; final BoxFit fit; - final bool? showStorageIndicator; + final bool showStorageIndicator; final bool lockSelection; final int? heroOffset; @@ -42,33 +42,23 @@ class ThumbnailTile extends ConsumerWidget { multiSelectProvider.select((multiselect) => multiselect.selectedAssets.contains(asset)), ); - final borderStyle = lockSelection - ? BoxDecoration( - color: context.colorScheme.surfaceContainerHighest, - border: Border.all(color: context.colorScheme.surfaceContainerHighest, width: 6), - ) - : isSelected - ? BoxDecoration( - color: assetContainerColor, - border: Border.all(color: assetContainerColor, width: 6), - ) - : const BoxDecoration(); - - final hasStack = asset is RemoteAsset && asset.stackId != null; - final bool storageIndicator = - showStorageIndicator ?? ref.watch(settingsProvider.select((s) => s.get(Setting.showStorageIndicator))); + ref.watch(settingsProvider.select((s) => s.get(Setting.showStorageIndicator))) && showStorageIndicator; return Stack( children: [ + Container(color: lockSelection ? context.colorScheme.surfaceContainerHighest : assetContainerColor), AnimatedContainer( duration: Durations.short4, curve: Curves.decelerate, - decoration: borderStyle, - child: ClipRRect( - borderRadius: isSelected || lockSelection - ? const BorderRadius.all(Radius.circular(15.0)) - : BorderRadius.zero, + padding: EdgeInsets.all(isSelected || lockSelection ? 6 : 0), + child: TweenAnimationBuilder( + tween: Tween(begin: 0.0, end: (isSelected || lockSelection) ? 15.0 : 0.0), + duration: Durations.short4, + curve: Curves.decelerate, + builder: (context, value, child) { + return ClipRRect(borderRadius: BorderRadius.all(Radius.circular(value)), child: child); + }, child: Stack( children: [ Positioned.fill( @@ -77,21 +67,10 @@ class ThumbnailTile extends ConsumerWidget { child: Thumbnail.fromAsset(asset: asset, size: size), ), ), - if (hasStack) + if (asset != null) Align( alignment: Alignment.topRight, - child: Padding( - padding: EdgeInsets.only(right: 10.0, top: asset.isVideo ? 24.0 : 6.0), - child: const _TileOverlayIcon(Icons.burst_mode_rounded), - ), - ), - if (asset != null && asset.isVideo) - Align( - alignment: Alignment.topRight, - child: Padding( - padding: const EdgeInsets.only(right: 10.0, top: 6.0), - child: _VideoIndicator(asset.duration), - ), + child: _AssetTypeIcons(asset: asset), ), if (storageIndicator && asset != null) switch (asset.storage) { @@ -129,29 +108,36 @@ class ThumbnailTile extends ConsumerWidget { ), ), ), - if (isSelected || lockSelection) - Padding( - padding: const EdgeInsets.all(3.0), - child: Align( - alignment: Alignment.topLeft, - child: _SelectionIndicator( - isSelected: isSelected, - isLocked: lockSelection, - color: lockSelection ? context.colorScheme.surfaceContainerHighest : assetContainerColor, + TweenAnimationBuilder( + tween: Tween(begin: 0.0, end: (isSelected || lockSelection) ? 1.0 : 0.0), + duration: Durations.short4, + curve: Curves.decelerate, + builder: (context, value, child) { + return Padding( + padding: EdgeInsets.all((isSelected || lockSelection) ? value * 3.0 : 3.0), + child: Align( + alignment: Alignment.topLeft, + child: Opacity( + opacity: (isSelected || lockSelection) ? 1 : value, + child: _SelectionIndicator( + isLocked: lockSelection, + color: lockSelection ? context.colorScheme.surfaceContainerHighest : assetContainerColor, + ), + ), ), - ), - ), + ); + }, + ), ], ); } } class _SelectionIndicator extends StatelessWidget { - final bool isSelected; final bool isLocked; final Color? color; - const _SelectionIndicator({required this.isSelected, required this.isLocked, this.color}); + const _SelectionIndicator({required this.isLocked, this.color}); @override Widget build(BuildContext context) { @@ -160,13 +146,11 @@ class _SelectionIndicator extends StatelessWidget { decoration: BoxDecoration(shape: BoxShape.circle, color: color), child: const Icon(Icons.check_circle_rounded, color: Colors.grey), ); - } else if (isSelected) { + } else { return DecoratedBox( decoration: BoxDecoration(shape: BoxShape.circle, color: color), child: Icon(Icons.check_circle_rounded, color: context.primaryColor), ); - } else { - return const Icon(Icons.circle_outlined, color: Colors.white); } } } @@ -214,3 +198,34 @@ class _TileOverlayIcon extends StatelessWidget { ); } } + +class _AssetTypeIcons extends StatelessWidget { + final BaseAsset asset; + + const _AssetTypeIcons({required this.asset}); + + @override + Widget build(BuildContext context) { + final hasStack = asset is RemoteAsset && (asset as RemoteAsset).stackId != null; + final isLivePhoto = asset is RemoteAsset && asset.livePhotoVideoId != null; + + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + if (asset.isVideo) + Padding(padding: const EdgeInsets.only(right: 10.0, top: 6.0), child: _VideoIndicator(asset.duration)), + if (hasStack) + const Padding( + padding: EdgeInsets.only(right: 10.0, top: 6.0), + child: _TileOverlayIcon(Icons.burst_mode_rounded), + ), + if (isLivePhoto) + const Padding( + padding: EdgeInsets.only(right: 10.0, top: 6.0), + child: _TileOverlayIcon(Icons.motion_photos_on_rounded), + ), + ], + ); + } +} diff --git a/mobile/lib/presentation/widgets/map/map.widget.dart b/mobile/lib/presentation/widgets/map/map.widget.dart index 0c3b37a3b4..4e4ae45098 100644 --- a/mobile/lib/presentation/widgets/map/map.widget.dart +++ b/mobile/lib/presentation/widgets/map/map.widget.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:io'; +import 'dart:math'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; @@ -9,8 +10,8 @@ import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart'; -import 'package:immich_mobile/presentation/widgets/map/map_utils.dart'; import 'package:immich_mobile/presentation/widgets/map/map.state.dart'; +import 'package:immich_mobile/presentation/widgets/map/map_utils.dart'; import 'package:immich_mobile/utils/async_mutex.dart'; import 'package:immich_mobile/utils/debounce.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; @@ -114,12 +115,14 @@ class _DriftMapState extends ConsumerState { } final bounds = await controller.getVisibleRegion(); - _reloadMutex.run(() async { - if (mounted && ref.read(mapStateProvider.notifier).setBounds(bounds)) { - final markers = await ref.read(mapMarkerProvider(bounds).future); - await reloadMarkers(markers); - } - }); + unawaited( + _reloadMutex.run(() async { + if (mounted && ref.read(mapStateProvider.notifier).setBounds(bounds)) { + final markers = await ref.read(mapMarkerProvider(bounds).future); + await reloadMarkers(markers); + } + }), + ); } Future reloadMarkers(Map markers) async { @@ -147,7 +150,7 @@ class _DriftMapState extends ConsumerState { final controller = mapController; if (controller != null && location != null) { - controller.animateCamera( + await controller.animateCamera( CameraUpdate.newLatLngZoom(LatLng(location.latitude, location.longitude), MapUtils.mapZoomToAssetLevel), duration: const Duration(milliseconds: 800), ); @@ -184,9 +187,13 @@ class _Map extends StatelessWidget { initialCameraPosition: initialLocation == null ? const CameraPosition(target: LatLng(0, 0), zoom: 0) : CameraPosition(target: initialLocation, zoom: MapUtils.mapZoomToAssetLevel), + compassEnabled: false, + rotateGesturesEnabled: false, styleString: style, onMapCreated: onMapCreated, onStyleLoadedCallback: onMapReady, + attributionButtonPosition: AttributionButtonPosition.topRight, + attributionButtonMargins: Platform.isIOS ? const Point(40, 12) : const Point(40, 72), ), ), ); diff --git a/mobile/lib/presentation/widgets/map/map_utils.dart b/mobile/lib/presentation/widgets/map/map_utils.dart index 1c18fc48d6..80df5995b6 100644 --- a/mobile/lib/presentation/widgets/map/map_utils.dart +++ b/mobile/lib/presentation/widgets/map/map_utils.dart @@ -73,7 +73,7 @@ class MapUtils { try { bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled && !silent) { - showDialog(context: context, builder: (context) => _LocationServiceDisabledDialog(context)); + unawaited(showDialog(context: context, builder: (context) => _LocationServiceDisabledDialog(context))); return (null, LocationPermission.deniedForever); } diff --git a/mobile/lib/presentation/widgets/search/quick_date_picker.dart b/mobile/lib/presentation/widgets/search/quick_date_picker.dart new file mode 100644 index 0000000000..09b1cee700 --- /dev/null +++ b/mobile/lib/presentation/widgets/search/quick_date_picker.dart @@ -0,0 +1,208 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; + +sealed class DateFilterInputModel { + DateTimeRange asDateTimeRange(); + + String asHumanReadable(BuildContext context) { + // General implementation for arbitrary date and time ranges + // If date range is less than 24 hours, set the end date to the end of the day + final date = asDateTimeRange(); + if (date.end.difference(date.start).inHours < 24) { + return DateFormat.yMMMd().format(date.start.toLocal()); + } else { + return 'search_filter_date_interval'.t( + context: context, + args: { + "start": DateFormat.yMMMd().format(date.start.toLocal()), + "end": DateFormat.yMMMd().format(date.end.toLocal()), + }, + ); + } + } +} + +class RecentMonthRangeFilter extends DateFilterInputModel { + final int monthDelta; + RecentMonthRangeFilter(this.monthDelta); + + @override + DateTimeRange asDateTimeRange() { + final now = DateTime.now(); + // Note that DateTime's constructor properly handles month overflow. + final from = DateTime(now.year, now.month - monthDelta, 1); + return DateTimeRange(start: from, end: now); + } + + @override + String asHumanReadable(BuildContext context) { + return 'last_months'.t(context: context, args: {"count": monthDelta.toString()}); + } +} + +class YearFilter extends DateFilterInputModel { + final int year; + YearFilter(this.year); + + @override + DateTimeRange asDateTimeRange() { + final now = DateTime.now(); + final from = DateTime(year, 1, 1); + + if (now.year == year) { + // To not go beyond today if the user picks the current year + return DateTimeRange(start: from, end: now); + } + + final to = DateTime(year, 12, 31, 23, 59, 59); + return DateTimeRange(start: from, end: to); + } + + @override + String asHumanReadable(BuildContext context) { + return 'in_year'.tr(namedArgs: {"year": year.toString()}); + } +} + +class CustomDateFilter extends DateFilterInputModel { + final DateTime start; + final DateTime end; + + CustomDateFilter(this.start, this.end); + + factory CustomDateFilter.fromRange(DateTimeRange range) { + return CustomDateFilter(range.start, range.end); + } + + @override + DateTimeRange asDateTimeRange() { + return DateTimeRange(start: start, end: end); + } +} + +enum _QuickPickerType { last1Month, last3Months, last9Months, year, custom } + +class QuickDatePicker extends HookWidget { + QuickDatePicker({super.key, required this.currentInput, required this.onSelect, required this.onRequestPicker}) + : _selection = _selectionFromModel(currentInput), + _initialYear = _initialYearFromModel(currentInput); + + final Function() onRequestPicker; + final Function(DateFilterInputModel range) onSelect; + + final DateFilterInputModel? currentInput; + final _QuickPickerType? _selection; + final int _initialYear; + + // Generate a list of recent years from 2000 to the current year (including the current one) + final List _recentYears = List.generate(1 + DateTime.now().year - 2000, (index) { + return index + 2000; + }); + + static int _initialYearFromModel(DateFilterInputModel? model) { + return model?.asDateTimeRange().start.year ?? DateTime.now().year; + } + + static _QuickPickerType? _selectionFromModel(DateFilterInputModel? model) { + if (model is RecentMonthRangeFilter) { + return switch (model.monthDelta) { + 1 => _QuickPickerType.last1Month, + 3 => _QuickPickerType.last3Months, + 9 => _QuickPickerType.last9Months, + _ => _QuickPickerType.custom, + }; + } else if (model is YearFilter) { + return _QuickPickerType.year; + } else if (model is CustomDateFilter) { + return _QuickPickerType.custom; + } + return null; + } + + Text _monthLabel(BuildContext context, int monthsFromNow) => + const Text('last_months').t(context: context, args: {"count": monthsFromNow.toString()}); + + Widget _yearPicker(BuildContext context) { + final size = MediaQuery.of(context).size; + return Row( + children: [ + const Text("in_year_selector").tr(), + const SizedBox(width: 15), + Expanded( + child: DropdownMenu( + initialSelection: _initialYear, + menuStyle: MenuStyle(maximumSize: WidgetStateProperty.all(Size(size.width, size.height * 0.5))), + dropdownMenuEntries: _recentYears.map((e) => DropdownMenuEntry(value: e, label: e.toString())).toList(), + onSelected: (year) { + if (year == null) return; + onSelect(YearFilter(year)); + }, + ), + ), + ], + ); + } + + // We want the exact date picker to always be selectable. + // Even if it's already toggled it should always open the full date picker, RadioListTiles don't do that by default + // so we wrap it in a InkWell + Widget _exactPicker(BuildContext context) { + final hasPreviousInput = currentInput != null && currentInput is CustomDateFilter; + + return InkWell( + onTap: onRequestPicker, + child: IgnorePointer( + ignoring: true, + child: RadioListTile( + title: const Text('pick_custom_range').tr(), + subtitle: hasPreviousInput ? Text(currentInput!.asHumanReadable(context)) : null, + secondary: hasPreviousInput ? const Icon(Icons.edit) : null, + value: _QuickPickerType.custom, + toggleable: true, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), + child: Scrollbar( + // Depending on the screen size the last option might get cut off + // Add a clear visual cue that there are more options when scrolling + // When the screen size is large enough the scrollbar is hidden automatically + trackVisibility: true, + thumbVisibility: true, + child: SingleChildScrollView( + child: RadioGroup( + onChanged: (value) { + if (value == null) return; + final _ = switch (value) { + _QuickPickerType.custom => onRequestPicker(), + _QuickPickerType.last1Month => onSelect(RecentMonthRangeFilter(1)), + _QuickPickerType.last3Months => onSelect(RecentMonthRangeFilter(3)), + _QuickPickerType.last9Months => onSelect(RecentMonthRangeFilter(9)), + // When a year is selected the combobox triggers onSelect() on its own. + // Here we handle the radio button being selected which can only ever be the initial year + _QuickPickerType.year => onSelect(YearFilter(_initialYear)), + }; + }, + groupValue: _selection, + child: Column( + children: [ + RadioListTile(title: _monthLabel(context, 1), value: _QuickPickerType.last1Month, toggleable: true), + RadioListTile(title: _monthLabel(context, 3), value: _QuickPickerType.last3Months, toggleable: true), + RadioListTile(title: _monthLabel(context, 9), value: _QuickPickerType.last9Months, toggleable: true), + RadioListTile(title: _yearPicker(context), value: _QuickPickerType.year, toggleable: true), + _exactPicker(context), + ], + ), + ), + ), + ), + ); + } +} diff --git a/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart b/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart index 8121bb76d3..b879b33f68 100644 --- a/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart +++ b/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:math' as math; import 'package:auto_route/auto_route.dart'; @@ -15,8 +16,9 @@ import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart' import 'package:immich_mobile/presentation/widgets/timeline/timeline_drag_region.dart'; import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart'; import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/routing/router.dart'; @@ -156,11 +158,14 @@ class _AssetTileWidget extends ConsumerWidget { await ref.read(timelineServiceProvider).loadAssets(assetIndex, 1); ref.read(isPlayingMotionVideoProvider.notifier).playing = false; AssetViewer.setAsset(ref, asset); - ctx.pushRoute( - AssetViewerRoute( - initialIndex: assetIndex, - timelineService: ref.read(timelineServiceProvider), - heroOffset: heroOffset, + unawaited( + ctx.pushRoute( + AssetViewerRoute( + initialIndex: assetIndex, + timelineService: ref.read(timelineServiceProvider), + heroOffset: heroOffset, + currentAlbum: ref.read(currentRemoteAlbumProvider), + ), ), ); } diff --git a/mobile/lib/presentation/widgets/timeline/timeline.state.dart b/mobile/lib/presentation/widgets/timeline/timeline.state.dart index b3aec23f7f..1e1d4130f7 100644 --- a/mobile/lib/presentation/widgets/timeline/timeline.state.dart +++ b/mobile/lib/presentation/widgets/timeline/timeline.state.dart @@ -14,7 +14,7 @@ class TimelineArgs { final double maxHeight; final double spacing; final int columnCount; - final bool? showStorageIndicator; + final bool showStorageIndicator; final bool withStack; final GroupAssetsBy? groupBy; @@ -23,7 +23,7 @@ class TimelineArgs { required this.maxHeight, this.spacing = kTimelineSpacing, this.columnCount = kTimelineColumnCount, - this.showStorageIndicator, + this.showStorageIndicator = false, this.withStack = false, this.groupBy, }); diff --git a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart index c38116c64e..70dd15bf7f 100644 --- a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart +++ b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart @@ -36,10 +36,11 @@ class Timeline extends StatelessWidget { this.showStorageIndicator = false, this.withStack = false, this.appBar = const ImmichSliverAppBar(floating: true, pinned: false, snap: false), - this.bottomSheet = const GeneralBottomSheet(minChildSize: 0.18), + this.bottomSheet = const GeneralBottomSheet(minChildSize: 0.23), this.groupBy, this.withScrubber = true, this.snapToMonth = true, + this.initialScrollOffset, }); final Widget? topSliverWidget; @@ -51,6 +52,7 @@ class Timeline extends StatelessWidget { final GroupAssetsBy? groupBy; final bool withScrubber; final bool snapToMonth; + final double? initialScrollOffset; @override Widget build(BuildContext context) { @@ -78,6 +80,7 @@ class Timeline extends StatelessWidget { bottomSheet: bottomSheet, withScrubber: withScrubber, snapToMonth: snapToMonth, + initialScrollOffset: initialScrollOffset, ), ), ), @@ -93,6 +96,7 @@ class _SliverTimeline extends ConsumerStatefulWidget { this.bottomSheet, this.withScrubber = true, this.snapToMonth = true, + this.initialScrollOffset, }); final Widget? topSliverWidget; @@ -101,6 +105,7 @@ class _SliverTimeline extends ConsumerStatefulWidget { final Widget? bottomSheet; final bool withScrubber; final bool snapToMonth; + final double? initialScrollOffset; @override ConsumerState createState() => _SliverTimelineState(); @@ -124,7 +129,10 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> { @override void initState() { super.initState(); - _scrollController = ScrollController(onAttach: _restoreScalePosition); + _scrollController = ScrollController( + initialScrollOffset: widget.initialScrollOffset ?? 0.0, + onAttach: _restoreScalePosition, + ); _eventSubscription = EventStream.shared.listen(_onEvent); final currentTilesPerRow = ref.read(settingsProvider).get(Setting.tilesPerRow); @@ -212,11 +220,16 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> { if (fallbackSegment != null) { // Scroll to the segment with a small offset to show the header final targetOffset = fallbackSegment.startOffset - 50; - _scrollController.animateTo( - targetOffset.clamp(0.0, _scrollController.position.maxScrollExtent), - duration: const Duration(milliseconds: 500), - curve: Curves.easeInOut, - ); + ref.read(timelineStateProvider.notifier).setScrubbing(true); + _scrollController + .animateTo( + targetOffset.clamp(0.0, _scrollController.position.maxScrollExtent), + duration: const Duration(milliseconds: 500), + curve: Curves.easeInOut, + ) + .whenComplete(() => ref.read(timelineStateProvider.notifier).setScrubbing(false)); + } else { + ref.read(timelineStateProvider.notifier).setScrubbing(false); } }); } diff --git a/mobile/lib/providers/activity.provider.dart b/mobile/lib/providers/activity.provider.dart index a867a5a281..5e0e71d85d 100644 --- a/mobile/lib/providers/activity.provider.dart +++ b/mobile/lib/providers/activity.provider.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:immich_mobile/models/activities/activity.model.dart'; import 'package:immich_mobile/providers/activity_service.provider.dart'; import 'package:immich_mobile/providers/activity_statistics.provider.dart'; @@ -16,13 +17,20 @@ class AlbumActivity extends _$AlbumActivity { Future removeActivity(String id) async { if (await ref.watch(activityServiceProvider).removeActivity(id)) { - final activities = state.valueOrNull ?? []; - final removedActivity = activities.firstWhere((a) => a.id == id); - activities.remove(removedActivity); - state = AsyncData(activities); - // Decrement activity count only for comments + final removedActivity = _removeFromState(id); + if (removedActivity == null) { + return; + } + + if (assetId != null) { + ref.read(albumActivityProvider(albumId).notifier)._removeFromState(id); + } + if (removedActivity.type == ActivityType.comment) { ref.watch(activityStatisticsProvider(albumId, assetId).notifier).removeActivity(); + if (assetId != null) { + ref.watch(activityStatisticsProvider(albumId).notifier).removeActivity(); + } } } } @@ -30,8 +38,10 @@ class AlbumActivity extends _$AlbumActivity { Future addLike() async { final activity = await ref.watch(activityServiceProvider).addActivity(albumId, ActivityType.like, assetId: assetId); if (activity.hasValue) { - final activities = state.asData?.value ?? []; - state = AsyncData([...activities, activity.requireValue]); + _addToState(activity.requireValue); + if (assetId != null) { + ref.read(albumActivityProvider(albumId).notifier)._addToState(activity.requireValue); + } } } @@ -41,8 +51,10 @@ class AlbumActivity extends _$AlbumActivity { .addActivity(albumId, ActivityType.comment, assetId: assetId, comment: comment); if (activity.hasValue) { - final activities = state.valueOrNull ?? []; - state = AsyncData([...activities, activity.requireValue]); + _addToState(activity.requireValue); + if (assetId != null) { + ref.read(albumActivityProvider(albumId).notifier)._addToState(activity.requireValue); + } ref.watch(activityStatisticsProvider(albumId, assetId).notifier).addActivity(); // The previous addActivity call would increase the count of an asset if assetId != null // To also increase the activity count of the album, calling it once again with assetId set to null @@ -51,6 +63,29 @@ class AlbumActivity extends _$AlbumActivity { } } } + + void _addToState(Activity activity) { + final activities = state.valueOrNull ?? []; + if (activities.any((a) => a.id == activity.id)) { + return; + } + state = AsyncData([...activities, activity]); + } + + Activity? _removeFromState(String id) { + final activities = state.valueOrNull; + if (activities == null) { + return null; + } + final activity = activities.firstWhereOrNull((a) => a.id == id); + if (activity == null) { + return null; + } + + final updated = [...activities]..remove(activity); + state = AsyncData(updated); + return activity; + } } /// Mock class for testing diff --git a/mobile/lib/providers/activity.provider.g.dart b/mobile/lib/providers/activity.provider.g.dart index dc927795f8..6ca99e4f72 100644 --- a/mobile/lib/providers/activity.provider.g.dart +++ b/mobile/lib/providers/activity.provider.g.dart @@ -6,7 +6,7 @@ part of 'activity.provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$albumActivityHash() => r'3b0d7acee4d41c84b3f220784c3b904c83f836e6'; +String _$albumActivityHash() => r'154e8ae98da3efc142369eae46d4005468fd67da'; /// Copied from Dart SDK class _SystemHash { diff --git a/mobile/lib/providers/activity_service.provider.dart b/mobile/lib/providers/activity_service.provider.dart index a7fd0715f8..f17617bced 100644 --- a/mobile/lib/providers/activity_service.provider.dart +++ b/mobile/lib/providers/activity_service.provider.dart @@ -1,4 +1,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/repositories/activity_api.repository.dart'; import 'package:immich_mobile/services/activity.service.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -6,4 +8,8 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'activity_service.provider.g.dart'; @riverpod -ActivityService activityService(Ref ref) => ActivityService(ref.watch(activityApiRepositoryProvider)); +ActivityService activityService(Ref ref) => ActivityService( + ref.watch(activityApiRepositoryProvider), + ref.watch(timelineFactoryProvider), + ref.watch(assetServiceProvider), +); diff --git a/mobile/lib/providers/activity_service.provider.g.dart b/mobile/lib/providers/activity_service.provider.g.dart index 2bf160c487..4641738fc4 100644 --- a/mobile/lib/providers/activity_service.provider.g.dart +++ b/mobile/lib/providers/activity_service.provider.g.dart @@ -6,7 +6,7 @@ part of 'activity_service.provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$activityServiceHash() => r'ce775779787588defe1e76406e09a9c109470310'; +String _$activityServiceHash() => r'3ce0eb33948138057cc63f07a7598047b99e7599'; /// See also [activityService]. @ProviderFor(activityService) diff --git a/mobile/lib/providers/app_life_cycle.provider.dart b/mobile/lib/providers/app_life_cycle.provider.dart index ec6495440a..6d0c0acb0d 100644 --- a/mobile/lib/providers/app_life_cycle.provider.dart +++ b/mobile/lib/providers/app_life_cycle.provider.dart @@ -148,17 +148,21 @@ class AppLifeCycleNotifier extends StateNotifier { final isAlbumLinkedSyncEnable = _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.syncAlbums); try { + bool syncSuccess = false; await Future.wait([ _safeRun(backgroundManager.syncLocal(), "syncLocal"), - _safeRun(backgroundManager.syncRemote(), "syncRemote"), - ]); - - await Future.wait([ - _safeRun(backgroundManager.hashAssets(), "hashAssets").then((_) { - _resumeBackup(); - }), - _resumeBackup(), + _safeRun(backgroundManager.syncRemote().then((success) => syncSuccess = success), "syncRemote"), ]); + if (syncSuccess) { + await Future.wait([ + _safeRun(backgroundManager.hashAssets(), "hashAssets").then((_) { + _resumeBackup(); + }), + _resumeBackup(), + ]); + } else { + await _safeRun(backgroundManager.hashAssets(), "hashAssets"); + } if (isAlbumLinkedSyncEnable) { await _safeRun(backgroundManager.syncLinkedAlbum(), "syncLinkedAlbum"); @@ -238,7 +242,7 @@ class AppLifeCycleNotifier extends StateNotifier { } try { - LogService.I.flush(); + await LogService.I.flush(); } catch (_) {} } @@ -251,7 +255,7 @@ class AppLifeCycleNotifier extends StateNotifier { // Flush logs before closing database try { - LogService.I.flush(); + await LogService.I.flush(); } catch (_) {} // Close Isar database safely diff --git a/mobile/lib/providers/asset.provider.dart b/mobile/lib/providers/asset.provider.dart index 75635950ff..d5a4e42b74 100644 --- a/mobile/lib/providers/asset.provider.dart +++ b/mobile/lib/providers/asset.provider.dart @@ -98,7 +98,7 @@ class AssetNotifier extends StateNotifier { Future onNewAssetUploaded(Asset newAsset) async { // eTag on device is not valid after partially modifying the assets - Store.delete(StoreKey.assetETag); + await Store.delete(StoreKey.assetETag); await _syncService.syncNewAssetToDb(newAsset); } diff --git a/mobile/lib/providers/asset_viewer/download.provider.dart b/mobile/lib/providers/asset_viewer/download.provider.dart index 36b935abe7..a461d5766a 100644 --- a/mobile/lib/providers/asset_viewer/download.provider.dart +++ b/mobile/lib/providers/asset_viewer/download.provider.dart @@ -1,14 +1,16 @@ +import 'dart:async'; + import 'package:background_downloader/background_downloader.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/models/download/download_state.model.dart'; import 'package:immich_mobile/models/download/livephotos_medatada.model.dart'; import 'package:immich_mobile/services/album.service.dart'; import 'package:immich_mobile/services/download.service.dart'; -import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/services/share.service.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:immich_mobile/widgets/common/share_dialog.dart'; @@ -159,24 +161,26 @@ class DownloadStateNotifier extends StateNotifier { } void shareAsset(Asset asset, BuildContext context) async { - showDialog( - context: context, - builder: (BuildContext buildContext) { - _shareService.shareAsset(asset, context).then((bool status) { - if (!status) { - ImmichToast.show( - context: context, - msg: 'image_viewer_page_state_provider_share_error'.tr(), - toastType: ToastType.error, - gravity: ToastGravity.BOTTOM, - ); - } - buildContext.pop(); - }); - return const ShareDialog(); - }, - barrierDismissible: false, - useRootNavigator: false, + unawaited( + showDialog( + context: context, + builder: (BuildContext buildContext) { + _shareService.shareAsset(asset, context).then((bool status) { + if (!status) { + ImmichToast.show( + context: context, + msg: 'image_viewer_page_state_provider_share_error'.tr(), + toastType: ToastType.error, + gravity: ToastGravity.BOTTOM, + ); + } + buildContext.pop(); + }); + return const ShareDialog(); + }, + barrierDismissible: false, + useRootNavigator: false, + ), ); } } diff --git a/mobile/lib/providers/asset_viewer/share_intent_upload.provider.dart b/mobile/lib/providers/asset_viewer/share_intent_upload.provider.dart index 7b2ab5b27a..0f9c32b410 100644 --- a/mobile/lib/providers/asset_viewer/share_intent_upload.provider.dart +++ b/mobile/lib/providers/asset_viewer/share_intent_upload.provider.dart @@ -104,7 +104,7 @@ class ShareIntentUploadStateNotifier extends StateNotifier upload(File file) async { final task = await _buildUploadTask(hash(file.path).toString(), file); - _uploadService.enqueueTasks([task]); + await _uploadService.enqueueTasks([task]); } Future _buildUploadTask(String id, File file, {Map? fields}) async { diff --git a/mobile/lib/providers/asset_viewer/video_player_controls_provider.dart b/mobile/lib/providers/asset_viewer/video_player_controls_provider.dart index 3cfc2e2f6f..44740268db 100644 --- a/mobile/lib/providers/asset_viewer/video_player_controls_provider.dart +++ b/mobile/lib/providers/asset_viewer/video_player_controls_provider.dart @@ -4,7 +4,7 @@ import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider class VideoPlaybackControls { const VideoPlaybackControls({required this.position, required this.pause, this.restarted = false}); - final double position; + final Duration position; final bool pause; final bool restarted; } @@ -13,7 +13,7 @@ final videoPlayerControlsProvider = StateNotifierProvider { VideoPlayerControls(this.ref) : super(videoPlayerControlsDefault); @@ -30,10 +30,10 @@ class VideoPlayerControls extends StateNotifier { state = videoPlayerControlsDefault; } - double get position => state.position; + Duration get position => state.position; bool get paused => state.pause; - set position(double value) { + set position(Duration value) { if (state.position == value) { return; } @@ -62,7 +62,7 @@ class VideoPlayerControls extends StateNotifier { } void restart() { - state = const VideoPlaybackControls(position: 0, pause: false, restarted: true); + state = const VideoPlaybackControls(position: Duration.zero, pause: false, restarted: true); ref.read(videoPlaybackValueProvider.notifier).value = ref .read(videoPlaybackValueProvider.notifier) .value diff --git a/mobile/lib/providers/asset_viewer/video_player_value_provider.dart b/mobile/lib/providers/asset_viewer/video_player_value_provider.dart index c478ddd6f5..31b0f4656e 100644 --- a/mobile/lib/providers/asset_viewer/video_player_value_provider.dart +++ b/mobile/lib/providers/asset_viewer/video_player_value_provider.dart @@ -33,8 +33,8 @@ class VideoPlaybackValue { }; return VideoPlaybackValue( - position: Duration(seconds: playbackInfo.position), - duration: Duration(seconds: videoInfo.duration), + position: Duration(milliseconds: playbackInfo.position), + duration: Duration(milliseconds: videoInfo.duration), state: status, volume: playbackInfo.volume, ); diff --git a/mobile/lib/providers/background_sync.provider.dart b/mobile/lib/providers/background_sync.provider.dart index e6e83b64df..a61cd93022 100644 --- a/mobile/lib/providers/background_sync.provider.dart +++ b/mobile/lib/providers/background_sync.provider.dart @@ -1,12 +1,21 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/utils/background_sync.dart'; +import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; import 'package:immich_mobile/providers/sync_status.provider.dart'; final backgroundSyncProvider = Provider((ref) { final syncStatusNotifier = ref.read(syncStatusProvider.notifier); + final backupProvider = ref.read(driftBackupProvider.notifier); + final manager = BackgroundSyncManager( - onRemoteSyncStart: syncStatusNotifier.startRemoteSync, - onRemoteSyncComplete: syncStatusNotifier.completeRemoteSync, + onRemoteSyncStart: () { + syncStatusNotifier.startRemoteSync(); + backupProvider.updateError(BackupError.none); + }, + onRemoteSyncComplete: (isSuccess) { + syncStatusNotifier.completeRemoteSync(); + backupProvider.updateError(isSuccess == true ? BackupError.none : BackupError.syncFailed); + }, onRemoteSyncError: syncStatusNotifier.errorRemoteSync, onLocalSyncStart: syncStatusNotifier.startLocalSync, onLocalSyncComplete: syncStatusNotifier.completeLocalSync, diff --git a/mobile/lib/providers/backup/backup.provider.dart b/mobile/lib/providers/backup/backup.provider.dart index 03666466ff..9eb01b6109 100644 --- a/mobile/lib/providers/backup/backup.provider.dart +++ b/mobile/lib/providers/backup/backup.provider.dart @@ -380,7 +380,7 @@ class BackupNotifier extends StateNotifier { state = state.copyWith(backgroundBackup: isEnabled); if (isEnabled != Store.get(StoreKey.backgroundBackup, !isEnabled)) { - Store.put(StoreKey.backgroundBackup, isEnabled); + await Store.put(StoreKey.backgroundBackup, isEnabled); } if (state.backupProgress != BackUpProgressEnum.inBackground) { @@ -474,7 +474,7 @@ class BackupNotifier extends StateNotifier { ); await notifyBackgroundServiceCanRun(); } else { - openAppSettings(); + await openAppSettings(); } } @@ -533,10 +533,10 @@ class BackupNotifier extends StateNotifier { progressInFileSpeedUpdateTime: DateTime.now(), progressInFileSpeedUpdateSentBytes: 0, ); - _updatePersistentAlbumsSelection(); + await _updatePersistentAlbumsSelection(); } - updateDiskInfo(); + await updateDiskInfo(); } void _onUploadProgress(int sent, int total) { diff --git a/mobile/lib/providers/backup/backup_verification.provider.dart b/mobile/lib/providers/backup/backup_verification.provider.dart index da4253576b..50270e87ca 100644 --- a/mobile/lib/providers/backup/backup_verification.provider.dart +++ b/mobile/lib/providers/backup/backup_verification.provider.dart @@ -2,10 +2,10 @@ import 'dart:async'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; -import 'package:immich_mobile/providers/backup/backup.provider.dart'; -import 'package:immich_mobile/services/backup_verification.service.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/providers/asset.provider.dart'; +import 'package:immich_mobile/providers/backup/backup.provider.dart'; +import 'package:immich_mobile/services/backup_verification.service.dart'; import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -44,7 +44,7 @@ class BackupVerification extends _$BackupVerification { } return; } - WakelockPlus.enable(); + unawaited(WakelockPlus.enable()); const limit = 100; final toDelete = await ref.read(backupVerificationServiceProvider).findWronglyBackedUpAssets(limit: limit); @@ -73,7 +73,7 @@ class BackupVerification extends _$BackupVerification { } } } finally { - WakelockPlus.disable(); + unawaited(WakelockPlus.disable()); state = false; } } diff --git a/mobile/lib/providers/backup/backup_verification.provider.g.dart b/mobile/lib/providers/backup/backup_verification.provider.g.dart index 727e06a12c..13f6819fa7 100644 --- a/mobile/lib/providers/backup/backup_verification.provider.g.dart +++ b/mobile/lib/providers/backup/backup_verification.provider.g.dart @@ -7,7 +7,7 @@ part of 'backup_verification.provider.dart'; // ************************************************************************** String _$backupVerificationHash() => - r'b204e43ab575d5fa5b2ee663297f32bcee9074f5'; + r'b4b34909ed1af3f28877ea457d53a4a18b6417f8'; /// See also [BackupVerification]. @ProviderFor(BackupVerification) diff --git a/mobile/lib/providers/backup/drift_backup.provider.dart b/mobile/lib/providers/backup/drift_backup.provider.dart index 1ce14e5204..f52fc654f2 100644 --- a/mobile/lib/providers/backup/drift_backup.provider.dart +++ b/mobile/lib/providers/backup/drift_backup.provider.dart @@ -1,6 +1,5 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:async'; -import 'dart:convert'; import 'package:background_downloader/background_downloader.dart'; import 'package:collection/collection.dart'; @@ -8,12 +7,13 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/extensions/string_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/services/upload.service.dart'; -import 'package:logging/logging.dart'; import 'package:immich_mobile/utils/debug_print.dart'; +import 'package:logging/logging.dart'; class EnqueueStatus { final int enqueueCount; @@ -36,6 +36,7 @@ class DriftUploadStatus { final int fileSize; final String networkSpeedAsString; final bool? isFailed; + final String? error; const DriftUploadStatus({ required this.taskId, @@ -44,6 +45,7 @@ class DriftUploadStatus { required this.fileSize, required this.networkSpeedAsString, this.isFailed, + this.error, }); DriftUploadStatus copyWith({ @@ -53,6 +55,7 @@ class DriftUploadStatus { int? fileSize, String? networkSpeedAsString, bool? isFailed, + String? error, }) { return DriftUploadStatus( taskId: taskId ?? this.taskId, @@ -61,12 +64,13 @@ class DriftUploadStatus { fileSize: fileSize ?? this.fileSize, networkSpeedAsString: networkSpeedAsString ?? this.networkSpeedAsString, isFailed: isFailed ?? this.isFailed, + error: error ?? this.error, ); } @override String toString() { - return 'DriftUploadStatus(taskId: $taskId, filename: $filename, progress: $progress, fileSize: $fileSize, networkSpeedAsString: $networkSpeedAsString, isFailed: $isFailed)'; + return 'DriftUploadStatus(taskId: $taskId, filename: $filename, progress: $progress, fileSize: $fileSize, networkSpeedAsString: $networkSpeedAsString, isFailed: $isFailed, error: $error)'; } @override @@ -78,7 +82,8 @@ class DriftUploadStatus { other.progress == progress && other.fileSize == fileSize && other.networkSpeedAsString == networkSpeedAsString && - other.isFailed == isFailed; + other.isFailed == isFailed && + other.error == error; } @override @@ -88,37 +93,13 @@ class DriftUploadStatus { progress.hashCode ^ fileSize.hashCode ^ networkSpeedAsString.hashCode ^ - isFailed.hashCode; + isFailed.hashCode ^ + error.hashCode; } - - Map toMap() { - return { - 'taskId': taskId, - 'filename': filename, - 'progress': progress, - 'fileSize': fileSize, - 'networkSpeedAsString': networkSpeedAsString, - 'isFailed': isFailed, - }; - } - - factory DriftUploadStatus.fromMap(Map map) { - return DriftUploadStatus( - taskId: map['taskId'] as String, - filename: map['filename'] as String, - progress: map['progress'] as double, - fileSize: map['fileSize'] as int, - networkSpeedAsString: map['networkSpeedAsString'] as String, - isFailed: map['isFailed'] != null ? map['isFailed'] as bool : null, - ); - } - - String toJson() => json.encode(toMap()); - - factory DriftUploadStatus.fromJson(String source) => - DriftUploadStatus.fromMap(json.decode(source) as Map); } +enum BackupError { none, syncFailed } + class DriftBackupState { final int totalCount; final int backupCount; @@ -128,7 +109,9 @@ class DriftBackupState { final int enqueueCount; final int enqueueTotalCount; + final bool isSyncing; final bool isCanceling; + final BackupError error; final Map uploadItems; @@ -140,7 +123,9 @@ class DriftBackupState { required this.enqueueCount, required this.enqueueTotalCount, required this.isCanceling, + required this.isSyncing, required this.uploadItems, + this.error = BackupError.none, }); DriftBackupState copyWith({ @@ -151,7 +136,9 @@ class DriftBackupState { int? enqueueCount, int? enqueueTotalCount, bool? isCanceling, + bool? isSyncing, Map? uploadItems, + BackupError? error, }) { return DriftBackupState( totalCount: totalCount ?? this.totalCount, @@ -161,13 +148,15 @@ class DriftBackupState { enqueueCount: enqueueCount ?? this.enqueueCount, enqueueTotalCount: enqueueTotalCount ?? this.enqueueTotalCount, isCanceling: isCanceling ?? this.isCanceling, + isSyncing: isSyncing ?? this.isSyncing, uploadItems: uploadItems ?? this.uploadItems, + error: error ?? this.error, ); } @override String toString() { - return 'DriftBackupState(totalCount: $totalCount, backupCount: $backupCount, remainderCount: $remainderCount, processingCount: $processingCount, enqueueCount: $enqueueCount, enqueueTotalCount: $enqueueTotalCount, isCanceling: $isCanceling, uploadItems: $uploadItems)'; + return 'DriftBackupState(totalCount: $totalCount, backupCount: $backupCount, remainderCount: $remainderCount, processingCount: $processingCount, enqueueCount: $enqueueCount, enqueueTotalCount: $enqueueTotalCount, isCanceling: $isCanceling, isSyncing: $isSyncing, uploadItems: $uploadItems, error: $error)'; } @override @@ -182,7 +171,9 @@ class DriftBackupState { other.enqueueCount == enqueueCount && other.enqueueTotalCount == enqueueTotalCount && other.isCanceling == isCanceling && - mapEquals(other.uploadItems, uploadItems); + other.isSyncing == isSyncing && + mapEquals(other.uploadItems, uploadItems) && + other.error == error; } @override @@ -194,7 +185,9 @@ class DriftBackupState { enqueueCount.hashCode ^ enqueueTotalCount.hashCode ^ isCanceling.hashCode ^ - uploadItems.hashCode; + isSyncing.hashCode ^ + uploadItems.hashCode ^ + error.hashCode; } } @@ -213,7 +206,9 @@ class DriftBackupNotifier extends StateNotifier { enqueueCount: 0, enqueueTotalCount: 0, isCanceling: false, + isSyncing: false, uploadItems: {}, + error: BackupError.none, ), ) { { @@ -266,7 +261,24 @@ class DriftBackupNotifier extends StateNotifier { return; } - state = state.copyWith(uploadItems: {...state.uploadItems, taskId: currentItem.copyWith(isFailed: true)}); + String? error; + final exception = update.exception; + if (exception != null && exception is TaskHttpException) { + final message = tryJsonDecode(exception.description)?['message'] as String?; + if (message != null) { + final responseCode = exception.httpResponseCode; + error = "${exception.exceptionType}, response code $responseCode: $message"; + } + } + error ??= update.exception?.toString(); + + state = state.copyWith( + uploadItems: { + ...state.uploadItems, + taskId: currentItem.copyWith(isFailed: true, error: error), + }, + ); + _logger.fine("Upload failed for taskId: $taskId, exception: ${update.exception}"); break; case TaskStatus.canceled: @@ -330,7 +342,16 @@ class DriftBackupNotifier extends StateNotifier { ); } + void updateError(BackupError error) async { + state = state.copyWith(error: error); + } + + void updateSyncing(bool isSyncing) async { + state = state.copyWith(isSyncing: isSyncing); + } + Future startBackup(String userId) { + state = state.copyWith(error: BackupError.none); return _uploadService.startBackup(userId, _updateEnqueueCount); } @@ -340,7 +361,7 @@ class DriftBackupNotifier extends StateNotifier { Future cancel() async { dPrint(() => "Canceling backup tasks..."); - state = state.copyWith(enqueueCount: 0, enqueueTotalCount: 0, isCanceling: true); + state = state.copyWith(enqueueCount: 0, enqueueTotalCount: 0, isCanceling: true, error: BackupError.none); final activeTaskCount = await _uploadService.cancelBackup(); @@ -356,6 +377,7 @@ class DriftBackupNotifier extends StateNotifier { Future handleBackupResume(String userId) async { _logger.info("Resuming backup tasks..."); + state = state.copyWith(error: BackupError.none); final tasks = await _uploadService.getActiveTasks(kBackupGroup); _logger.info("Found ${tasks.length} tasks"); @@ -383,7 +405,7 @@ final driftBackupCandidateProvider = FutureProvider.autoDispose return []; } - return ref.read(backupRepositoryProvider).getCandidates(user.id); + return ref.read(backupRepositoryProvider).getCandidates(user.id, onlyHashed: false); }); final driftCandidateBackupAlbumInfoProvider = FutureProvider.autoDispose.family, String>(( diff --git a/mobile/lib/providers/backup/manual_upload.provider.dart b/mobile/lib/providers/backup/manual_upload.provider.dart index bfc079bfa3..6ad8730356 100644 --- a/mobile/lib/providers/backup/manual_upload.provider.dart +++ b/mobile/lib/providers/backup/manual_upload.provider.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:cancellation_token_http/http.dart'; @@ -26,11 +27,11 @@ import 'package:immich_mobile/services/backup.service.dart'; import 'package:immich_mobile/services/backup_album.service.dart'; import 'package:immich_mobile/services/local_notification.service.dart'; import 'package:immich_mobile/utils/backup_progress.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:logging/logging.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:photo_manager/photo_manager.dart' show PMProgressHandler; -import 'package:immich_mobile/utils/debug_print.dart'; final manualUploadProvider = StateNotifierProvider((ref) { return ManualUploadNotifier( @@ -294,7 +295,7 @@ class ManualUploadNotifier extends StateNotifier { ); } } else { - openAppSettings(); + unawaited(openAppSettings()); dPrint(() => "[_startUpload] Do not have permission to the gallery"); } } catch (e) { diff --git a/mobile/lib/providers/image/immich_local_image_provider.dart b/mobile/lib/providers/image/immich_local_image_provider.dart index 8c46c52906..b9e09eb357 100644 --- a/mobile/lib/providers/image/immich_local_image_provider.dart +++ b/mobile/lib/providers/image/immich_local_image_provider.dart @@ -3,7 +3,6 @@ import 'dart:io'; import 'dart:ui' as ui; import 'package:cached_network_image/cached_network_image.dart'; - import 'package:flutter/foundation.dart'; import 'package:flutter/painting.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; @@ -77,7 +76,7 @@ class ImmichLocalImageProvider extends ImageProvider { } catch (error, stack) { log.severe('Error loading local image ${asset.fileName}', error, stack); } finally { - chunkEvents.close(); + unawaited(chunkEvents.close()); } } diff --git a/mobile/lib/providers/infrastructure/action.provider.dart b/mobile/lib/providers/infrastructure/action.provider.dart index 9a343aa358..9467f63483 100644 --- a/mobile/lib/providers/infrastructure/action.provider.dart +++ b/mobile/lib/providers/infrastructure/action.provider.dart @@ -1,9 +1,14 @@ +import 'dart:async'; + import 'package:auto_route/auto_route.dart'; import 'package:background_downloader/background_downloader.dart'; import 'package:flutter/material.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/services/asset.service.dart'; import 'package:immich_mobile/models/download/livephotos_medatada.model.dart'; +import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart'; +import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; @@ -12,6 +17,7 @@ import 'package:immich_mobile/services/action.service.dart'; import 'package:immich_mobile/services/download.service.dart'; import 'package:immich_mobile/services/timeline.service.dart'; import 'package:immich_mobile/services/upload.service.dart'; +import 'package:immich_mobile/widgets/asset_grid/delete_dialog.dart'; import 'package:logging/logging.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -36,6 +42,7 @@ class ActionNotifier extends Notifier { late ActionService _service; late UploadService _uploadService; late DownloadService _downloadService; + late AssetService _assetService; ActionNotifier() : super(); @@ -43,6 +50,7 @@ class ActionNotifier extends Notifier { void build() { _uploadService = ref.watch(uploadServiceProvider); _service = ref.watch(actionServiceProvider); + _assetService = ref.watch(assetServiceProvider); _downloadService = ref.watch(downloadServiceProvider); _downloadService.onImageDownloadStatus = _downloadImageCallback; _downloadService.onVideoDownloadStatus = _downloadVideoCallback; @@ -64,7 +72,7 @@ class ActionNotifier extends Notifier { void _downloadLivePhotoCallback(TaskStatusUpdate update) async { if (update.status == TaskStatus.complete) { final livePhotosId = LivePhotosMetadata.fromJson(update.task.metaData).id; - _downloadService.saveLivePhotos(update.task, livePhotosId); + unawaited(_downloadService.saveLivePhotos(update.task, livePhotosId)); } } @@ -72,11 +80,14 @@ class ActionNotifier extends Notifier { return _getAssets(source).whereType().toIds().toList(growable: false); } - List _getLocalIdsForSource(ActionSource source) { + List _getLocalIdsForSource(ActionSource source, {bool ignoreLocalOnly = false}) { final Set assets = _getAssets(source); final List localIds = []; for (final asset in assets) { + if (ignoreLocalOnly && asset.storage != AssetState.merged) { + continue; + } if (asset is LocalAsset) { localIds.add(asset.id); } else if (asset is RemoteAsset && asset.localId != null) { @@ -122,7 +133,7 @@ class ActionNotifier extends Notifier { if (assets.length > 1) { return ActionResult(count: assets.length, success: false, error: 'Cannot troubleshoot multiple assets'); } - context.pushRoute(AssetTroubleshootRoute(asset: assets.first)); + unawaited(context.pushRoute(AssetTroubleshootRoute(asset: assets.first))); return ActionResult(count: assets.length, success: true); } @@ -184,7 +195,7 @@ class ActionNotifier extends Notifier { Future moveToLockFolder(ActionSource source) async { final ids = _getOwnedRemoteIdsForSource(source); - final localIds = _getLocalIdsForSource(source); + final localIds = _getLocalIdsForSource(source, ignoreLocalOnly: true); try { await _service.moveToLockFolder(ids, localIds); return ActionResult(count: ids.length, success: true); @@ -252,8 +263,28 @@ class ActionNotifier extends Notifier { } } - Future deleteLocal(ActionSource source) async { - final ids = _getLocalIdsForSource(source); + Future deleteLocal(ActionSource source, BuildContext context) async { + // Always perform the operation if there is only one merged asset + final assets = _getAssets(source); + bool? backedUpOnly = assets.length == 1 && assets.first.storage == AssetState.merged + ? true + : await showDialog( + context: context, + builder: (BuildContext context) => DeleteLocalOnlyDialog(onDeleteLocal: (_) {}), + ); + + if (backedUpOnly == null) { + // User cancelled the dialog + return null; + } + + final List ids; + if (backedUpOnly) { + ids = assets.where((asset) => asset.storage == AssetState.merged).map((asset) => asset.localId!).toList(); + } else { + ids = _getLocalIdsForSource(source); + } + try { final deletedCount = await _service.deleteLocal(ids); return ActionResult(count: deletedCount, success: true); @@ -335,6 +366,14 @@ class ActionNotifier extends Notifier { final assets = _getOwnedRemoteAssetsForSource(source); try { await _service.unStack(assets.map((e) => e.stackId).nonNulls.toList()); + if (source == ActionSource.viewer) { + final updatedParent = await _assetService.getRemoteAsset(assets.first.id); + if (updatedParent != null) { + ref.read(currentAssetNotifier.notifier).setAsset(updatedParent); + ref.read(assetViewerProvider.notifier).setAsset(updatedParent); + } + } + return ActionResult(count: assets.length, success: true); } catch (error, stack) { _logger.severe('Failed to unstack assets', error, stack); diff --git a/mobile/lib/providers/infrastructure/album.provider.dart b/mobile/lib/providers/infrastructure/album.provider.dart index 8388480974..1ddabc1604 100644 --- a/mobile/lib/providers/infrastructure/album.provider.dart +++ b/mobile/lib/providers/infrastructure/album.provider.dart @@ -1,4 +1,5 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/services/local_album.service.dart'; @@ -40,3 +41,7 @@ final remoteAlbumProvider = NotifierProvider, String>( + (ref, assetId) => ref.watch(remoteAlbumServiceProvider).getAlbumsContainingAsset(assetId), +); diff --git a/mobile/lib/providers/infrastructure/asset.provider.dart b/mobile/lib/providers/infrastructure/asset.provider.dart index 102e6aa60c..70cb200bf1 100644 --- a/mobile/lib/providers/infrastructure/asset.provider.dart +++ b/mobile/lib/providers/infrastructure/asset.provider.dart @@ -2,7 +2,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/services/asset.service.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; final localAssetRepository = Provider( (ref) => DriftLocalAssetRepository(ref.watch(driftProvider)), @@ -12,6 +14,10 @@ final remoteAssetRepositoryProvider = Provider( (ref) => RemoteAssetRepository(ref.watch(driftProvider)), ); +final trashedLocalAssetRepository = Provider( + (ref) => DriftTrashedLocalAssetRepository(ref.watch(driftProvider)), +); + final assetServiceProvider = Provider( (ref) => AssetService( remoteAssetRepository: ref.watch(remoteAssetRepositoryProvider), @@ -19,9 +25,13 @@ final assetServiceProvider = Provider( ), ); -final placesProvider = FutureProvider>( - (ref) => AssetService( - remoteAssetRepository: ref.watch(remoteAssetRepositoryProvider), - localAssetRepository: ref.watch(localAssetRepository), - ).getPlaces(), -); +final placesProvider = FutureProvider>((ref) { + final assetService = ref.watch(assetServiceProvider); + final auth = ref.watch(currentUserProvider); + + if (auth == null) { + return Future.value(const []); + } + + return assetService.getPlaces(auth.id); +}); diff --git a/mobile/lib/providers/infrastructure/current_album.provider.dart b/mobile/lib/providers/infrastructure/current_album.provider.dart index 0d95674ec7..6c2fc248ba 100644 --- a/mobile/lib/providers/infrastructure/current_album.provider.dart +++ b/mobile/lib/providers/infrastructure/current_album.provider.dart @@ -1,36 +1,30 @@ -import 'dart:async'; - import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; -final currentRemoteAlbumProvider = AutoDisposeNotifierProvider( +final currentRemoteAlbumScopedProvider = Provider((ref) => null); +final currentRemoteAlbumProvider = NotifierProvider( CurrentAlbumNotifier.new, + dependencies: [currentRemoteAlbumScopedProvider, remoteAlbumServiceProvider], ); -class CurrentAlbumNotifier extends AutoDisposeNotifier { - KeepAliveLink? _keepAliveLink; - StreamSubscription? _assetSubscription; - +class CurrentAlbumNotifier extends Notifier { @override - RemoteAlbum? build() => null; + RemoteAlbum? build() { + final album = ref.watch(currentRemoteAlbumScopedProvider); - void setAlbum(RemoteAlbum album) { - _keepAliveLink?.close(); - _assetSubscription?.cancel(); - state = album; + if (album == null) { + return null; + } - _assetSubscription = ref.watch(remoteAlbumServiceProvider).watchAlbum(album.id).listen((updatedAlbum) { + final watcher = ref.watch(remoteAlbumServiceProvider).watchAlbum(album.id).listen((updatedAlbum) { if (updatedAlbum != null) { state = updatedAlbum; } }); - _keepAliveLink = ref.keepAlive(); - } - void dispose() { - _keepAliveLink?.close(); - _assetSubscription?.cancel(); - state = null; + ref.onDispose(watcher.cancel); + + return album; } } diff --git a/mobile/lib/providers/infrastructure/sync.provider.dart b/mobile/lib/providers/infrastructure/sync.provider.dart index f03754505c..6ba9c4bb78 100644 --- a/mobile/lib/providers/infrastructure/sync.provider.dart +++ b/mobile/lib/providers/infrastructure/sync.provider.dart @@ -10,11 +10,17 @@ import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/storage.provider.dart'; +import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; final syncStreamServiceProvider = Provider( (ref) => SyncStreamService( syncApiRepository: ref.watch(syncApiRepositoryProvider), syncStreamRepository: ref.watch(syncStreamRepositoryProvider), + localAssetRepository: ref.watch(localAssetRepository), + trashedLocalAssetRepository: ref.watch(trashedLocalAssetRepository), + localFilesManager: ref.watch(localFilesManagerRepositoryProvider), + storageRepository: ref.watch(storageRepositoryProvider), cancelChecker: ref.watch(cancellationProvider), ), ); @@ -26,6 +32,9 @@ final syncStreamRepositoryProvider = Provider((ref) => SyncStreamRepository(ref. final localSyncServiceProvider = Provider( (ref) => LocalSyncService( localAlbumRepository: ref.watch(localAlbumRepository), + trashedLocalAssetRepository: ref.watch(trashedLocalAssetRepository), + localFilesManager: ref.watch(localFilesManagerRepositoryProvider), + storageRepository: ref.watch(storageRepositoryProvider), nativeSyncApi: ref.watch(nativeSyncApiProvider), ), ); @@ -35,5 +44,6 @@ final hashServiceProvider = Provider( localAlbumRepository: ref.watch(localAlbumRepository), localAssetRepository: ref.watch(localAssetRepository), nativeSyncApi: ref.watch(nativeSyncApiProvider), + trashedLocalAssetRepository: ref.watch(trashedLocalAssetRepository), ), ); diff --git a/mobile/lib/providers/infrastructure/trash_sync.provider.dart b/mobile/lib/providers/infrastructure/trash_sync.provider.dart new file mode 100644 index 0000000000..a783080f33 --- /dev/null +++ b/mobile/lib/providers/infrastructure/trash_sync.provider.dart @@ -0,0 +1,12 @@ +import 'package:async/async.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; + +typedef TrashedAssetsCount = ({int total, int hashed}); + +final trashedAssetsCountProvider = StreamProvider((ref) { + final repo = ref.watch(trashedLocalAssetRepository); + final total$ = repo.watchCount(); + final hashed$ = repo.watchHashedCount(); + return StreamZip([total$, hashed$]).map((values) => (total: values[0], hashed: values[1])); +}); diff --git a/mobile/lib/providers/server_info.provider.dart b/mobile/lib/providers/server_info.provider.dart index 25b1002b7a..9619ba86a1 100644 --- a/mobile/lib/providers/server_info.provider.dart +++ b/mobile/lib/providers/server_info.provider.dart @@ -1,11 +1,12 @@ -import 'package:easy_localization/easy_localization.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/models/server_info/server_config.model.dart'; import 'package:immich_mobile/models/server_info/server_disk_info.model.dart'; import 'package:immich_mobile/models/server_info/server_features.model.dart'; import 'package:immich_mobile/models/server_info/server_info.model.dart'; import 'package:immich_mobile/models/server_info/server_version.model.dart'; import 'package:immich_mobile/services/server_info.service.dart'; +import 'package:immich_mobile/utils/semver.dart'; import 'package:logging/logging.dart'; import 'package:package_info_plus/package_info_plus.dart'; @@ -24,9 +25,7 @@ class ServerInfoNotifier extends StateNotifier { mapDarkStyleUrl: 'https://tiles.immich.cloud/v1/style/dark.json', ), serverDiskInfo: ServerDiskInfo(diskAvailable: "0", diskSize: "0", diskUse: "0", diskUsagePercentage: 0), - isVersionMismatch: false, - isNewReleaseAvailable: false, - versionMismatchErrorMessage: "", + versionStatus: VersionStatus.upToDate, ), ); @@ -43,73 +42,42 @@ class ServerInfoNotifier extends StateNotifier { try { final serverVersion = await _serverInfoService.getServerVersion(); + // using isClientOutOfDate since that will show to users reguardless of if they are an admin if (serverVersion == null) { - state = state.copyWith(isVersionMismatch: true, versionMismatchErrorMessage: "common_server_error".tr()); + state = state.copyWith(versionStatus: VersionStatus.error); return; } await _checkServerVersionMismatch(serverVersion); } catch (e, stackTrace) { _log.severe("Failed to get server version", e, stackTrace); - state = state.copyWith(isVersionMismatch: true); + state = state.copyWith(versionStatus: VersionStatus.error); return; } } - _checkServerVersionMismatch(ServerVersion serverVersion) async { - state = state.copyWith(serverVersion: serverVersion); + _checkServerVersionMismatch(ServerVersion serverVersion, {ServerVersion? latestVersion}) async { + state = state.copyWith(serverVersion: serverVersion, latestVersion: latestVersion); var packageInfo = await PackageInfo.fromPlatform(); + SemVer clientVersion = SemVer.fromString(packageInfo.version); - Map appVersion = _getDetailVersion(packageInfo.version); - - if (appVersion["major"]! > serverVersion.major) { - state = state.copyWith( - isVersionMismatch: true, - versionMismatchErrorMessage: "profile_drawer_server_out_of_date_major".tr(), - ); + if (serverVersion < clientVersion || (latestVersion != null && serverVersion < latestVersion)) { + state = state.copyWith(versionStatus: VersionStatus.serverOutOfDate); return; } - if (appVersion["major"]! < serverVersion.major) { - state = state.copyWith( - isVersionMismatch: true, - versionMismatchErrorMessage: "profile_drawer_client_out_of_date_major".tr(), - ); + if (clientVersion < serverVersion && clientVersion.differenceType(serverVersion) != SemVerType.patch) { + state = state.copyWith(versionStatus: VersionStatus.clientOutOfDate); return; } - if (appVersion["minor"]! > serverVersion.minor) { - state = state.copyWith( - isVersionMismatch: true, - versionMismatchErrorMessage: "profile_drawer_server_out_of_date_minor".tr(), - ); - return; - } - - if (appVersion["minor"]! < serverVersion.minor) { - state = state.copyWith( - isVersionMismatch: true, - versionMismatchErrorMessage: "profile_drawer_client_out_of_date_minor".tr(), - ); - return; - } - - state = state.copyWith(isVersionMismatch: false, versionMismatchErrorMessage: ""); + state = state.copyWith(versionStatus: VersionStatus.upToDate); } - handleNewRelease(ServerVersion serverVersion, ServerVersion latestVersion) { + handleReleaseInfo(ServerVersion serverVersion, ServerVersion latestVersion) { // Update local server version - _checkServerVersionMismatch(serverVersion); - - final majorEqual = latestVersion.major == serverVersion.major; - final minorEqual = majorEqual && latestVersion.minor == serverVersion.minor; - final newVersionAvailable = - latestVersion.major > serverVersion.major || - (majorEqual && latestVersion.minor > serverVersion.minor) || - (minorEqual && latestVersion.patch > serverVersion.patch); - - state = state.copyWith(latestVersion: latestVersion, isNewReleaseAvailable: newVersionAvailable); + _checkServerVersionMismatch(serverVersion, latestVersion: latestVersion); } getServerFeatures() async { @@ -127,18 +95,15 @@ class ServerInfoNotifier extends StateNotifier { } state = state.copyWith(serverConfig: serverConfig); } - - Map _getDetailVersion(String version) { - List detail = version.split("."); - - var major = detail[0]; - var minor = detail[1]; - var patch = detail[2]; - - return {"major": int.parse(major), "minor": int.parse(minor), "patch": int.parse(patch.replaceAll("-DEBUG", ""))}; - } } final serverInfoProvider = StateNotifierProvider((ref) { return ServerInfoNotifier(ref.read(serverInfoServiceProvider)); }); + +final versionWarningPresentProvider = Provider.family((ref, user) { + final serverInfo = ref.watch(serverInfoProvider); + return serverInfo.versionStatus == VersionStatus.clientOutOfDate || + serverInfo.versionStatus == VersionStatus.error || + ((user?.isAdmin ?? false) && serverInfo.versionStatus == VersionStatus.serverOutOfDate); +}); diff --git a/mobile/lib/providers/shared_link.provider.dart b/mobile/lib/providers/shared_link.provider.dart index f574554bcb..fb44aea203 100644 --- a/mobile/lib/providers/shared_link.provider.dart +++ b/mobile/lib/providers/shared_link.provider.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/models/shared_link/shared_link.model.dart'; import 'package:immich_mobile/services/shared_link.service.dart'; @@ -16,7 +18,7 @@ class SharedLinksNotifier extends StateNotifier>> { Future deleteLink(String id) async { await _sharedLinkService.deleteSharedLink(id); state = const AsyncLoading(); - fetchLinks(); + unawaited(fetchLinks()); } } diff --git a/mobile/lib/providers/timeline/multiselect.provider.dart b/mobile/lib/providers/timeline/multiselect.provider.dart index e225e0c98d..6949413cd9 100644 --- a/mobile/lib/providers/timeline/multiselect.provider.dart +++ b/mobile/lib/providers/timeline/multiselect.provider.dart @@ -28,6 +28,8 @@ class MultiSelectState { bool get hasRemote => selectedAssets.any((asset) => asset.storage == AssetState.remote || asset.storage == AssetState.merged); + bool get hasStacked => selectedAssets.any((asset) => asset is RemoteAsset && asset.stackId != null); + bool get hasLocal => selectedAssets.any((asset) => asset.storage == AssetState.local); bool get hasMerged => selectedAssets.any((asset) => asset.storage == AssetState.merged); diff --git a/mobile/lib/providers/trash.provider.dart b/mobile/lib/providers/trash.provider.dart index adf3b1027b..41b9160b9b 100644 --- a/mobile/lib/providers/trash.provider.dart +++ b/mobile/lib/providers/trash.provider.dart @@ -1,6 +1,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/services/trash.service.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/services/trash.service.dart'; import 'package:logging/logging.dart'; class TrashNotifier extends StateNotifier { diff --git a/mobile/lib/providers/websocket.provider.dart b/mobile/lib/providers/websocket.provider.dart index 136c6049a7..6a1083bfcc 100644 --- a/mobile/lib/providers/websocket.provider.dart +++ b/mobile/lib/providers/websocket.provider.dart @@ -15,10 +15,10 @@ import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/sync.service.dart'; import 'package:immich_mobile/utils/debounce.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; import 'package:logging/logging.dart'; import 'package:openapi/api.dart'; import 'package:socket_io_client/socket_io_client.dart'; -import 'package:immich_mobile/utils/debug_print.dart'; enum PendingAction { assetDelete, assetUploaded, assetHidden, assetTrash } @@ -307,7 +307,7 @@ class WebsocketNotifier extends StateNotifier { final serverVersion = ServerVersion.fromDto(serverVersionDto); final releaseVersion = ServerVersion.fromDto(releaseVersionDto); - _ref.read(serverInfoProvider.notifier).handleNewRelease(serverVersion, releaseVersion); + _ref.read(serverInfoProvider.notifier).handleReleaseInfo(serverVersion, releaseVersion); } void _handleSyncAssetUploadReady(dynamic data) { diff --git a/mobile/lib/repositories/asset_media.repository.dart b/mobile/lib/repositories/asset_media.repository.dart index bfcf7060e0..2e4bdfd32c 100644 --- a/mobile/lib/repositories/asset_media.repository.dart +++ b/mobile/lib/repositories/asset_media.repository.dart @@ -1,5 +1,7 @@ +import 'dart:async'; import 'dart:io'; +import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/widgets.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; @@ -8,6 +10,7 @@ import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/asset.entity.dart' as asset_entity; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/extensions/response_extensions.dart'; import 'package:immich_mobile/repositories/asset_api.repository.dart'; import 'package:immich_mobile/utils/hash.dart'; @@ -20,11 +23,33 @@ final assetMediaRepositoryProvider = Provider((ref) => AssetMediaRepository(ref. class AssetMediaRepository { final AssetApiRepository _assetApiRepository; + static final Logger _log = Logger("AssetMediaRepository"); const AssetMediaRepository(this._assetApiRepository); - Future> deleteAll(List ids) => PhotoManager.editor.deleteWithIds(ids); + Future _androidSupportsTrash() async { + if (Platform.isAndroid) { + DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); + AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; + int sdkVersion = androidInfo.version.sdkInt; + return sdkVersion >= 31; + } + return false; + } + + Future> deleteAll(List ids) async { + if (CurrentPlatform.isAndroid) { + if (await _androidSupportsTrash()) { + return PhotoManager.editor.android.moveToTrash( + ids.map((e) => AssetEntity(id: e, width: 1, height: 1, typeInt: 0)).toList(), + ); + } else { + return PhotoManager.editor.deleteWithIds(ids); + } + } + return PhotoManager.editor.deleteWithIds(ids); + } Future get(String id) async { final entity = await AssetEntity.fromId(id); @@ -64,14 +89,22 @@ class AssetMediaRepository { return null; } - // titleAsync gets the correct original filename for some assets on iOS - // otherwise using the `entity.title` would return a random GUID - return await entity.titleAsync; + try { + // titleAsync gets the correct original filename for some assets on iOS + // otherwise using the `entity.title` would return a random GUID + final originalFilename = await entity.titleAsync; + // treat empty filename as missing + return originalFilename.isNotEmpty ? originalFilename : null; + } catch (e) { + _log.warning("Failed to get original filename for asset: $id. Error: $e"); + return null; + } } // TODO: make this more efficient Future shareAssets(List assets, BuildContext context) async { final downloadedXFiles = []; + final tempFiles = []; for (var asset in assets) { final localId = (asset is LocalAsset) @@ -82,6 +115,9 @@ class AssetMediaRepository { if (localId != null) { File? f = await AssetEntity(id: localId, width: 1, height: 1, typeInt: 0).originFile; downloadedXFiles.add(XFile(f!.path)); + if (CurrentPlatform.isIOS) { + tempFiles.add(f); + } } else if (asset is RemoteAsset) { final tempDir = await getTemporaryDirectory(); final name = asset.name; @@ -95,6 +131,7 @@ class AssetMediaRepository { await tempFile.writeAsBytes(res.bodyBytes); downloadedXFiles.add(XFile(tempFile.path)); + tempFiles.add(tempFile); } else { _log.warning("Asset type not supported for sharing: $asset"); continue; @@ -109,18 +146,20 @@ class AssetMediaRepository { // we dont want to await the share result since the // "preparing" dialog will not disappear until final size = context.sizeData; - Share.shareXFiles( - downloadedXFiles, - sharePositionOrigin: Rect.fromPoints(Offset.zero, Offset(size.width / 3, size.height)), - ).then((result) async { - for (var file in downloadedXFiles) { - try { - await File(file.path).delete(); - } catch (e) { - _log.warning("Failed to delete temporary file: ${file.path}", e); + unawaited( + Share.shareXFiles( + downloadedXFiles, + sharePositionOrigin: Rect.fromPoints(Offset.zero, Offset(size.width / 3, size.height)), + ).then((result) async { + for (var file in tempFiles) { + try { + await file.delete(); + } catch (e) { + _log.warning("Failed to delete temporary file: ${file.path}", e); + } } - } - }); + }), + ); return downloadedXFiles.length; } diff --git a/mobile/lib/repositories/download.repository.dart b/mobile/lib/repositories/download.repository.dart index 1ac9410fc6..c578746a4c 100644 --- a/mobile/lib/repositories/download.repository.dart +++ b/mobile/lib/repositories/download.repository.dart @@ -121,7 +121,7 @@ class DownloadRepository { _dummyMetadata['part'] = LivePhotosPart.video.index; tasks[taskIndex++] = DownloadTask( taskId: livePhotoVideoId, - url: url, + url: getOriginalUrlForRemoteId(livePhotoVideoId), headers: headers, filename: asset.name.toUpperCase().replaceAll(RegExp(r"\.(JPG|HEIC)$"), '.MOV'), updates: Updates.statusAndProgress, diff --git a/mobile/lib/repositories/folder_api.repository.dart b/mobile/lib/repositories/folder_api.repository.dart index dfda19e45e..d20ca8e0a9 100644 --- a/mobile/lib/repositories/folder_api.repository.dart +++ b/mobile/lib/repositories/folder_api.repository.dart @@ -8,7 +8,7 @@ import 'package:openapi/api.dart'; final folderApiRepositoryProvider = Provider((ref) => FolderApiRepository(ref.watch(apiServiceProvider).viewApi)); class FolderApiRepository extends ApiRepository { - final ViewApi _api; + final ViewsApi _api; final Logger _log = Logger("FolderApiRepository"); FolderApiRepository(this._api); diff --git a/mobile/lib/repositories/local_files_manager.repository.dart b/mobile/lib/repositories/local_files_manager.repository.dart index 519d79b49b..765c9a6f0e 100644 --- a/mobile/lib/repositories/local_files_manager.repository.dart +++ b/mobile/lib/repositories/local_files_manager.repository.dart @@ -1,13 +1,16 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/services/local_files_manager.service.dart'; +import 'package:logging/logging.dart'; final localFilesManagerRepositoryProvider = Provider( (ref) => LocalFilesManagerRepository(ref.watch(localFileManagerServiceProvider)), ); class LocalFilesManagerRepository { - const LocalFilesManagerRepository(this._service); + LocalFilesManagerRepository(this._service); + final Logger _logger = Logger('SyncStreamService'); final LocalFilesManagerService _service; Future moveToTrash(List mediaUrls) async { @@ -21,4 +24,26 @@ class LocalFilesManagerRepository { Future requestManageMediaPermission() async { return await _service.requestManageMediaPermission(); } + + Future hasManageMediaPermission() async { + return await _service.hasManageMediaPermission(); + } + + Future manageMediaPermission() async { + return await _service.manageMediaPermission(); + } + + Future> restoreAssetsFromTrash(Iterable assets) async { + final restoredIds = []; + for (final asset in assets) { + _logger.info("Restoring from trash, localId: ${asset.id}, remoteId: ${asset.checksum}"); + try { + await _service.restoreFromTrashById(asset.id, asset.type.index); + restoredIds.add(asset.id); + } catch (e) { + _logger.warning("Restoring failure: $e"); + } + } + return restoredIds; + } } diff --git a/mobile/lib/routing/app_navigation_observer.dart b/mobile/lib/routing/app_navigation_observer.dart index 26ec017b9a..b05a28172d 100644 --- a/mobile/lib/routing/app_navigation_observer.dart +++ b/mobile/lib/routing/app_navigation_observer.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -12,7 +14,7 @@ class AppNavigationObserver extends AutoRouterObserver { @override Future didChangeTabRoute(TabPageRoute route, TabPageRoute previousRoute) async { - Future(() => ref.read(inLockedViewProvider.notifier).state = false); + unawaited(Future(() => ref.read(inLockedViewProvider.notifier).state = false)); } @override diff --git a/mobile/lib/routing/auth_guard.dart b/mobile/lib/routing/auth_guard.dart index 33eb8e81ad..b0cd9ea9ea 100644 --- a/mobile/lib/routing/auth_guard.dart +++ b/mobile/lib/routing/auth_guard.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:auto_route/auto_route.dart'; @@ -26,18 +27,18 @@ class AuthGuard extends AutoRouteGuard { if (res == null || res.authStatus != true) { // If the access token is invalid, take user back to login _log.fine('User token is invalid. Redirecting to login'); - router.replaceAll([const LoginRoute()]); + unawaited(router.replaceAll([const LoginRoute()])); } } on StoreKeyNotFoundException catch (_) { // If there is no access token, take us to the login page _log.warning('No access token in the store.'); - router.replaceAll([const LoginRoute()]); + unawaited(router.replaceAll([const LoginRoute()])); return; } on ApiException catch (e) { // On an unauthorized request, take us to the login page if (e.code == HttpStatus.unauthorized) { _log.warning("Unauthorized access token."); - router.replaceAll([const LoginRoute()]); + unawaited(router.replaceAll([const LoginRoute()])); return; } } catch (e) { diff --git a/mobile/lib/routing/backup_permission_guard.dart b/mobile/lib/routing/backup_permission_guard.dart index 245a4b27af..f52516f2e5 100644 --- a/mobile/lib/routing/backup_permission_guard.dart +++ b/mobile/lib/routing/backup_permission_guard.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:auto_route/auto_route.dart'; import 'package:immich_mobile/providers/gallery_permission.provider.dart'; import 'package:immich_mobile/routing/router.dart'; @@ -13,7 +15,7 @@ class BackupPermissionGuard extends AutoRouteGuard { if (p) { resolver.next(true); } else { - router.push(const PermissionOnboardingRoute()); + unawaited(router.push(const PermissionOnboardingRoute())); } } } diff --git a/mobile/lib/routing/gallery_guard.dart b/mobile/lib/routing/gallery_guard.dart index eace8257b6..6a4b1bddab 100644 --- a/mobile/lib/routing/gallery_guard.dart +++ b/mobile/lib/routing/gallery_guard.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:auto_route/auto_route.dart'; import 'package:immich_mobile/routing/router.dart'; @@ -13,12 +15,14 @@ class GalleryGuard extends AutoRouteGuard { // Replace instead of pushing duplicate final args = resolver.route.args as GalleryViewerRouteArgs; - router.replace( - GalleryViewerRoute( - renderList: args.renderList, - initialIndex: args.initialIndex, - heroOffset: args.heroOffset, - showStack: args.showStack, + unawaited( + router.replace( + GalleryViewerRoute( + renderList: args.renderList, + initialIndex: args.initialIndex, + heroOffset: args.heroOffset, + showStack: args.showStack, + ), ), ); // Prevent further navigation since we replaced the route diff --git a/mobile/lib/routing/locked_guard.dart b/mobile/lib/routing/locked_guard.dart index 851407ff16..ddb6a7e694 100644 --- a/mobile/lib/routing/locked_guard.dart +++ b/mobile/lib/routing/locked_guard.dart @@ -1,8 +1,9 @@ +import 'dart:async'; + import 'package:auto_route/auto_route.dart'; import 'package:flutter/services.dart'; import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/routing/router.dart'; - import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/local_auth.service.dart'; import 'package:immich_mobile/services/secure_storage.service.dart'; @@ -30,7 +31,7 @@ class LockedGuard extends AutoRouteGuard { /// Check if a pincode has been created but this user. Show the form to create if not exist if (!authStatus.pinCode) { - router.push(PinAuthRoute(createPinCode: true)); + unawaited(router.push(PinAuthRoute(createPinCode: true))); } if (authStatus.isElevated) { @@ -42,7 +43,7 @@ class LockedGuard extends AutoRouteGuard { /// the user has enabled the biometric authentication final securePinCode = await _secureStorageService.read(kSecuredPinCode); if (securePinCode == null) { - router.push(PinAuthRoute()); + unawaited(router.push(PinAuthRoute())); return; } @@ -74,7 +75,7 @@ class LockedGuard extends AutoRouteGuard { } on ApiException { // PIN code has changed, need to re-enter to access await _secureStorageService.delete(kSecuredPinCode); - router.push(PinAuthRoute()); + unawaited(router.push(PinAuthRoute())); } catch (error) { _log.severe("Failed to access locked page", error); resolver.next(false); diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart index 7554c7b1cf..abe7ac3fa2 100644 --- a/mobile/lib/routing/router.dart +++ b/mobile/lib/routing/router.dart @@ -303,7 +303,7 @@ class AppRouter extends RootStackRouter { AutoRoute(page: DriftBackupAlbumSelectionRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: LocalTimelineRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: MainTimelineRoute.page, guards: [_authGuard, _duplicateGuard]), - AutoRoute(page: RemoteAlbumRoute.page, guards: [_authGuard, _duplicateGuard]), + AutoRoute(page: RemoteAlbumRoute.page, guards: [_authGuard]), AutoRoute( page: AssetViewerRoute.page, guards: [_authGuard, _duplicateGuard], @@ -313,6 +313,7 @@ class AppRouter extends RootStackRouter { settings: page, pageBuilder: (_, __, ___) => child, opaque: false, + transitionsBuilder: TransitionsBuilders.fadeIn, ), ), ), diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart index 4e488a30c7..146b313c2d 100644 --- a/mobile/lib/routing/router.gr.dart +++ b/mobile/lib/routing/router.gr.dart @@ -448,6 +448,7 @@ class AssetViewerRoute extends PageRouteInfo { required int initialIndex, required TimelineService timelineService, int? heroOffset, + RemoteAlbum? currentAlbum, List? children, }) : super( AssetViewerRoute.name, @@ -456,6 +457,7 @@ class AssetViewerRoute extends PageRouteInfo { initialIndex: initialIndex, timelineService: timelineService, heroOffset: heroOffset, + currentAlbum: currentAlbum, ), initialChildren: children, ); @@ -471,6 +473,7 @@ class AssetViewerRoute extends PageRouteInfo { initialIndex: args.initialIndex, timelineService: args.timelineService, heroOffset: args.heroOffset, + currentAlbum: args.currentAlbum, ); }, ); @@ -482,6 +485,7 @@ class AssetViewerRouteArgs { required this.initialIndex, required this.timelineService, this.heroOffset, + this.currentAlbum, }); final Key? key; @@ -492,9 +496,11 @@ class AssetViewerRouteArgs { final int? heroOffset; + final RemoteAlbum? currentAlbum; + @override String toString() { - return 'AssetViewerRouteArgs{key: $key, initialIndex: $initialIndex, timelineService: $timelineService, heroOffset: $heroOffset}'; + return 'AssetViewerRouteArgs{key: $key, initialIndex: $initialIndex, timelineService: $timelineService, heroOffset: $heroOffset, currentAlbum: $currentAlbum}'; } } @@ -706,36 +712,78 @@ class DownloadInfoRoute extends PageRouteInfo { /// generated route for /// [DriftActivitiesPage] -class DriftActivitiesRoute extends PageRouteInfo { - const DriftActivitiesRoute({List? children}) - : super(DriftActivitiesRoute.name, initialChildren: children); +class DriftActivitiesRoute extends PageRouteInfo { + DriftActivitiesRoute({ + Key? key, + required RemoteAlbum album, + List? children, + }) : super( + DriftActivitiesRoute.name, + args: DriftActivitiesRouteArgs(key: key, album: album), + initialChildren: children, + ); static const String name = 'DriftActivitiesRoute'; static PageInfo page = PageInfo( name, builder: (data) { - return const DriftActivitiesPage(); + final args = data.argsAs(); + return DriftActivitiesPage(key: args.key, album: args.album); }, ); } +class DriftActivitiesRouteArgs { + const DriftActivitiesRouteArgs({this.key, required this.album}); + + final Key? key; + + final RemoteAlbum album; + + @override + String toString() { + return 'DriftActivitiesRouteArgs{key: $key, album: $album}'; + } +} + /// generated route for /// [DriftAlbumOptionsPage] -class DriftAlbumOptionsRoute extends PageRouteInfo { - const DriftAlbumOptionsRoute({List? children}) - : super(DriftAlbumOptionsRoute.name, initialChildren: children); +class DriftAlbumOptionsRoute extends PageRouteInfo { + DriftAlbumOptionsRoute({ + Key? key, + required RemoteAlbum album, + List? children, + }) : super( + DriftAlbumOptionsRoute.name, + args: DriftAlbumOptionsRouteArgs(key: key, album: album), + initialChildren: children, + ); static const String name = 'DriftAlbumOptionsRoute'; static PageInfo page = PageInfo( name, builder: (data) { - return const DriftAlbumOptionsPage(); + final args = data.argsAs(); + return DriftAlbumOptionsPage(key: args.key, album: args.album); }, ); } +class DriftAlbumOptionsRouteArgs { + const DriftAlbumOptionsRouteArgs({this.key, required this.album}); + + final Key? key; + + final RemoteAlbum album; + + @override + String toString() { + return 'DriftAlbumOptionsRouteArgs{key: $key, album: $album}'; + } +} + /// generated route for /// [DriftAlbumsPage] class DriftAlbumsRoute extends PageRouteInfo { @@ -1410,43 +1458,20 @@ class DriftRecentlyTakenRoute extends PageRouteInfo { /// generated route for /// [DriftSearchPage] -class DriftSearchRoute extends PageRouteInfo { - DriftSearchRoute({ - Key? key, - SearchFilter? preFilter, - List? children, - }) : super( - DriftSearchRoute.name, - args: DriftSearchRouteArgs(key: key, preFilter: preFilter), - initialChildren: children, - ); +class DriftSearchRoute extends PageRouteInfo { + const DriftSearchRoute({List? children}) + : super(DriftSearchRoute.name, initialChildren: children); static const String name = 'DriftSearchRoute'; static PageInfo page = PageInfo( name, builder: (data) { - final args = data.argsAs( - orElse: () => const DriftSearchRouteArgs(), - ); - return DriftSearchPage(key: args.key, preFilter: args.preFilter); + return const DriftSearchPage(); }, ); } -class DriftSearchRouteArgs { - const DriftSearchRouteArgs({this.key, this.preFilter}); - - final Key? key; - - final SearchFilter? preFilter; - - @override - String toString() { - return 'DriftSearchRouteArgs{key: $key, preFilter: $preFilter}'; - } -} - /// generated route for /// [DriftTrashPage] class DriftTrashRoute extends PageRouteInfo { diff --git a/mobile/lib/services/action.service.dart b/mobile/lib/services/action.service.dart index 9c3768080b..59b627ecc3 100644 --- a/mobile/lib/services/action.service.dart +++ b/mobile/lib/services/action.service.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -50,7 +52,7 @@ class ActionService { ); Future shareLink(List remoteIds, BuildContext context) async { - context.pushRoute(SharedLinkEditRoute(assetsList: remoteIds)); + unawaited(context.pushRoute(SharedLinkEditRoute(assetsList: remoteIds))); } Future favorite(List remoteIds) async { diff --git a/mobile/lib/services/activity.service.dart b/mobile/lib/services/activity.service.dart index 1f09309947..382a7fe107 100644 --- a/mobile/lib/services/activity.service.dart +++ b/mobile/lib/services/activity.service.dart @@ -1,16 +1,25 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/errors.dart'; +import 'package:immich_mobile/domain/services/asset.service.dart'; +import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/mixins/error_logger.mixin.dart'; import 'package:immich_mobile/models/activities/activity.model.dart'; +import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.page.dart'; +import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart'; import 'package:immich_mobile/repositories/activity_api.repository.dart'; +import 'package:immich_mobile/routing/router.dart'; import 'package:logging/logging.dart'; +import 'package:immich_mobile/entities/store.entity.dart' as immich_store; class ActivityService with ErrorLoggerMixin { final ActivityApiRepository _activityApiRepository; + final TimelineFactory _timelineFactory; + final AssetService _assetService; @override final Logger logger = Logger("ActivityService"); - ActivityService(this._activityApiRepository); + ActivityService(this._activityApiRepository, this._timelineFactory, this._assetService); Future> getAllActivities(String albumId, {String? assetId}) async { return logError( @@ -49,4 +58,22 @@ class ActivityService with ErrorLoggerMixin { errorMessage: "Failed to create $type for album $albumId", ); } + + Future buildAssetViewerRoute(String assetId, WidgetRef ref) async { + if (immich_store.Store.isBetaTimelineEnabled) { + final asset = await _assetService.getRemoteAsset(assetId); + if (asset == null) { + return null; + } + + AssetViewer.setAsset(ref, asset); + return AssetViewerRoute( + initialIndex: 0, + timelineService: _timelineFactory.fromAssets([asset], TimelineOrigin.albumActivities), + currentAlbum: ref.read(currentRemoteAlbumProvider), + ); + } + + return null; + } } diff --git a/mobile/lib/services/album.service.dart b/mobile/lib/services/album.service.dart index a9eee0528e..8d77b569e6 100644 --- a/mobile/lib/services/album.service.dart +++ b/mobile/lib/services/album.service.dart @@ -83,7 +83,7 @@ class AlbumService { if (selectedIds.isEmpty) { final numLocal = await _albumRepository.count(local: true); if (numLocal > 0) { - _syncService.removeAllLocalAlbumsAndAssets(); + await _syncService.removeAllLocalAlbumsAndAssets(); } return false; } diff --git a/mobile/lib/services/api.service.dart b/mobile/lib/services/api.service.dart index 4033ffb184..1a714b6f40 100644 --- a/mobile/lib/services/api.service.dart +++ b/mobile/lib/services/api.service.dart @@ -6,18 +6,18 @@ import 'package:device_info_plus/device_info_plus.dart'; import 'package:http/http.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; import 'package:immich_mobile/utils/url_helper.dart'; +import 'package:immich_mobile/utils/user_agent.dart'; import 'package:logging/logging.dart'; import 'package:openapi/api.dart'; -import 'package:immich_mobile/utils/user_agent.dart'; -import 'package:immich_mobile/utils/debug_print.dart'; class ApiService implements Authentication { late ApiClient _apiClient; late UsersApi usersApi; late AuthenticationApi authenticationApi; - late OAuthApi oAuthApi; + late AuthenticationApi oAuthApi; late AlbumsApi albumsApi; late AssetsApi assetsApi; late SearchApi searchApi; @@ -32,7 +32,7 @@ class ApiService implements Authentication { late DownloadApi downloadApi; late TrashApi trashApi; late StacksApi stacksApi; - late ViewApi viewApi; + late ViewsApi viewApi; late MemoriesApi memoriesApi; late SessionsApi sessionsApi; @@ -56,7 +56,7 @@ class ApiService implements Authentication { } usersApi = UsersApi(_apiClient); authenticationApi = AuthenticationApi(_apiClient); - oAuthApi = OAuthApi(_apiClient); + oAuthApi = AuthenticationApi(_apiClient); albumsApi = AlbumsApi(_apiClient); assetsApi = AssetsApi(_apiClient); serverInfoApi = ServerApi(_apiClient); @@ -71,7 +71,7 @@ class ApiService implements Authentication { downloadApi = DownloadApi(_apiClient); trashApi = TrashApi(_apiClient); stacksApi = StacksApi(_apiClient); - viewApi = ViewApi(_apiClient); + viewApi = ViewsApi(_apiClient); memoriesApi = MemoriesApi(_apiClient); sessionsApi = SessionsApi(_apiClient); } @@ -86,7 +86,7 @@ class ApiService implements Authentication { setEndpoint(endpoint); // Save in local database for next startup - Store.put(StoreKey.serverEndpoint, endpoint); + await Store.put(StoreKey.serverEndpoint, endpoint); return endpoint; } diff --git a/mobile/lib/services/app_settings.service.dart b/mobile/lib/services/app_settings.service.dart index 8d47c6b5fa..0f81fb9a77 100644 --- a/mobile/lib/services/app_settings.service.dart +++ b/mobile/lib/services/app_settings.service.dart @@ -34,6 +34,7 @@ enum AppSettingsEnum { preferRemoteImage(StoreKey.preferRemoteImage, null, false), loopVideo(StoreKey.loopVideo, "loopVideo", true), loadOriginalVideo(StoreKey.loadOriginalVideo, "loadOriginalVideo", false), + autoPlayVideo(StoreKey.autoPlayVideo, "autoPlayVideo", true), mapThemeMode(StoreKey.mapThemeMode, null, 0), mapShowFavoriteOnly(StoreKey.mapShowFavoriteOnly, null, false), mapIncludeArchived(StoreKey.mapIncludeArchived, null, false), diff --git a/mobile/lib/services/auth.service.dart b/mobile/lib/services/auth.service.dart index 91c23cac1c..3173f49957 100644 --- a/mobile/lib/services/auth.service.dart +++ b/mobile/lib/services/auth.service.dart @@ -58,7 +58,7 @@ class AuthService { Future validateServerUrl(String url) async { final validUrl = await _apiService.resolveAndSetEndpoint(url); await _apiService.setDeviceInfoHeader(); - Store.put(StoreKey.serverUrl, validUrl); + await Store.put(StoreKey.serverUrl, validUrl); return validUrl; } diff --git a/mobile/lib/services/background.service.dart b/mobile/lib/services/background.service.dart index 33a8e810f1..b69aa53014 100644 --- a/mobile/lib/services/background.service.dart +++ b/mobile/lib/services/background.service.dart @@ -291,7 +291,7 @@ class BackgroundService { case "backgroundProcessing": case "onAssetsChanged": try { - _clearErrorNotifications(); + unawaited(_clearErrorNotifications()); // iOS should time out after some threshold so it doesn't wait // indefinitely and can run later @@ -342,7 +342,7 @@ class BackgroundService { ); HttpSSLOptions.apply(); - ref.read(apiServiceProvider).setAccessToken(Store.get(StoreKey.accessToken)); + await ref.read(apiServiceProvider).setAccessToken(Store.get(StoreKey.accessToken)); await ref.read(authServiceProvider).setOpenApiServiceEndpoint(); dPrint(() => "[BG UPLOAD] Using endpoint: ${ref.read(apiServiceProvider).apiClient.basePath}"); @@ -385,7 +385,7 @@ class BackgroundService { await ref.read(backupAlbumRepositoryProvider).deleteAll(toDelete); await ref.read(backupAlbumRepositoryProvider).updateAll(toUpsert); } else if (Store.tryGet(StoreKey.backupFailedSince) == null) { - Store.put(StoreKey.backupFailedSince, DateTime.now()); + await Store.put(StoreKey.backupFailedSince, DateTime.now()); return false; } // Android should check for new assets added while performing backup @@ -412,9 +412,11 @@ class BackgroundService { try { toUpload = await backupService.removeAlreadyUploadedAssets(toUpload); } catch (e) { - _showErrorNotification( - title: "backup_background_service_error_title".tr(), - content: "backup_background_service_connection_failed_message".tr(), + unawaited( + _showErrorNotification( + title: "backup_background_service_error_title".tr(), + content: "backup_background_service_connection_failed_message".tr(), + ), ); return false; } @@ -428,13 +430,15 @@ class BackgroundService { } _assetsToUploadCount = toUpload.length; _uploadedAssetsCount = 0; - _updateNotification( - title: "backup_background_service_in_progress_notification".tr(), - content: notifyTotalProgress ? formatAssetBackupProgress(_uploadedAssetsCount, _assetsToUploadCount) : null, - progress: 0, - max: notifyTotalProgress ? _assetsToUploadCount : 0, - indeterminate: !notifyTotalProgress, - onlyIfFG: !notifyTotalProgress, + unawaited( + _updateNotification( + title: "backup_background_service_in_progress_notification".tr(), + content: notifyTotalProgress ? formatAssetBackupProgress(_uploadedAssetsCount, _assetsToUploadCount) : null, + progress: 0, + max: notifyTotalProgress ? _assetsToUploadCount : 0, + indeterminate: !notifyTotalProgress, + onlyIfFG: !notifyTotalProgress, + ), ); _cancellationToken = CancellationToken(); @@ -452,9 +456,11 @@ class BackgroundService { ); if (!ok && !_cancellationToken!.isCancelled) { - _showErrorNotification( - title: "backup_background_service_error_title".tr(), - content: "backup_background_service_backup_failed_message".tr(), + unawaited( + _showErrorNotification( + title: "backup_background_service_error_title".tr(), + content: "backup_background_service_backup_failed_message".tr(), + ), ); } diff --git a/mobile/lib/services/backup_verification.service.dart b/mobile/lib/services/backup_verification.service.dart index 94c4721cca..1e8d426df8 100644 --- a/mobile/lib/services/backup_verification.service.dart +++ b/mobile/lib/services/backup_verification.service.dart @@ -120,7 +120,7 @@ class BackupVerificationService { await tuple.fileMediaRepository.enableBackgroundAccess(); final ApiService apiService = ApiService(); apiService.setEndpoint(tuple.endpoint); - apiService.setAccessToken(tuple.auth); + await apiService.setAccessToken(tuple.auth); for (int i = 0; i < tuple.deleteCandidates.length; i++) { if (await _compareAssets(tuple.deleteCandidates[i], tuple.originals[i], apiService)) { result.add(tuple.deleteCandidates[i]); diff --git a/mobile/lib/services/deep_link.service.dart b/mobile/lib/services/deep_link.service.dart index 6226781919..6ede7f6830 100644 --- a/mobile/lib/services/deep_link.service.dart +++ b/mobile/lib/services/deep_link.service.dart @@ -8,9 +8,8 @@ import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.page.dart'; import 'package:immich_mobile/providers/album/current_album.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/asset.provider.dart' as beta_asset_provider; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/asset.provider.dart' as beta_asset_provider; import 'package:immich_mobile/providers/infrastructure/memory.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/routing/router.dart'; @@ -29,7 +28,6 @@ final deepLinkServiceProvider = Provider( // Below is used for beta timeline ref.watch(timelineFactoryProvider), ref.watch(beta_asset_provider.assetServiceProvider), - ref.watch(currentRemoteAlbumProvider.notifier), ref.watch(remoteAlbumServiceProvider), ref.watch(driftMemoryServiceProvider), ), @@ -46,7 +44,6 @@ class DeepLinkService { /// Used for beta timeline final TimelineFactory _betaTimelineFactory; final beta_asset_service.AssetService _betaAssetService; - final CurrentAlbumNotifier _betaCurrentAlbumNotifier; final RemoteAlbumService _betaRemoteAlbumService; final DriftMemoryService _betaMemoryServiceProvider; @@ -58,7 +55,6 @@ class DeepLinkService { this._currentAlbum, this._betaTimelineFactory, this._betaAssetService, - this._betaCurrentAlbumNotifier, this._betaRemoteAlbumService, this._betaMemoryServiceProvider, ); @@ -81,6 +77,7 @@ class DeepLinkService { "memory" => await _buildMemoryDeepLink(queryParams['id'] ?? ''), "asset" => await _buildAssetDeepLink(queryParams['id'] ?? '', ref), "album" => await _buildAlbumDeepLink(queryParams['id'] ?? ''), + "activity" => await _buildActivityDeepLink(queryParams['albumId'] ?? ''), _ => null, }; @@ -150,7 +147,10 @@ class DeepLinkService { } AssetViewer.setAsset(ref, asset); - return AssetViewerRoute(initialIndex: 0, timelineService: _betaTimelineFactory.fromAssets([asset])); + return AssetViewerRoute( + initialIndex: 0, + timelineService: _betaTimelineFactory.fromAssets([asset], TimelineOrigin.deepLink), + ); } else { // TODO: Remove this when beta is default final asset = await _assetService.getAssetByRemoteId(assetId); @@ -173,7 +173,6 @@ class DeepLinkService { return null; } - _betaCurrentAlbumNotifier.setAlbum(album); return RemoteAlbumRoute(album: album); } else { // TODO: Remove this when beta is default @@ -187,4 +186,18 @@ class DeepLinkService { return AlbumViewerRoute(albumId: album.id); } } + + Future _buildActivityDeepLink(String albumId) async { + if (Store.isBetaTimelineEnabled == false) { + return null; + } + + final album = await _betaRemoteAlbumService.get(albumId); + + if (album == null || album.isActivityEnabled == false) { + return null; + } + + return DriftActivitiesRoute(album: album); + } } diff --git a/mobile/lib/services/local_files_manager.service.dart b/mobile/lib/services/local_files_manager.service.dart index 7cb3067342..0cc00f3e4b 100644 --- a/mobile/lib/services/local_files_manager.service.dart +++ b/mobile/lib/services/local_files_manager.service.dart @@ -6,6 +6,7 @@ final localFileManagerServiceProvider = Provider((ref) class LocalFilesManagerService { const LocalFilesManagerService(); + static final Logger _logger = Logger('LocalFilesManager'); static const MethodChannel _channel = MethodChannel('file_trash'); @@ -27,6 +28,15 @@ class LocalFilesManagerService { } } + Future restoreFromTrashById(String mediaId, int type) async { + try { + return await _channel.invokeMethod('restoreFromTrash', {'mediaId': mediaId, 'type': type}); + } catch (e, s) { + _logger.warning('Error restore file from trash by Id', e, s); + return false; + } + } + Future requestManageMediaPermission() async { try { return await _channel.invokeMethod('requestManageMediaPermission'); @@ -35,4 +45,22 @@ class LocalFilesManagerService { return false; } } + + Future hasManageMediaPermission() async { + try { + return await _channel.invokeMethod('hasManageMediaPermission'); + } catch (e, s) { + _logger.warning('Error requesting manage media permission state', e, s); + return false; + } + } + + Future manageMediaPermission() async { + try { + return await _channel.invokeMethod('manageMediaPermission'); + } catch (e, s) { + _logger.warning('Error requesting manage media permission settings', e, s); + return false; + } + } } diff --git a/mobile/lib/services/map.service.dart b/mobile/lib/services/map.service.dart index 2d236f77ef..5b50e8a890 100644 --- a/mobile/lib/services/map.service.dart +++ b/mobile/lib/services/map.service.dart @@ -1,9 +1,9 @@ import 'package:immich_mobile/mixins/error_logger.mixin.dart'; import 'package:immich_mobile/models/map/map_marker.model.dart'; import 'package:immich_mobile/services/api.service.dart'; +import 'package:immich_mobile/utils/user_agent.dart'; import 'package:logging/logging.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; -import 'package:immich_mobile/utils/user_agent.dart'; class MapService with ErrorLoggerMixin { final ApiService _apiService; @@ -16,7 +16,7 @@ class MapService with ErrorLoggerMixin { Future _setMapUserAgentHeader() async { final userAgent = await getUserAgentString(); - setHttpHeaders({'User-Agent': userAgent}); + await setHttpHeaders({'User-Agent': userAgent}); } Future> getMapMarkers({ diff --git a/mobile/lib/services/server_info.service.dart b/mobile/lib/services/server_info.service.dart index 0bce9366d2..460e135421 100644 --- a/mobile/lib/services/server_info.service.dart +++ b/mobile/lib/services/server_info.service.dart @@ -14,15 +14,6 @@ class ServerInfoService { const ServerInfoService(this._apiService); - Future ping() async { - try { - await _apiService.serverInfoApi.pingServer().timeout(const Duration(seconds: 5)); - return true; - } catch (e) { - return false; - } - } - Future getDiskInfo() async { try { final dto = await _apiService.serverInfoApi.getStorage(); diff --git a/mobile/lib/services/share.service.dart b/mobile/lib/services/share.service.dart index 7ba385d71c..06a4a192d4 100644 --- a/mobile/lib/services/share.service.dart +++ b/mobile/lib/services/share.service.dart @@ -1,13 +1,15 @@ +import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/extensions/response_extensions.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/response_extensions.dart'; import 'package:immich_mobile/providers/api.provider.dart'; import 'package:logging/logging.dart'; import 'package:path_provider/path_provider.dart'; import 'package:share_plus/share_plus.dart'; + import 'api.service.dart'; final shareServiceProvider = Provider((ref) => ShareService(ref.watch(apiServiceProvider))); @@ -58,9 +60,11 @@ class ShareService { } final size = MediaQuery.of(context).size; - Share.shareXFiles( - downloadedXFiles, - sharePositionOrigin: Rect.fromPoints(Offset.zero, Offset(size.width / 3, size.height)), + unawaited( + Share.shareXFiles( + downloadedXFiles, + sharePositionOrigin: Rect.fromPoints(Offset.zero, Offset(size.width / 3, size.height)), + ), ); return true; } catch (error) { diff --git a/mobile/lib/services/sync.service.dart b/mobile/lib/services/sync.service.dart index 1a5cb2a116..f5b55f36eb 100644 --- a/mobile/lib/services/sync.service.dart +++ b/mobile/lib/services/sync.service.dart @@ -705,7 +705,7 @@ class SyncService { if (assets.isEmpty) return; if (Platform.isAndroid && _appSettingsService.getSetting(AppSettingsEnum.manageLocalMediaAndroid)) { - _toggleTrashStatusForAssets(assets); + await _toggleTrashStatusForAssets(assets); } try { diff --git a/mobile/lib/services/upload.service.dart b/mobile/lib/services/upload.service.dart index b1130dab80..1ce0cf0322 100644 --- a/mobile/lib/services/upload.service.dart +++ b/mobile/lib/services/upload.service.dart @@ -4,11 +4,13 @@ import 'dart:io'; import 'package:background_downloader/background_downloader.dart'; import 'package:cancellation_token_http/http.dart'; +import 'package:flutter/foundation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; @@ -16,12 +18,13 @@ import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/storage.provider.dart'; +import 'package:immich_mobile/repositories/asset_media.repository.dart'; import 'package:immich_mobile/repositories/upload.repository.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; import 'package:logging/logging.dart'; import 'package:path/path.dart' as p; -import 'package:immich_mobile/utils/debug_print.dart'; final uploadServiceProvider = Provider((ref) { final service = UploadService( @@ -30,6 +33,7 @@ final uploadServiceProvider = Provider((ref) { ref.watch(storageRepositoryProvider), ref.watch(localAssetRepository), ref.watch(appSettingsServiceProvider), + ref.watch(assetMediaRepositoryProvider), ); ref.onDispose(service.dispose); @@ -43,6 +47,7 @@ class UploadService { this._storageRepository, this._localAssetRepository, this._appSettingsService, + this._assetMediaRepository, ) { _uploadRepository.onUploadStatus = _onUploadCallback; _uploadRepository.onTaskProgress = _onTaskProgressCallback; @@ -53,6 +58,7 @@ class UploadService { final StorageRepository _storageRepository; final DriftLocalAssetRepository _localAssetRepository; final AppSettingsService _appSettingsService; + final AssetMediaRepository _assetMediaRepository; final Logger _logger = Logger('UploadService'); final StreamController _taskStatusController = StreamController.broadcast(); @@ -97,7 +103,7 @@ class UploadService { await _storageRepository.clearCache(); List tasks = []; for (final asset in localAssets) { - final task = await _getUploadTask( + final task = await getUploadTask( asset, group: kManualUploadGroup, priority: 1, // High priority after upload motion photo part @@ -135,7 +141,7 @@ class UploadService { final batch = candidates.skip(i).take(batchSize).toList(); List tasks = []; for (final asset in batch) { - final task = await _getUploadTask(asset); + final task = await getUploadTask(asset); if (task != null) { tasks.add(task); } @@ -205,10 +211,20 @@ class UploadService { return _uploadRepository.start(); } - void _handleTaskStatusUpdate(TaskStatusUpdate update) { + void _handleTaskStatusUpdate(TaskStatusUpdate update) async { switch (update.status) { case TaskStatus.complete: - _handleLivePhoto(update); + unawaited(_handleLivePhoto(update)); + + if (CurrentPlatform.isIOS) { + try { + final path = await update.task.filePath(); + await File(path).delete(); + } catch (e) { + _logger.severe('Error deleting file path for iOS: $e'); + } + } + break; default: @@ -237,13 +253,13 @@ class UploadService { return; } - final uploadTask = await _getLivePhotoUploadTask(localAsset, response['id'] as String); + final uploadTask = await getLivePhotoUploadTask(localAsset, response['id'] as String); if (uploadTask == null) { return; } - enqueueTasks([uploadTask]); + await enqueueTasks([uploadTask]); } catch (error, stackTrace) { dPrint(() => "Error handling live photo upload task: $error $stackTrace"); } @@ -285,7 +301,8 @@ class UploadService { ); } - Future _getUploadTask(LocalAsset asset, {String group = kBackupGroup, int? priority}) async { + @visibleForTesting + Future getUploadTask(LocalAsset asset, {String group = kBackupGroup, int? priority}) async { final entity = await _storageRepository.getAssetEntityForAsset(asset); if (entity == null) { return null; @@ -313,7 +330,8 @@ class UploadService { return null; } - final originalFileName = entity.isLivePhoto ? p.setExtension(asset.name, p.extension(file.path)) : asset.name; + final fileName = await _assetMediaRepository.getOriginalFilename(asset.id) ?? asset.name; + final originalFileName = entity.isLivePhoto ? p.setExtension(fileName, p.extension(file.path)) : fileName; String metadata = UploadTaskMetadata( localAssetId: asset.id, @@ -337,7 +355,8 @@ class UploadService { ); } - Future _getLivePhotoUploadTask(LocalAsset asset, String livePhotoVideoId) async { + @visibleForTesting + Future getLivePhotoUploadTask(LocalAsset asset, String livePhotoVideoId) async { final entity = await _storageRepository.getAssetEntityForAsset(asset); if (entity == null) { return null; @@ -351,12 +370,13 @@ class UploadService { final fields = {'livePhotoVideoId': livePhotoVideoId}; final requiresWiFi = _shouldRequireWiFi(asset); + final originalFileName = await _assetMediaRepository.getOriginalFilename(asset.id) ?? asset.name; return buildUploadTask( file, createdAt: asset.createdAt, modifiedAt: asset.updatedAt, - originalFileName: asset.name, + originalFileName: originalFileName, deviceAssetId: asset.id, fields: fields, group: kBackupLivePhotoGroup, diff --git a/mobile/lib/utils/action_button.utils.dart b/mobile/lib/utils/action_button.utils.dart index 090aeeeaa7..42729becc9 100644 --- a/mobile/lib/utils/action_button.utils.dart +++ b/mobile/lib/utils/action_button.utils.dart @@ -14,8 +14,10 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/remove_from_al import 'package:immich_mobile/presentation/widgets/action_buttons/remove_from_lock_folder_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/similar_photos_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/unarchive_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart'; class ActionButtonContext { @@ -24,6 +26,7 @@ class ActionButtonContext { final bool isArchived; final bool isTrashEnabled; final bool isInLockedView; + final bool isStacked; final RemoteAlbum? currentAlbum; final bool advancedTroubleshooting; final ActionSource source; @@ -33,6 +36,7 @@ class ActionButtonContext { required this.isOwner, required this.isArchived, required this.isTrashEnabled, + required this.isStacked, required this.isInLockedView, required this.currentAlbum, required this.advancedTroubleshooting, @@ -44,6 +48,7 @@ enum ActionButtonType { advancedInfo, share, shareLink, + similarPhotos, archive, unarchive, download, @@ -55,6 +60,7 @@ enum ActionButtonType { deleteLocal, upload, removeFromAlbum, + unstack, likeActivity; bool shouldShow(ActionButtonContext context) { @@ -110,11 +116,18 @@ enum ActionButtonType { context.isOwner && // !context.isInLockedView && // context.currentAlbum != null, + ActionButtonType.unstack => + context.isOwner && // + !context.isInLockedView && // + context.isStacked, ActionButtonType.likeActivity => !context.isInLockedView && context.currentAlbum != null && context.currentAlbum!.isActivityEnabled && context.currentAlbum!.isShared, + ActionButtonType.similarPhotos => + !context.isInLockedView && // + context.asset is RemoteAsset, }; } @@ -138,28 +151,14 @@ enum ActionButtonType { source: context.source, ), ActionButtonType.likeActivity => const LikeActivityActionButton(), + ActionButtonType.unstack => UnStackActionButton(source: context.source), + ActionButtonType.similarPhotos => SimilarPhotosActionButton(assetId: (context.asset as RemoteAsset).id), }; } } class ActionButtonBuilder { - static const List _actionTypes = [ - ActionButtonType.advancedInfo, - ActionButtonType.share, - ActionButtonType.shareLink, - ActionButtonType.likeActivity, - ActionButtonType.archive, - ActionButtonType.unarchive, - ActionButtonType.download, - ActionButtonType.trash, - ActionButtonType.deletePermanent, - ActionButtonType.delete, - ActionButtonType.moveToLockFolder, - ActionButtonType.removeFromLockFolder, - ActionButtonType.deleteLocal, - ActionButtonType.upload, - ActionButtonType.removeFromAlbum, - ]; + static const List _actionTypes = ActionButtonType.values; static List build(ActionButtonContext context) { return _actionTypes.where((type) => type.shouldShow(context)).map((type) => type.buildButton(context)).toList(); diff --git a/mobile/lib/utils/bootstrap.dart b/mobile/lib/utils/bootstrap.dart index c77ceaa62d..f5c7513d1b 100644 --- a/mobile/lib/utils/bootstrap.dart +++ b/mobile/lib/utils/bootstrap.dart @@ -43,14 +43,14 @@ void configureFileDownloaderNotifications() { FileDownloader().configureNotificationForGroup( kManualUploadGroup, running: TaskNotification('uploading_media'.t(), 'backup_background_service_in_progress_notification'.t()), - complete: TaskNotification('upload_finished'.t(), 'backup_background_service_in_progress_notification'.t()), + complete: TaskNotification('upload_finished'.t(), 'backup_background_service_complete_notification'.t()), groupNotificationId: kManualUploadGroup, ); FileDownloader().configureNotificationForGroup( kBackupGroup, running: TaskNotification('uploading_media'.t(), 'backup_background_service_in_progress_notification'.t()), - complete: TaskNotification('upload_finished'.t(), 'backup_background_service_in_progress_notification'.t()), + complete: TaskNotification('upload_finished'.t(), 'backup_background_service_complete_notification'.t()), groupNotificationId: kBackupGroup, ); } diff --git a/mobile/lib/utils/datetime_helpers.dart b/mobile/lib/utils/datetime_helpers.dart index 829f71c37e..c13c8ca312 100644 --- a/mobile/lib/utils/datetime_helpers.dart +++ b/mobile/lib/utils/datetime_helpers.dart @@ -1,7 +1,7 @@ const int _maxMillisecondsSinceEpoch = 8640000000000000; // 275760-09-13 const int _minMillisecondsSinceEpoch = -62135596800000; // 0001-01-01 -DateTime? tryFromSecondsSinceEpoch(int? secondsSinceEpoch) { +DateTime? tryFromSecondsSinceEpoch(int? secondsSinceEpoch, {bool isUtc = false}) { if (secondsSinceEpoch == null) { return null; } @@ -12,7 +12,7 @@ DateTime? tryFromSecondsSinceEpoch(int? secondsSinceEpoch) { } try { - return DateTime.fromMillisecondsSinceEpoch(milliSeconds); + return DateTime.fromMillisecondsSinceEpoch(milliSeconds, isUtc: isUtc); } catch (e) { return null; } diff --git a/mobile/lib/utils/isolate.dart b/mobile/lib/utils/isolate.dart index e8b7d410f4..491e1bf107 100644 --- a/mobile/lib/utils/isolate.dart +++ b/mobile/lib/utils/isolate.dart @@ -11,6 +11,7 @@ import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/utils/bootstrap.dart'; import 'package:immich_mobile/utils/debug_print.dart'; import 'package:immich_mobile/utils/http_ssl_options.dart'; +import 'package:immich_mobile/wm_executor.dart'; import 'package:logging/logging.dart'; import 'package:worker_manager/worker_manager.dart'; @@ -31,7 +32,8 @@ Cancelable runInIsolateGentle({ throw const InvalidIsolateUsageException(); } - return workerManager.executeGentle((cancelledChecker) async { + return workerManagerPatch.executeGentle((cancelledChecker) async { + T? result; await runZonedGuarded( () async { BackgroundIsolateBinaryMessenger.ensureInitialized(token); @@ -53,7 +55,7 @@ Cancelable runInIsolateGentle({ try { HttpSSLOptions.apply(applyNative: false); - return await computation(ref); + result = await computation(ref); } on CanceledError { log.warning("Computation cancelled ${debugLabel == null ? '' : ' for $debugLabel'}"); } catch (error, stack) { @@ -83,12 +85,11 @@ Cancelable runInIsolateGentle({ await Future.delayed(const Duration(seconds: 2)); } } - return null; }, (error, stack) { dPrint(() => "Error in isolate $debugLabel zone: $error, $stack"); }, ); - return null; + return result; }); } diff --git a/mobile/lib/utils/map_utils.dart b/mobile/lib/utils/map_utils.dart index 80e20b7c6c..6213b214a9 100644 --- a/mobile/lib/utils/map_utils.dart +++ b/mobile/lib/utils/map_utils.dart @@ -1,8 +1,10 @@ +import 'dart:async'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:geolocator/geolocator.dart'; import 'package:immich_mobile/models/map/map_marker.model.dart'; import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; -import 'package:geolocator/geolocator.dart'; import 'package:logging/logging.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; @@ -68,7 +70,7 @@ class MapUtils { try { bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled && !silent) { - showDialog(context: context, builder: (context) => _LocationServiceDisabledDialog()); + unawaited(showDialog(context: context, builder: (context) => _LocationServiceDisabledDialog())); return (null, LocationPermission.deniedForever); } diff --git a/mobile/lib/utils/migration.dart b/mobile/lib/utils/migration.dart index 1be8647e3d..2ed6d9549f 100644 --- a/mobile/lib/utils/migration.dart +++ b/mobile/lib/utils/migration.dart @@ -22,13 +22,14 @@ import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/utils/debug_print.dart'; import 'package:immich_mobile/utils/diff.dart'; import 'package:isar/isar.dart'; // ignore: import_rule_photo_manager import 'package:photo_manager/photo_manager.dart'; -const int targetVersion = 16; +const int targetVersion = 17; Future migrateDatabaseIfNeeded(Isar db, Drift drift) async { final hasVersion = Store.tryGet(StoreKey.version) != null; @@ -64,6 +65,13 @@ Future migrateDatabaseIfNeeded(Isar db, Drift drift) async { await handleBetaMigration(version, await _isNewInstallation(db, drift), SyncStreamRepository(drift)); + if (version < 17 && Store.isBetaTimelineEnabled) { + final delay = Store.get(StoreKey.backupTriggerDelay, AppSettingsEnum.backupTriggerDelay.defaultValue); + if (delay >= 1000) { + await Store.put(StoreKey.backupTriggerDelay, (delay / 1000).toInt()); + } + } + if (targetVersion >= 12) { await Store.put(StoreKey.version, targetVersion); return; diff --git a/mobile/lib/utils/openapi_patching.dart b/mobile/lib/utils/openapi_patching.dart index 33199d5225..0c1f03086f 100644 --- a/mobile/lib/utils/openapi_patching.dart +++ b/mobile/lib/utils/openapi_patching.dart @@ -46,6 +46,16 @@ dynamic upgradeDto(dynamic value, String targetType) { addDefault(value, 'profileChangedAt', DateTime.now().toIso8601String()); addDefault(value, 'hasProfileImage', false); } + case 'ServerFeaturesDto': + if (value is Map) { + addDefault(value, 'ocr', false); + } + break; + case 'MemoriesResponse': + if (value is Map) { + addDefault(value, 'duration', 5); + } + break; } } diff --git a/mobile/lib/utils/selection_handlers.dart b/mobile/lib/utils/selection_handlers.dart index d128ef8fac..f0d333e262 100644 --- a/mobile/lib/utils/selection_handlers.dart +++ b/mobile/lib/utils/selection_handlers.dart @@ -101,7 +101,7 @@ Future handleEditDateTime(WidgetRef ref, BuildContext context, List return; } - ref.read(assetServiceProvider).changeDateTime(selection.toList(), dateTime); + await ref.read(assetServiceProvider).changeDateTime(selection.toList(), dateTime); } Future handleEditLocation(WidgetRef ref, BuildContext context, List selection) async { @@ -120,7 +120,7 @@ Future handleEditLocation(WidgetRef ref, BuildContext context, List return; } - ref.read(assetServiceProvider).changeLocation(selection.toList(), location); + await ref.read(assetServiceProvider).changeLocation(selection.toList(), location); } Future handleSetAssetsVisibility( diff --git a/mobile/lib/utils/semver.dart b/mobile/lib/utils/semver.dart new file mode 100644 index 0000000000..aebfd2fe4c --- /dev/null +++ b/mobile/lib/utils/semver.dart @@ -0,0 +1,87 @@ +enum SemVerType { major, minor, patch } + +class SemVer { + final int major; + final int minor; + final int patch; + + const SemVer({required this.major, required this.minor, required this.patch}); + + @override + String toString() { + return '$major.$minor.$patch'; + } + + SemVer copyWith({int? major, int? minor, int? patch}) { + return SemVer(major: major ?? this.major, minor: minor ?? this.minor, patch: patch ?? this.patch); + } + + factory SemVer.fromString(String version) { + if (version.toLowerCase().startsWith("v")) { + version = version.substring(1); + } + + final parts = version.split("-")[0].split('.'); + if (parts.length != 3) { + throw FormatException('Invalid semantic version string: $version'); + } + + try { + return SemVer(major: int.parse(parts[0]), minor: int.parse(parts[1]), patch: int.parse(parts[2])); + } catch (e) { + throw FormatException('Invalid semantic version string: $version'); + } + } + + bool operator >(SemVer other) { + if (major != other.major) { + return major > other.major; + } + if (minor != other.minor) { + return minor > other.minor; + } + return patch > other.patch; + } + + bool operator <(SemVer other) { + if (major != other.major) { + return major < other.major; + } + if (minor != other.minor) { + return minor < other.minor; + } + return patch < other.patch; + } + + bool operator >=(SemVer other) { + return this > other || this == other; + } + + bool operator <=(SemVer other) { + return this < other || this == other; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is SemVer && other.major == major && other.minor == minor && other.patch == patch; + } + + SemVerType? differenceType(SemVer other) { + if (major != other.major) { + return SemVerType.major; + } + if (minor != other.minor) { + return SemVerType.minor; + } + if (patch != other.patch) { + return SemVerType.patch; + } + + return null; + } + + @override + int get hashCode => major.hashCode ^ minor.hashCode ^ patch.hashCode; +} diff --git a/mobile/lib/widgets/activities/activity_tile.dart b/mobile/lib/widgets/activities/activity_tile.dart index 4b66bd5eaf..6812d1b90c 100644 --- a/mobile/lib/widgets/activities/activity_tile.dart +++ b/mobile/lib/widgets/activities/activity_tile.dart @@ -1,16 +1,19 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/datetime_extensions.dart'; import 'package:immich_mobile/models/activities/activity.model.dart'; +import 'package:immich_mobile/providers/activity_service.provider.dart'; import 'package:immich_mobile/providers/image/immich_remote_thumbnail_provider.dart'; import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; import 'package:immich_mobile/widgets/common/user_circle_avatar.dart'; class ActivityTile extends HookConsumerWidget { final Activity activity; + final bool isBottomSheet; - const ActivityTile(this.activity, {super.key}); + const ActivityTile(this.activity, {super.key, this.isBottomSheet = false}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -18,25 +21,35 @@ class ActivityTile extends HookConsumerWidget { final isLike = activity.type == ActivityType.like; // Asset thumbnail is displayed when we are accessing activities from the album page // currentAssetProvider will not be set until we open the gallery viewer - final showAssetThumbnail = asset == null && activity.assetId != null; + final showAssetThumbnail = asset == null && activity.assetId != null && !isBottomSheet; + + onTap() async { + final activityService = ref.read(activityServiceProvider); + final route = await activityService.buildAssetViewerRoute(activity.assetId!, ref); + if (route != null) { + await context.pushRoute(route); + } + } return ListTile( minVerticalPadding: 15, leading: isLike ? Container( - width: 44, + width: isBottomSheet ? 30 : 44, alignment: Alignment.center, child: Icon(Icons.favorite_rounded, color: Colors.red[700]), ) + : isBottomSheet + ? UserCircleAvatar(user: activity.user, size: 30, radius: 15) : UserCircleAvatar(user: activity.user), title: _ActivityTitle( userName: activity.user.name, createdAt: activity.createdAt.timeAgo(), - leftAlign: isLike || showAssetThumbnail, + leftAlign: isBottomSheet ? false : (isLike || showAssetThumbnail), ), // No subtitle for like, so center title titleAlignment: !isLike ? ListTileTitleAlignment.top : ListTileTitleAlignment.center, - trailing: showAssetThumbnail ? _ActivityAssetThumbnail(activity.assetId!) : null, + trailing: showAssetThumbnail ? _ActivityAssetThumbnail(activity.assetId!, onTap) : null, subtitle: !isLike ? Text(activity.comment!) : null, ); } @@ -75,22 +88,26 @@ class _ActivityTitle extends StatelessWidget { class _ActivityAssetThumbnail extends StatelessWidget { final String assetId; + final GestureTapCallback? onTap; - const _ActivityAssetThumbnail(this.assetId); + const _ActivityAssetThumbnail(this.assetId, this.onTap); @override Widget build(BuildContext context) { - return Container( - width: 40, - height: 30, - decoration: BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(4)), - image: DecorationImage( - image: ImmichRemoteThumbnailProvider(assetId: assetId), - fit: BoxFit.cover, + return GestureDetector( + onTap: onTap, + child: Container( + width: 40, + height: 30, + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(4)), + image: DecorationImage( + image: ImmichRemoteThumbnailProvider(assetId: assetId), + fit: BoxFit.cover, + ), ), + child: const SizedBox.shrink(), ), - child: const SizedBox.shrink(), ); } } diff --git a/mobile/lib/widgets/activities/comment_bubble.dart b/mobile/lib/widgets/activities/comment_bubble.dart new file mode 100644 index 0000000000..11d5c21cec --- /dev/null +++ b/mobile/lib/widgets/activities/comment_bubble.dart @@ -0,0 +1,143 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/datetime_extensions.dart'; +import 'package:immich_mobile/models/activities/activity.model.dart'; +import 'package:immich_mobile/providers/activity.provider.dart'; +import 'package:immich_mobile/providers/activity_service.provider.dart'; +import 'package:immich_mobile/providers/image/immich_remote_thumbnail_provider.dart'; +import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/widgets/activities/dismissible_activity.dart'; +import 'package:immich_mobile/widgets/common/user_circle_avatar.dart'; + +class CommentBubble extends ConsumerWidget { + final Activity activity; + final bool isAssetActivity; + + const CommentBubble({super.key, required this.activity, this.isAssetActivity = false}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final user = ref.watch(currentUserProvider); + final album = ref.watch(currentRemoteAlbumProvider)!; + final isOwn = activity.user.id == user?.id; + final canDelete = isOwn || album.ownerId == user?.id; + final showThumbnail = !isAssetActivity && activity.assetId != null && activity.assetId!.isNotEmpty; + final isLike = activity.type == ActivityType.like; + final bgColor = isOwn ? context.colorScheme.primaryContainer : context.colorScheme.surfaceContainer; + + final activityNotifier = ref.read( + albumActivityProvider(album.id, isAssetActivity ? activity.assetId : null).notifier, + ); + + Future openAssetViewer() async { + final activityService = ref.read(activityServiceProvider); + final route = await activityService.buildAssetViewerRoute(activity.assetId!, ref); + if (route != null) await context.pushRoute(route); + } + + // avatar (hidden for own messages) + Widget avatar = const SizedBox.shrink(); + if (!isOwn) { + avatar = UserCircleAvatar(user: activity.user, size: 28, radius: 14); + } + + // Thumbnail with tappable behavior and optional heart overlay + Widget? thumbnail; + if (showThumbnail) { + thumbnail = ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 150, maxHeight: 150), + child: Stack( + children: [ + GestureDetector( + onTap: openAssetViewer, + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(10)), + child: Image( + image: ImmichRemoteThumbnailProvider(assetId: activity.assetId!), + fit: BoxFit.cover, + ), + ), + ), + if (isLike) + Positioned( + right: 6, + bottom: 6, + child: Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration(color: Colors.white.withValues(alpha: 0.7), shape: BoxShape.circle), + child: Icon(Icons.favorite, color: Colors.red[600], size: 18), + ), + ), + ], + ), + ); + } + + // Likes widget + Widget? likes; + if (isLike && !showThumbnail) { + likes = Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration(color: Colors.white.withValues(alpha: 0.7), shape: BoxShape.circle), + child: Icon(Icons.favorite, color: Colors.red[600], size: 18), + ); + } + + // Comment bubble, comment-only + Widget? commentBubble; + if (activity.comment != null && activity.comment!.isNotEmpty) { + commentBubble = ConstrainedBox( + constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.5), + child: Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration(color: bgColor, borderRadius: const BorderRadius.all(Radius.circular(12))), + child: Text( + activity.comment ?? '', + style: context.textTheme.bodyLarge?.copyWith(color: context.colorScheme.onSurface), + ), + ), + ); + } + + // Combined content widgets + final List contentChildren = [thumbnail, likes, commentBubble].whereType().toList(); + + return DismissibleActivity( + onDismiss: canDelete ? (id) async => await activityNotifier.removeActivity(id) : null, + activity.id, + Align( + alignment: isOwn ? Alignment.centerRight : Alignment.centerLeft, + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.86), + child: Container( + margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 10), + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (!isOwn) ...[avatar, const SizedBox(width: 8)], + // Content column + Column( + crossAxisAlignment: isOwn ? CrossAxisAlignment.end : CrossAxisAlignment.start, + children: [ + ...contentChildren.map((w) => Padding(padding: const EdgeInsets.only(bottom: 8.0), child: w)), + Text( + '${activity.user.name} • ${activity.createdAt.timeAgo()}', + style: context.textTheme.labelMedium?.copyWith( + color: context.colorScheme.onSurface.withValues(alpha: 0.6), + ), + ), + ], + ), + if (isOwn) const SizedBox(width: 8), + ], + ), + ), + ), + ), + ); + } +} diff --git a/mobile/lib/widgets/activities/dismissible_activity.dart b/mobile/lib/widgets/activities/dismissible_activity.dart index 2f017d51ed..806181ecdc 100644 --- a/mobile/lib/widgets/activities/dismissible_activity.dart +++ b/mobile/lib/widgets/activities/dismissible_activity.dart @@ -5,13 +5,17 @@ import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; /// Wraps an [ActivityTile] and makes it dismissible class DismissibleActivity extends StatelessWidget { final String activityId; - final ActivityTile body; + final Widget body; final Function(String)? onDismiss; const DismissibleActivity(this.activityId, this.body, {this.onDismiss, super.key}); @override Widget build(BuildContext context) { + if (onDismiss == null) { + return body; + } + return Dismissible( key: Key(activityId), dismissThresholds: const {DismissDirection.horizontal: 0.7}, diff --git a/mobile/lib/widgets/album/album_viewer_appbar.dart b/mobile/lib/widgets/album/album_viewer_appbar.dart index 420218d7e5..4fd4b31013 100644 --- a/mobile/lib/widgets/album/album_viewer_appbar.dart +++ b/mobile/lib/widgets/album/album_viewer_appbar.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -57,7 +59,7 @@ class AlbumViewerAppbar extends HookConsumerWidget implements PreferredSizeWidge deleteAlbum() async { final bool success = await ref.watch(albumProvider.notifier).deleteAlbum(album); - context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()])); + unawaited(context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]))); if (!success) { ImmichToast.show( @@ -105,7 +107,7 @@ class AlbumViewerAppbar extends HookConsumerWidget implements PreferredSizeWidge bool isSuccess = await ref.watch(albumProvider.notifier).leaveAlbum(album); if (isSuccess) { - context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()])); + unawaited(context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]))); } else { context.pop(); ImmichToast.show( diff --git a/mobile/lib/widgets/album/remote_album_shared_user_icons.dart b/mobile/lib/widgets/album/remote_album_shared_user_icons.dart index 9f88b23f92..8913e94136 100644 --- a/mobile/lib/widgets/album/remote_album_shared_user_icons.dart +++ b/mobile/lib/widgets/album/remote_album_shared_user_icons.dart @@ -25,7 +25,7 @@ class RemoteAlbumSharedUserIcons extends ConsumerWidget { } return GestureDetector( - onTap: () => context.pushRoute(const DriftAlbumOptionsRoute()), + onTap: () => context.pushRoute(DriftAlbumOptionsRoute(album: currentAlbum)), child: SizedBox( height: 50, child: ListView.builder( diff --git a/mobile/lib/widgets/asset_grid/delete_dialog.dart b/mobile/lib/widgets/asset_grid/delete_dialog.dart index e7c7775e54..adb22889a8 100644 --- a/mobile/lib/widgets/asset_grid/delete_dialog.dart +++ b/mobile/lib/widgets/asset_grid/delete_dialog.dart @@ -22,12 +22,12 @@ class DeleteLocalOnlyDialog extends StatelessWidget { @override Widget build(BuildContext context) { void onDeleteBackedUpOnly() { - context.pop(); + context.pop(true); onDeleteLocal(true); } void onForceDelete() { - context.pop(); + context.pop(false); onDeleteLocal(false); } @@ -36,26 +36,44 @@ class DeleteLocalOnlyDialog extends StatelessWidget { title: const Text("delete_dialog_title").tr(), content: const Text("delete_dialog_alert_local_non_backed_up").tr(), actions: [ - TextButton( - onPressed: () => context.pop(), - child: Text( - "cancel", - style: TextStyle(color: context.primaryColor, fontWeight: FontWeight.bold), - ).tr(), + SizedBox( + width: double.infinity, + height: 48, + child: FilledButton( + onPressed: () => context.pop(), + style: FilledButton.styleFrom( + backgroundColor: context.colorScheme.surfaceDim, + foregroundColor: context.primaryColor, + ), + child: const Text("cancel", style: TextStyle(fontWeight: FontWeight.bold)).tr(), + ), ), - TextButton( - onPressed: onDeleteBackedUpOnly, - child: Text( - "delete_local_dialog_ok_backed_up_only", - style: TextStyle(color: context.colorScheme.tertiary, fontWeight: FontWeight.bold), - ).tr(), + const SizedBox(height: 8), + SizedBox( + width: double.infinity, + height: 48, + + child: FilledButton( + onPressed: onDeleteBackedUpOnly, + style: FilledButton.styleFrom( + backgroundColor: context.colorScheme.errorContainer, + foregroundColor: context.colorScheme.onErrorContainer, + ), + child: const Text( + "delete_local_dialog_ok_backed_up_only", + style: TextStyle(fontWeight: FontWeight.bold), + ).tr(), + ), ), - TextButton( - onPressed: onForceDelete, - child: Text( - "delete_local_dialog_ok_force", - style: TextStyle(color: Colors.red[400], fontWeight: FontWeight.bold), - ).tr(), + const SizedBox(height: 8), + SizedBox( + width: double.infinity, + height: 48, + child: FilledButton( + onPressed: onForceDelete, + style: FilledButton.styleFrom(backgroundColor: Colors.red[400], foregroundColor: Colors.white), + child: const Text("delete_local_dialog_ok_force", style: TextStyle(fontWeight: FontWeight.bold)).tr(), + ), ), ], ); diff --git a/mobile/lib/widgets/asset_grid/multiselect_grid.dart b/mobile/lib/widgets/asset_grid/multiselect_grid.dart index da2957c027..c0d8a6bea2 100644 --- a/mobile/lib/widgets/asset_grid/multiselect_grid.dart +++ b/mobile/lib/widgets/asset_grid/multiselect_grid.dart @@ -314,10 +314,10 @@ class MultiselectGrid extends HookConsumerWidget { final result = await ref.read(albumServiceProvider).createAlbumWithGeneratedName(assets); if (result != null) { - ref.watch(albumProvider.notifier).refreshRemoteAlbums(); + unawaited(ref.watch(albumProvider.notifier).refreshRemoteAlbums()); selectionEnabledHook.value = false; - context.pushRoute(AlbumViewerRoute(albumId: result.id)); + unawaited(context.pushRoute(AlbumViewerRoute(albumId: result.id))); } } finally { processing.value = false; @@ -346,7 +346,7 @@ class MultiselectGrid extends HookConsumerWidget { ); if (remoteAssets.isNotEmpty) { - handleEditDateTime(ref, context, remoteAssets.toList()); + unawaited(handleEditDateTime(ref, context, remoteAssets.toList())); } } finally { selectionEnabledHook.value = false; @@ -361,7 +361,7 @@ class MultiselectGrid extends HookConsumerWidget { ); if (remoteAssets.isNotEmpty) { - handleEditLocation(ref, context, remoteAssets.toList()); + unawaited(handleEditLocation(ref, context, remoteAssets.toList())); } } finally { selectionEnabledHook.value = false; diff --git a/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart b/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart index 00f7bc494d..5707e3678f 100644 --- a/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart +++ b/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:auto_route/auto_route.dart'; @@ -81,7 +82,7 @@ class BottomGalleryBar extends ConsumerWidget { // to not throw the error when the next preCache index is called if (totalAssets.value == 1 || assetIndex.value == totalAssets.value - 1) { // Handle only one asset - context.maybePop(); + await context.maybePop(); } totalAssets.value -= 1; @@ -111,18 +112,20 @@ class BottomGalleryBar extends ConsumerWidget { } // Asset is permanently removed - showDialog( - context: context, - builder: (BuildContext _) { - return DeleteDialog( - onDelete: () async { - final isDeleted = await onDelete(true); - if (isDeleted) { - removeAssetFromStack(); - } - }, - ); - }, + unawaited( + showDialog( + context: context, + builder: (BuildContext _) { + return DeleteDialog( + onDelete: () async { + final isDeleted = await onDelete(true); + if (isDeleted) { + removeAssetFromStack(); + } + }, + ); + }, + ), ); } @@ -150,7 +153,7 @@ class BottomGalleryBar extends ConsumerWidget { onTap: () async { await unStack(); ctx.pop(); - context.maybePop(); + await context.maybePop(); }, title: const Text("viewer_unstack", style: TextStyle(fontWeight: FontWeight.bold)).tr(), ), @@ -178,9 +181,11 @@ class BottomGalleryBar extends ConsumerWidget { void handleEdit() async { final image = Image(image: ImmichImage.imageProvider(asset: asset)); - context.navigator.push( - MaterialPageRoute( - builder: (context) => EditImagePage(asset: asset, image: image, isEdited: false), + unawaited( + context.navigator.push( + MaterialPageRoute( + builder: (context) => EditImagePage(asset: asset, image: image, isEdited: false), + ), ), ); } diff --git a/mobile/lib/widgets/asset_viewer/cast_dialog.dart b/mobile/lib/widgets/asset_viewer/cast_dialog.dart index f7c80cca3d..d406f29a22 100644 --- a/mobile/lib/widgets/asset_viewer/cast_dialog.dart +++ b/mobile/lib/widgets/asset_viewer/cast_dialog.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -93,7 +95,7 @@ class CastDialog extends ConsumerWidget { } if (!isCurrentDevice(deviceName)) { - ref.read(castProvider.notifier).connect(type, deviceObj); + unawaited(ref.read(castProvider.notifier).connect(type, deviceObj)); } }, ); diff --git a/mobile/lib/widgets/asset_viewer/detail_panel/exif_map.dart b/mobile/lib/widgets/asset_viewer/detail_panel/exif_map.dart index 0edafa88c5..893e534084 100644 --- a/mobile/lib/widgets/asset_viewer/detail_panel/exif_map.dart +++ b/mobile/lib/widgets/asset_viewer/detail_panel/exif_map.dart @@ -1,11 +1,12 @@ +import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:immich_mobile/domain/models/exif.model.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; import 'package:immich_mobile/widgets/map/map_thumbnail.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'package:immich_mobile/utils/debug_print.dart'; class ExifMap extends StatelessWidget { final ExifInfo exifInfo; @@ -68,7 +69,7 @@ class ExifMap extends StatelessWidget { } dPrint(() => 'Opening Map Uri: $uri'); - launchUrl(uri); + unawaited(launchUrl(uri)); }, onCreated: onMapCreated, ); diff --git a/mobile/lib/widgets/asset_viewer/video_position.dart b/mobile/lib/widgets/asset_viewer/video_position.dart index c12bb5e682..9d9e2821ad 100644 --- a/mobile/lib/widgets/asset_viewer/video_position.dart +++ b/mobile/lib/widgets/asset_viewer/video_position.dart @@ -61,7 +61,7 @@ class VideoPosition extends HookConsumerWidget { return; } - ref.read(videoPlayerControlsProvider.notifier).position = seekToDuration.inSeconds.toDouble(); + ref.read(videoPlayerControlsProvider.notifier).position = seekToDuration; // This immediately updates the slider position without waiting for the video to update ref.read(videoPlaybackValueProvider.notifier).position = seekToDuration; diff --git a/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart b/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart index e504cf0675..c6a557964d 100644 --- a/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart +++ b/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart @@ -1,19 +1,21 @@ +import 'dart:async'; + import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart' hide Store; -import 'package:immich_mobile/entities/store.entity.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/models/backup/backup_state.model.dart'; import 'package:immich_mobile/providers/asset.provider.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart'; import 'package:immich_mobile/providers/backup/manual_upload.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; import 'package:immich_mobile/providers/locale_provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/utils/bytes_units.dart'; import 'package:immich_mobile/widgets/common/app_bar_dialog/app_bar_profile_info.dart'; @@ -44,19 +46,24 @@ class ImmichAppBarDialog extends HookConsumerWidget { }, []); buildTopRow() { - return Stack( - children: [ - Align( - alignment: Alignment.topLeft, - child: InkWell(onTap: () => context.pop(), child: const Icon(Icons.close, size: 20)), - ), - Center( - child: Image.asset( - context.isDarkTheme ? 'assets/immich-text-dark.png' : 'assets/immich-text-light.png', - height: 16, + return SizedBox( + height: 56, + child: Stack( + alignment: Alignment.centerLeft, + children: [ + IconButton(onPressed: () => context.pop(), icon: const Icon(Icons.close, size: 20)), + Align( + alignment: Alignment.center, + child: Padding( + padding: const EdgeInsets.only(bottom: 4), + child: Image.asset( + context.isDarkTheme ? 'assets/immich-text-dark.png' : 'assets/immich-text-light.png', + height: 16, + ), + ), ), - ), - ], + ], + ), ); } @@ -97,25 +104,27 @@ class ImmichAppBarDialog extends HookConsumerWidget { return; } - showDialog( - context: context, - builder: (BuildContext ctx) { - return ConfirmDialog( - title: "app_bar_signout_dialog_title", - content: "app_bar_signout_dialog_content", - ok: "yes", - onOk: () async { - isLoggingOut.value = true; - await ref.read(authProvider.notifier).logout().whenComplete(() => isLoggingOut.value = false); + unawaited( + showDialog( + context: context, + builder: (BuildContext ctx) { + return ConfirmDialog( + title: "app_bar_signout_dialog_title", + content: "app_bar_signout_dialog_content", + ok: "yes", + onOk: () async { + isLoggingOut.value = true; + await ref.read(authProvider.notifier).logout().whenComplete(() => isLoggingOut.value = false); - ref.read(manualUploadProvider.notifier).cancelBackup(); - ref.read(backupProvider.notifier).cancelBackup(); - ref.read(assetProvider.notifier).clearAllAssets(); - ref.read(websocketProvider.notifier).disconnect(); - context.replaceRoute(const LoginRoute()); - }, - ); - }, + ref.read(manualUploadProvider.notifier).cancelBackup(); + ref.read(backupProvider.notifier).cancelBackup(); + unawaited(ref.read(assetProvider.notifier).clearAllAssets()); + ref.read(websocketProvider.notifier).disconnect(); + unawaited(context.replaceRoute(const LoginRoute())); + }, + ); + }, + ), ); }, trailing: isLoggingOut.value @@ -256,7 +265,7 @@ class ImmichAppBarDialog extends HookConsumerWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Container(padding: const EdgeInsets.all(20), child: buildTopRow()), + Container(padding: const EdgeInsets.symmetric(horizontal: 8), child: buildTopRow()), const AppBarProfileInfoBox(), buildStorageInformation(), const AppBarServerInfo(), diff --git a/mobile/lib/widgets/common/app_bar_dialog/app_bar_profile_info.dart b/mobile/lib/widgets/common/app_bar_dialog/app_bar_profile_info.dart index 00366ca580..bc1d608b10 100644 --- a/mobile/lib/widgets/common/app_bar_dialog/app_bar_profile_info.dart +++ b/mobile/lib/widgets/common/app_bar_dialog/app_bar_profile_info.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -6,8 +8,8 @@ import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; import 'package:immich_mobile/providers/upload_profile_image.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart'; @@ -54,7 +56,7 @@ class AppBarProfileInfoBox extends HookConsumerWidget { ref.read(currentUserProvider.notifier).refresh(); } - ref.read(backupProvider.notifier).updateDiskInfo(); + unawaited(ref.read(backupProvider.notifier).updateDiskInfo()); } } } diff --git a/mobile/lib/widgets/common/app_bar_dialog/app_bar_server_info.dart b/mobile/lib/widgets/common/app_bar_dialog/app_bar_server_info.dart index 4aacfb3322..a83a3beee3 100644 --- a/mobile/lib/widgets/common/app_bar_dialog/app_bar_server_info.dart +++ b/mobile/lib/widgets/common/app_bar_dialog/app_bar_server_info.dart @@ -7,7 +7,9 @@ import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/models/server_info/server_info.model.dart'; import 'package:immich_mobile/providers/locale_provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/utils/url_helper.dart'; +import 'package:immich_mobile/widgets/common/app_bar_dialog/server_update_notification.dart'; import 'package:package_info_plus/package_info_plus.dart'; class AppBarServerInfo extends HookConsumerWidget { @@ -17,6 +19,8 @@ class AppBarServerInfo extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { ref.watch(localeProvider); ServerInfo serverInfoState = ref.watch(serverInfoProvider); + final user = ref.watch(currentUserProvider); + final bool showVersionWarning = ref.watch(versionWarningPresentProvider(user)); final appInfo = useState({}); const titleFontSize = 12.0; @@ -45,17 +49,10 @@ class AppBarServerInfo extends HookConsumerWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - serverInfoState.isVersionMismatch - ? serverInfoState.versionMismatchErrorMessage - : "profile_drawer_client_server_up_to_date".tr(), - textAlign: TextAlign.center, - style: TextStyle(fontSize: 11, color: context.primaryColor, fontWeight: FontWeight.w500), - ), - ), - const Padding(padding: EdgeInsets.symmetric(horizontal: 10), child: Divider(thickness: 1)), + if (showVersionWarning) ...[ + const Padding(padding: EdgeInsets.symmetric(horizontal: 8.0), child: ServerUpdateNotification()), + const Padding(padding: EdgeInsets.symmetric(horizontal: 10), child: Divider(thickness: 1)), + ], Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -182,7 +179,7 @@ class AppBarServerInfo extends HookConsumerWidget { padding: const EdgeInsets.only(left: 10.0), child: Row( children: [ - if (serverInfoState.isNewReleaseAvailable) + if (serverInfoState.versionStatus == VersionStatus.serverOutOfDate) const Padding( padding: EdgeInsets.only(right: 5.0), child: Icon(Icons.info, color: Color.fromARGB(255, 243, 188, 106), size: 12), diff --git a/mobile/lib/widgets/common/app_bar_dialog/server_update_notification.dart b/mobile/lib/widgets/common/app_bar_dialog/server_update_notification.dart new file mode 100644 index 0000000000..6068ee022e --- /dev/null +++ b/mobile/lib/widgets/common/app_bar_dialog/server_update_notification.dart @@ -0,0 +1,83 @@ +import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/constants.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/models/server_info/server_info.model.dart'; +import 'package:immich_mobile/providers/server_info.provider.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +class ServerUpdateNotification extends HookConsumerWidget { + const ServerUpdateNotification({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final serverInfoState = ref.watch(serverInfoProvider); + + Color errorColor = const Color.fromARGB(85, 253, 97, 83); + Color infoColor = context.isDarkTheme ? context.primaryColor.withAlpha(55) : context.primaryColor.withAlpha(25); + void openUpdateLink() { + String url; + if (serverInfoState.versionStatus == VersionStatus.serverOutOfDate) { + url = kImmichLatestRelease; + } else { + if (Platform.isIOS) { + url = kImmichAppStoreLink; + } else if (Platform.isAndroid) { + url = kImmichPlayStoreLink; + } else { + // Fallback to latest release for other/unknown platforms + url = kImmichLatestRelease; + } + } + + launchUrlString(url, mode: LaunchMode.externalApplication); + } + + return SizedBox( + width: double.infinity, + child: Container( + decoration: BoxDecoration( + color: serverInfoState.versionStatus == VersionStatus.error ? errorColor : infoColor, + borderRadius: const BorderRadius.all(Radius.circular(8)), + border: Border.all( + color: serverInfoState.versionStatus == VersionStatus.error + ? errorColor + : context.primaryColor.withAlpha(50), + width: 0.75, + ), + ), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + serverInfoState.versionStatus.message, + textAlign: TextAlign.start, + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: context.textTheme.labelLarge, + ), + if (serverInfoState.versionStatus == VersionStatus.serverOutOfDate || + serverInfoState.versionStatus == VersionStatus.clientOutOfDate) ...[ + const Spacer(), + TextButton( + onPressed: openUpdateLink, + style: TextButton.styleFrom( + padding: const EdgeInsets.all(4), + minimumSize: const Size(0, 0), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + child: serverInfoState.versionStatus == VersionStatus.clientOutOfDate + ? Text("action_common_update".tr(context: context)) + : Text("view".tr()), + ), + ], + ], + ), + ), + ); + } +} diff --git a/mobile/lib/widgets/common/feature_check.dart b/mobile/lib/widgets/common/feature_check.dart new file mode 100644 index 0000000000..ebaa0acfe7 --- /dev/null +++ b/mobile/lib/widgets/common/feature_check.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/models/server_info/server_features.model.dart'; +import 'package:immich_mobile/providers/server_info.provider.dart'; + +/// A utility widget that conditionally renders its child based on a server feature flag. +/// +/// Example usage: +/// ```dart +/// FeatureCheck( +/// feature: (features) => features.ocr, +/// child: Text('OCR is enabled'), +/// fallback: Text('OCR is not available'), +/// ) +/// ``` +class FeatureCheck extends ConsumerWidget { + /// A function that extracts the specific feature flag from ServerFeatures + final bool Function(ServerFeatures) feature; + + /// The widget to display when the feature is enabled + final Widget child; + + /// Optional widget to display when the feature is disabled + final Widget? fallback; + + const FeatureCheck({super.key, required this.feature, required this.child, this.fallback}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final serverFeatures = ref.watch(serverInfoProvider.select((s) => s.serverFeatures)); + final isFeatureEnabled = feature(serverFeatures); + if (isFeatureEnabled) { + return child; + } + + return fallback ?? const SizedBox.shrink(); + } +} diff --git a/mobile/lib/widgets/common/immich_app_bar.dart b/mobile/lib/widgets/common/immich_app_bar.dart index 7eaedd27b5..2bac100807 100644 --- a/mobile/lib/widgets/common/immich_app_bar.dart +++ b/mobile/lib/widgets/common/immich_app_bar.dart @@ -6,7 +6,6 @@ import 'package:flutter_svg/svg.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/models/backup/backup_state.model.dart'; -import 'package:immich_mobile/models/server_info/server_info.model.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart'; import 'package:immich_mobile/providers/cast.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; @@ -28,8 +27,8 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { Widget build(BuildContext context, WidgetRef ref) { final BackUpState backupState = ref.watch(backupProvider); final bool isEnableAutoBackup = backupState.backgroundBackup || backupState.autoBackup; - final ServerInfo serverInfoState = ref.watch(serverInfoProvider); final user = ref.watch(currentUserProvider); + final bool versionWarningPresent = ref.watch(versionWarningPresentProvider(user)); final isDarkTheme = context.isDarkTheme; const widgetSize = 30.0; final isCasting = ref.watch(castProvider.select((c) => c.isCasting)); @@ -46,8 +45,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { ), backgroundColor: Colors.transparent, alignment: Alignment.bottomRight, - isLabelVisible: - serverInfoState.isVersionMismatch || ((user?.isAdmin ?? false) && serverInfoState.isNewReleaseAvailable), + isLabelVisible: versionWarningPresent, offset: const Offset(-2, -12), child: user == null ? const Icon(Icons.face_outlined, size: widgetSize) @@ -129,19 +127,24 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { title: Builder( builder: (BuildContext context) { return Row( + crossAxisAlignment: CrossAxisAlignment.center, children: [ - Builder( - builder: (context) { - return Padding( - padding: const EdgeInsets.only(top: 3.0), - child: SvgPicture.asset( - context.isDarkTheme - ? 'assets/immich-logo-inline-dark.svg' - : 'assets/immich-logo-inline-light.svg', - height: 40, - ), - ); - }, + Padding( + padding: const EdgeInsets.only(top: 3.0), + child: SvgPicture.asset( + context.isDarkTheme ? 'assets/immich-logo-inline-dark.svg' : 'assets/immich-logo-inline-light.svg', + height: 40, + ), + ), + const Tooltip( + triggerMode: TooltipTriggerMode.tap, + showDuration: Duration(seconds: 4), + message: + "The old timeline is deprecated and will be removed in a future release. Kindly switch to the new timeline under Advanced Settings.", + child: Padding( + padding: EdgeInsets.only(top: 3.0), + child: Icon(Icons.error_rounded, fill: 1, color: Colors.amber, size: 20), + ), ), ], ); diff --git a/mobile/lib/widgets/common/immich_sliver_app_bar.dart b/mobile/lib/widgets/common/immich_sliver_app_bar.dart index 09c0518a23..f68d5c9fda 100644 --- a/mobile/lib/widgets/common/immich_sliver_app_bar.dart +++ b/mobile/lib/widgets/common/immich_sliver_app_bar.dart @@ -9,8 +9,8 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/models/server_info/server_info.model.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; import 'package:immich_mobile/providers/cast.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/sync_status.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; @@ -118,8 +118,10 @@ class _ProfileIndicator extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final ServerInfo serverInfoState = ref.watch(serverInfoProvider); final user = ref.watch(currentUserProvider); + final bool versionWarningPresent = ref.watch(versionWarningPresentProvider(user)); + final serverInfoState = ref.watch(serverInfoProvider); + const widgetSize = 30.0; void toggleReadonlyMode() { @@ -143,13 +145,21 @@ class _ProfileIndicator extends ConsumerWidget { borderRadius: const BorderRadius.all(Radius.circular(12)), child: Badge( label: Container( - decoration: BoxDecoration(color: Colors.black, borderRadius: BorderRadius.circular(widgetSize / 2)), - child: const Icon(Icons.info, color: Color.fromARGB(255, 243, 188, 106), size: widgetSize / 2), + decoration: BoxDecoration( + color: context.isDarkTheme ? Colors.black : Colors.white, + borderRadius: BorderRadius.circular(widgetSize / 2), + ), + child: Icon( + Icons.info, + color: serverInfoState.versionStatus == VersionStatus.error + ? context.colorScheme.error + : context.primaryColor, + size: widgetSize / 2, + ), ), backgroundColor: Colors.transparent, alignment: Alignment.bottomRight, - isLabelVisible: - serverInfoState.isVersionMismatch || ((user?.isAdmin ?? false) && serverInfoState.isNewReleaseAvailable), + isLabelVisible: versionWarningPresent, offset: const Offset(-2, -12), child: user == null ? const Icon(Icons.face_outlined, size: widgetSize) @@ -162,40 +172,32 @@ class _ProfileIndicator extends ConsumerWidget { } } +const double _kBadgeWidgetSize = 30.0; + class _BackupIndicator extends ConsumerWidget { const _BackupIndicator(); @override Widget build(BuildContext context, WidgetRef ref) { - const widgetSize = 30.0; final indicatorIcon = _getBackupBadgeIcon(context, ref); - final badgeBackground = context.colorScheme.surfaceContainer; return InkWell( onTap: () => context.pushRoute(const DriftBackupRoute()), borderRadius: const BorderRadius.all(Radius.circular(12)), child: Badge( - label: Container( - width: widgetSize / 2, - height: widgetSize / 2, - decoration: BoxDecoration( - color: badgeBackground, - border: Border.all(color: context.colorScheme.outline.withValues(alpha: .3)), - borderRadius: BorderRadius.circular(widgetSize / 2), - ), - child: indicatorIcon, - ), + label: indicatorIcon, backgroundColor: Colors.transparent, alignment: Alignment.bottomRight, isLabelVisible: indicatorIcon != null, offset: const Offset(-2, -12), - child: Icon(Icons.backup_rounded, size: widgetSize, color: context.primaryColor), + child: Icon(Icons.backup_rounded, size: _kBadgeWidgetSize, color: context.primaryColor), ), ); } Widget? _getBackupBadgeIcon(BuildContext context, WidgetRef ref) { final backupStateStream = ref.watch(settingsProvider).watch(Setting.enableBackup); + final hasError = ref.watch(driftBackupProvider.select((state) => state.error != BackupError.none)); final isDarkTheme = context.isDarkTheme; final iconColor = isDarkTheme ? Colors.white : Colors.black; final isUploading = ref.watch(driftBackupProvider.select((state) => state.uploadItems.isNotEmpty)); @@ -207,42 +209,76 @@ class _BackupIndicator extends ConsumerWidget { final backupEnabled = snapshot.data ?? false; if (!backupEnabled) { - return Icon( - Icons.cloud_off_rounded, - size: 9, - color: iconColor, - semanticLabel: 'backup_controller_page_backup'.tr(), + return _BadgeLabel( + Icon( + Icons.cloud_off_rounded, + size: 9, + color: iconColor, + semanticLabel: 'backup_controller_page_backup'.tr(), + ), + ); + } + + if (hasError) { + return _BadgeLabel( + Icon( + Icons.warning_rounded, + size: 12, + color: context.colorScheme.error, + semanticLabel: 'backup_controller_page_backup'.tr(), + ), + backgroundColor: context.colorScheme.errorContainer, ); } if (isUploading) { - return Container( - padding: const EdgeInsets.all(3.5), - child: Theme( - data: context.themeData.copyWith( - progressIndicatorTheme: context.themeData.progressIndicatorTheme.copyWith(year2023: true), - ), - child: CircularProgressIndicator( - strokeWidth: 2, - strokeCap: StrokeCap.round, - valueColor: AlwaysStoppedAnimation(iconColor), - semanticsLabel: 'backup_controller_page_backup'.tr(), + return _BadgeLabel( + Container( + padding: const EdgeInsets.all(3.5), + child: Theme( + data: context.themeData.copyWith( + progressIndicatorTheme: context.themeData.progressIndicatorTheme.copyWith(year2023: true), + ), + child: CircularProgressIndicator( + strokeWidth: 2, + strokeCap: StrokeCap.round, + valueColor: AlwaysStoppedAnimation(iconColor), + semanticsLabel: 'backup_controller_page_backup'.tr(), + ), ), ), ); } - return Icon( - Icons.check_outlined, - size: 9, - color: iconColor, - semanticLabel: 'backup_controller_page_backup'.tr(), + return _BadgeLabel( + Icon(Icons.check_outlined, size: 9, color: iconColor, semanticLabel: 'backup_controller_page_backup'.tr()), ); }, ); } } +class _BadgeLabel extends StatelessWidget { + final Widget indicator; + final Color? backgroundColor; + + const _BadgeLabel(this.indicator, {this.backgroundColor}); + + @override + Widget build(BuildContext context) { + return Container( + width: _kBadgeWidgetSize / 2, + height: _kBadgeWidgetSize / 2, + decoration: BoxDecoration( + color: backgroundColor ?? context.colorScheme.surfaceContainer, + border: Border.all(color: context.colorScheme.outline.withValues(alpha: .3)), + borderRadius: BorderRadius.circular(_kBadgeWidgetSize / 2), + ), + child: indicator, + ); + } +} + class _SyncStatusIndicator extends ConsumerStatefulWidget { const _SyncStatusIndicator(); diff --git a/mobile/lib/widgets/common/remote_album_sliver_app_bar.dart b/mobile/lib/widgets/common/remote_album_sliver_app_bar.dart index f75dd6e803..c0661bad48 100644 --- a/mobile/lib/widgets/common/remote_album_sliver_app_bar.dart +++ b/mobile/lib/widgets/common/remote_album_sliver_app_bar.dart @@ -18,7 +18,6 @@ import 'package:immich_mobile/providers/infrastructure/current_album.provider.da import 'package:immich_mobile/providers/infrastructure/remote_album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; -import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/widgets/album/remote_album_shared_user_icons.dart'; class RemoteAlbumSliverAppBar extends ConsumerStatefulWidget { @@ -89,7 +88,7 @@ class _MesmerizingSliverAppBarState extends ConsumerState context.navigateTo(const TabShellRoute(children: [DriftAlbumsRoute()])), + onPressed: () => context.maybePop(), ), actions: [ if (widget.onToggleAlbumOrder != null) diff --git a/mobile/lib/widgets/forms/login/login_form.dart b/mobile/lib/widgets/forms/login/login_form.dart index f100b58649..f810973298 100644 --- a/mobile/lib/widgets/forms/login/login_form.dart +++ b/mobile/lib/widgets/forms/login/login_form.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:math'; @@ -20,6 +21,7 @@ import 'package:immich_mobile/providers/gallery_permission.provider.dart'; import 'package:immich_mobile/providers/oauth.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart'; +import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/utils/provider_utils.dart'; import 'package:immich_mobile/utils/url_helper.dart'; @@ -176,6 +178,55 @@ class LoginForm extends HookConsumerWidget { } } + getManageMediaPermission() async { + final hasPermission = await ref.read(localFilesManagerRepositoryProvider).hasManageMediaPermission(); + if (!hasPermission) { + await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10))), + elevation: 5, + title: Text( + 'manage_media_access_title', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: context.primaryColor), + ).tr(), + content: SingleChildScrollView( + child: ListBody( + children: [ + const Text('manage_media_access_subtitle', style: TextStyle(fontSize: 14)).tr(), + const SizedBox(height: 4), + const Text('manage_media_access_rationale', style: TextStyle(fontSize: 12)).tr(), + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text( + 'cancel'.tr(), + style: TextStyle(fontWeight: FontWeight.w600, color: context.primaryColor), + ), + ), + TextButton( + onPressed: () { + ref.read(localFilesManagerRepositoryProvider).requestManageMediaPermission(); + Navigator.of(context).pop(); + }, + child: Text( + 'manage_media_access_settings'.tr(), + style: TextStyle(fontWeight: FontWeight.w600, color: context.primaryColor), + ), + ), + ], + ); + }, + ); + } + } + + bool isSyncRemoteDeletionsMode() => Platform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false); + login() async { TextInput.finishAutofillContext(); @@ -188,17 +239,20 @@ class LoginForm extends HookConsumerWidget { final result = await ref.read(authProvider.notifier).login(emailController.text, passwordController.text); if (result.shouldChangePassword && !result.isAdmin) { - context.pushRoute(const ChangePasswordRoute()); + unawaited(context.pushRoute(const ChangePasswordRoute())); } else { final isBeta = Store.isBetaTimelineEnabled; if (isBeta) { await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission(); - handleSyncFlow(); + if (isSyncRemoteDeletionsMode()) { + await getManageMediaPermission(); + } + unawaited(handleSyncFlow()); ref.read(websocketProvider.notifier).connect(); - context.replaceRoute(const TabShellRoute()); + unawaited(context.replaceRoute(const TabShellRoute())); return; } - context.replaceRoute(const TabControllerRoute()); + unawaited(context.replaceRoute(const TabControllerRoute())); } } catch (error) { ImmichToast.show( @@ -288,15 +342,18 @@ class LoginForm extends HookConsumerWidget { final permission = ref.watch(galleryPermissionNotifier); final isBeta = Store.isBetaTimelineEnabled; if (!isBeta && (permission.isGranted || permission.isLimited)) { - ref.watch(backupProvider.notifier).resumeBackup(); + unawaited(ref.watch(backupProvider.notifier).resumeBackup()); } if (isBeta) { await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission(); - handleSyncFlow(); - context.replaceRoute(const TabShellRoute()); + if (isSyncRemoteDeletionsMode()) { + await getManageMediaPermission(); + } + unawaited(handleSyncFlow()); + unawaited(context.replaceRoute(const TabShellRoute())); return; } - context.replaceRoute(const TabControllerRoute()); + unawaited(context.replaceRoute(const TabControllerRoute())); } } catch (error, stack) { log.severe('Error logging in with OAuth: $error', stack); diff --git a/mobile/lib/widgets/map/map_bottom_sheet.dart b/mobile/lib/widgets/map/map_bottom_sheet.dart index baf85e8075..fba9e9a041 100644 --- a/mobile/lib/widgets/map/map_bottom_sheet.dart +++ b/mobile/lib/widgets/map/map_bottom_sheet.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/models/map/map_event.model.dart'; -import 'package:immich_mobile/widgets/map/map_asset_grid.dart'; -import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/utils/draggable_scroll_controller.dart'; +import 'package:immich_mobile/widgets/map/map_asset_grid.dart'; class MapBottomSheet extends HookConsumerWidget { final Stream mapEventStream; @@ -34,7 +34,11 @@ class MapBottomSheet extends HookConsumerWidget { void handleMapEvents(MapEvent event) async { if (event is MapCloseBottomSheet) { - sheetController.animateTo(0.1, duration: const Duration(milliseconds: 200), curve: Curves.linearToEaseOut); + await sheetController.animateTo( + 0.1, + duration: const Duration(milliseconds: 200), + curve: Curves.linearToEaseOut, + ); } } diff --git a/mobile/lib/widgets/search/search_filter/filter_bottom_sheet_scaffold.dart b/mobile/lib/widgets/search/search_filter/filter_bottom_sheet_scaffold.dart index e8226b5b3a..dee42ec5a0 100644 --- a/mobile/lib/widgets/search/search_filter/filter_bottom_sheet_scaffold.dart +++ b/mobile/lib/widgets/search/search_filter/filter_bottom_sheet_scaffold.dart @@ -6,7 +6,7 @@ class FilterBottomSheetScaffold extends StatelessWidget { const FilterBottomSheetScaffold({ super.key, required this.child, - required this.onSearch, + this.onSearch, required this.onClear, required this.title, this.expanded, @@ -15,7 +15,7 @@ class FilterBottomSheetScaffold extends StatelessWidget { final bool? expanded; final String title; final Widget child; - final Function() onSearch; + final Function()? onSearch; final Function() onClear; @override @@ -48,15 +48,16 @@ class FilterBottomSheetScaffold extends StatelessWidget { }, child: const Text('clear').tr(), ), - const SizedBox(width: 8), - ElevatedButton( - key: const Key('search_filter_apply'), - onPressed: () { - onSearch(); - context.pop(); - }, - child: const Text('search_filter_apply').tr(), - ), + if (onSearch != null) const SizedBox(width: 8), + if (onSearch != null) + ElevatedButton( + key: const Key('search_filter_apply'), + onPressed: () { + onSearch!(); + context.pop(); + }, + child: const Text('search_filter_apply').tr(), + ), ], ), ), diff --git a/mobile/lib/widgets/settings/advanced_settings.dart b/mobile/lib/widgets/settings/advanced_settings.dart index 7a107b47d8..aee28c9449 100644 --- a/mobile/lib/widgets/settings/advanced_settings.dart +++ b/mobile/lib/widgets/settings/advanced_settings.dart @@ -8,15 +8,16 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/services/log.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; import 'package:immich_mobile/utils/http_ssl_options.dart'; import 'package:immich_mobile/widgets/settings/beta_timeline_list_tile.dart'; -import 'package:immich_mobile/widgets/settings/custom_proxy_headers_settings/custome_proxy_headers_settings.dart'; +import 'package:immich_mobile/widgets/settings/custom_proxy_headers_settings/custom_proxy_headers_settings.dart'; import 'package:immich_mobile/widgets/settings/local_storage_settings.dart'; +import 'package:immich_mobile/widgets/settings/settings_action_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart'; import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; @@ -25,12 +26,15 @@ import 'package:logging/logging.dart'; class AdvancedSettings extends HookConsumerWidget { const AdvancedSettings({super.key}); + @override Widget build(BuildContext context, WidgetRef ref) { bool isLoggedIn = ref.read(currentUserProvider) != null; final advancedTroubleshooting = useAppSettingsState(AppSettingsEnum.advancedTroubleshooting); final manageLocalMediaAndroid = useAppSettingsState(AppSettingsEnum.manageLocalMediaAndroid); + final isManageMediaSupported = useState(false); + final manageMediaAndroidPermission = useState(false); final levelId = useAppSettingsState(AppSettingsEnum.logLevel); final preferRemote = useAppSettingsState(AppSettingsEnum.preferRemoteImage); final allowSelfSignedSSLCert = useAppSettingsState(AppSettingsEnum.allowSelfSignedSSLCert); @@ -51,6 +55,18 @@ class AdvancedSettings extends HookConsumerWidget { return false; } + useEffect(() { + () async { + isManageMediaSupported.value = await checkAndroidVersion(); + if (isManageMediaSupported.value) { + manageMediaAndroidPermission.value = await ref + .read(localFilesManagerRepositoryProvider) + .hasManageMediaPermission(); + } + }(); + return null; + }, []); + final advancedSettings = [ SettingsSwitchListTile( enabled: true, @@ -58,11 +74,10 @@ class AdvancedSettings extends HookConsumerWidget { title: "advanced_settings_troubleshooting_title".tr(), subtitle: "advanced_settings_troubleshooting_subtitle".tr(), ), - FutureBuilder( - future: checkAndroidVersion(), - builder: (context, snapshot) { - if (snapshot.hasData && snapshot.data == true) { - return SettingsSwitchListTile( + if (isManageMediaSupported.value) + Column( + children: [ + SettingsSwitchListTile( enabled: true, valueNotifier: manageLocalMediaAndroid, title: "advanced_settings_sync_remote_deletions_title".tr(), @@ -71,14 +86,24 @@ class AdvancedSettings extends HookConsumerWidget { if (value) { final result = await ref.read(localFilesManagerRepositoryProvider).requestManageMediaPermission(); manageLocalMediaAndroid.value = result; + manageMediaAndroidPermission.value = result; } }, - ); - } else { - return const SizedBox.shrink(); - } - }, - ), + ), + SettingsActionTile( + title: "manage_media_access_title".tr(), + statusText: manageMediaAndroidPermission.value ? "allowed".tr() : "not_allowed".tr(), + subtitle: "manage_media_access_rationale".tr(), + statusColor: manageLocalMediaAndroid.value && !manageMediaAndroidPermission.value + ? const Color.fromARGB(255, 243, 188, 106) + : null, + onActionTap: () async { + final result = await ref.read(localFilesManagerRepositoryProvider).manageMediaPermission(); + manageMediaAndroidPermission.value = result; + }, + ), + ], + ), SettingsSliderListTile( text: "advanced_settings_log_level_title".tr(namedArgs: {'level': logLevel}), valueNotifier: levelId, @@ -100,7 +125,7 @@ class AdvancedSettings extends HookConsumerWidget { subtitle: "advanced_settings_self_signed_ssl_subtitle".tr(), onChanged: HttpSSLOptions.applyFromSettings, ), - const CustomeProxyHeaderSettings(), + const CustomProxyHeaderSettings(), SslClientCertSettings(isLoggedIn: ref.read(currentUserProvider) != null), if (!Store.isBetaTimelineEnabled) SettingsSwitchListTile( diff --git a/mobile/lib/widgets/settings/asset_viewer_settings/video_viewer_settings.dart b/mobile/lib/widgets/settings/asset_viewer_settings/video_viewer_settings.dart index 1d8d9812be..9a89b7e1e3 100644 --- a/mobile/lib/widgets/settings/asset_viewer_settings/video_viewer_settings.dart +++ b/mobile/lib/widgets/settings/asset_viewer_settings/video_viewer_settings.dart @@ -14,11 +14,18 @@ class VideoViewerSettings extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final useLoopVideo = useAppSettingsState(AppSettingsEnum.loopVideo); final useOriginalVideo = useAppSettingsState(AppSettingsEnum.loadOriginalVideo); + final useAutoPlayVideo = useAppSettingsState(AppSettingsEnum.autoPlayVideo); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SettingsSubTitle(title: "videos".tr()), + SettingsSwitchListTile( + valueNotifier: useAutoPlayVideo, + title: "setting_video_viewer_auto_play_title".tr(), + subtitle: "setting_video_viewer_auto_play_subtitle".tr(), + onChanged: (_) => ref.invalidate(appSettingsServiceProvider), + ), SettingsSwitchListTile( valueNotifier: useLoopVideo, title: "setting_video_viewer_looping_title".tr(), diff --git a/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart b/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart index 56506072c5..0296a6bd99 100644 --- a/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart +++ b/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart @@ -3,14 +3,18 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/memory.provider.dart'; import 'package:immich_mobile/providers/infrastructure/storage.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/trash_sync.provider.dart'; import 'package:immich_mobile/providers/sync_status.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/widgets/settings/beta_sync_settings/entity_count_tile.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; @@ -42,7 +46,12 @@ class SyncStatusAndActions extends HookConsumerWidget { await dbFile.copy(exportFile.path); - await Share.shareXFiles([XFile(exportFile.path)], text: 'Immich Database Export'); + final size = MediaQuery.of(context).size; + await Share.shareXFiles( + [XFile(exportFile.path)], + text: 'Immich Database Export', + sharePositionOrigin: Rect.fromPoints(Offset.zero, Offset(size.width / 3, size.height)), + ); Future.delayed(const Duration(seconds: 30), () async { if (await exportFile.exists()) { @@ -224,6 +233,7 @@ class _SyncStatsCounts extends ConsumerWidget { final localAlbumService = ref.watch(localAlbumServiceProvider); final remoteAlbumService = ref.watch(remoteAlbumServiceProvider); final memoryService = ref.watch(driftMemoryServiceProvider); + final appSettingsService = ref.watch(appSettingsServiceProvider); Future> loadCounts() async { final assetCounts = assetService.getAssetCounts(); @@ -346,6 +356,44 @@ class _SyncStatsCounts extends ConsumerWidget { ], ), ), + // To be removed once the experimental feature is stable + if (CurrentPlatform.isAndroid && + appSettingsService.getSetting(AppSettingsEnum.manageLocalMediaAndroid)) ...[ + _SectionHeaderText(text: "trash".t(context: context)), + Consumer( + builder: (context, ref, _) { + final counts = ref.watch(trashedAssetsCountProvider); + return counts.when( + data: (c) => Padding( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), + child: Flex( + direction: Axis.horizontal, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + spacing: 8.0, + children: [ + Expanded( + child: EntitiyCountTile( + label: "local".t(context: context), + count: c.total, + icon: Icons.delete_outline, + ), + ), + Expanded( + child: EntitiyCountTile( + label: "hashed_assets".t(context: context), + count: c.hashed, + icon: Icons.tag, + ), + ), + ], + ), + ), + loading: () => const CircularProgressIndicator(), + error: (e, st) => Text('Error: $e'), + ); + }, + ), + ], ], ); }, diff --git a/mobile/lib/widgets/settings/beta_timeline_list_tile.dart b/mobile/lib/widgets/settings/beta_timeline_list_tile.dart index 326d6c49d1..480665e614 100644 --- a/mobile/lib/widgets/settings/beta_timeline_list_tile.dart +++ b/mobile/lib/widgets/settings/beta_timeline_list_tile.dart @@ -1,12 +1,12 @@ +import 'dart:async'; + import 'package:auto_route/auto_route.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; -import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; @@ -16,10 +16,9 @@ class BetaTimelineListTile extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final betaTimelineValue = ref.watch(appSettingsServiceProvider).getSetting(AppSettingsEnum.betaTimeline); - final serverInfo = ref.watch(serverInfoProvider); final auth = ref.watch(authProvider); - if (!auth.isAuthenticated || (serverInfo.serverVersion.minor < 136 && kReleaseMode)) { + if (!auth.isAuthenticated) { return const SizedBox.shrink(); } @@ -45,7 +44,7 @@ class BetaTimelineListTile extends ConsumerWidget { ElevatedButton( onPressed: () async { Navigator.of(context).pop(); - context.router.replaceAll([ChangeExperienceRoute(switchingToBeta: value)]); + unawaited(context.router.replaceAll([ChangeExperienceRoute(switchingToBeta: value)])); }, child: Text("ok".t(context: context)), ), diff --git a/mobile/lib/widgets/settings/custom_proxy_headers_settings/custome_proxy_headers_settings.dart b/mobile/lib/widgets/settings/custom_proxy_headers_settings/custom_proxy_headers_settings.dart similarity index 73% rename from mobile/lib/widgets/settings/custom_proxy_headers_settings/custome_proxy_headers_settings.dart rename to mobile/lib/widgets/settings/custom_proxy_headers_settings/custom_proxy_headers_settings.dart index f0e248b39d..c3bb64faf6 100644 --- a/mobile/lib/widgets/settings/custom_proxy_headers_settings/custome_proxy_headers_settings.dart +++ b/mobile/lib/widgets/settings/custom_proxy_headers_settings/custom_proxy_headers_settings.dart @@ -3,10 +3,11 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; +import 'package:immich_mobile/generated/intl_keys.g.dart'; import 'package:immich_mobile/routing/router.dart'; -class CustomeProxyHeaderSettings extends StatelessWidget { - const CustomeProxyHeaderSettings({super.key}); +class CustomProxyHeaderSettings extends StatelessWidget { + const CustomProxyHeaderSettings({super.key}); @override Widget build(BuildContext context) { @@ -14,11 +15,11 @@ class CustomeProxyHeaderSettings extends StatelessWidget { contentPadding: const EdgeInsets.symmetric(horizontal: 20), dense: true, title: Text( - "headers_settings_tile_title".tr(), + IntlKeys.advanced_settings_proxy_headers_title.tr(), style: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500), ), subtitle: Text( - "headers_settings_tile_subtitle".tr(), + IntlKeys.advanced_settings_proxy_headers_subtitle.tr(), style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary), ), onTap: () => context.pushRoute(const HeaderSettingsRoute()), diff --git a/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart b/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart index 9fbc43a429..21e26c8f1f 100644 --- a/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart +++ b/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; @@ -102,13 +104,13 @@ class LocalNetworkPreference extends HookConsumerWidget { ), ); } else { - saveWifiName(wifiName); + unawaited(saveWifiName(wifiName)); } final serverEndpoint = ref.read(authProvider.notifier).getServerEndpoint(); if (serverEndpoint != null) { - saveLocalEndpoint(serverEndpoint); + unawaited(saveLocalEndpoint(serverEndpoint)); } } diff --git a/mobile/lib/widgets/settings/settings_action_tile.dart b/mobile/lib/widgets/settings/settings_action_tile.dart new file mode 100644 index 0000000000..b2b5988fa5 --- /dev/null +++ b/mobile/lib/widgets/settings/settings_action_tile.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:immich_mobile/extensions/theme_extensions.dart'; + +class SettingsActionTile extends StatelessWidget { + const SettingsActionTile({ + super.key, + required this.title, + required this.subtitle, + required this.onActionTap, + this.statusText, + this.statusColor, + this.contentPadding, + this.titleStyle, + this.subtitleStyle, + }); + + final String title; + final String subtitle; + final String? statusText; + final Color? statusColor; + final VoidCallback onActionTap; + final EdgeInsets? contentPadding; + final TextStyle? titleStyle; + final TextStyle? subtitleStyle; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return ListTile( + isThreeLine: true, + onTap: onActionTap, + titleAlignment: ListTileTitleAlignment.center, + title: Row( + children: [ + Expanded( + child: Text( + title, + style: titleStyle ?? theme.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500, height: 1.5), + ), + ), + if (statusText != null) + Padding( + padding: const EdgeInsets.only(left: 8), + child: Chip( + label: Text( + statusText!, + style: theme.textTheme.labelMedium?.copyWith( + color: statusColor ?? theme.colorScheme.onSurfaceVariant, + ), + ), + backgroundColor: theme.colorScheme.surface, + side: BorderSide(color: statusColor ?? theme.colorScheme.outlineVariant), + shape: StadiumBorder(side: BorderSide(color: statusColor ?? theme.colorScheme.outlineVariant)), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + visualDensity: VisualDensity.compact, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 0), + ), + ), + ], + ), + subtitle: Padding( + padding: const EdgeInsets.only(top: 4.0, right: 18.0), + child: Text( + subtitle, + style: subtitleStyle ?? theme.textTheme.bodyMedium?.copyWith(color: theme.colorScheme.onSurfaceSecondary), + ), + ), + trailing: Icon(Icons.arrow_forward_ios, size: 16, color: theme.colorScheme.onSurfaceVariant), + contentPadding: contentPadding ?? const EdgeInsets.symmetric(horizontal: 20.0, vertical: 8.0), + ); + } +} diff --git a/mobile/lib/wm_executor.dart b/mobile/lib/wm_executor.dart new file mode 100644 index 0000000000..73e882e8e6 --- /dev/null +++ b/mobile/lib/wm_executor.dart @@ -0,0 +1,251 @@ +// part of 'package:worker_manager/worker_manager.dart'; +// ignore_for_file: implementation_imports, avoid_print + +import 'dart:async'; +import 'dart:math'; + +import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; +import 'package:worker_manager/src/number_of_processors/processors_io.dart'; +import 'package:worker_manager/src/worker/worker.dart'; +import 'package:worker_manager/worker_manager.dart'; + +final workerManagerPatch = _Executor(); + +// [-2^54; 2^53] is compatible with dart2js, see core.int doc +const _minId = -9007199254740992; +const _maxId = 9007199254740992; + +class Mixinable { + late final itSelf = this as T; +} + +mixin _ExecutorLogger on Mixinable<_Executor> { + var log = false; + + @mustCallSuper + void init() { + logMessage("${itSelf._isolatesCount} workers have been spawned and initialized"); + } + + void logTaskAdded(String uid) { + logMessage("added task with number $uid"); + } + + @mustCallSuper + void dispose() { + logMessage("worker_manager have been disposed"); + } + + @mustCallSuper + void _cancel(Task task) { + logMessage("Task ${task.id} have been canceled"); + } + + void logMessage(String message) { + if (log) print(message); + } +} + +class _Executor extends Mixinable<_Executor> with _ExecutorLogger { + final _queue = PriorityQueue(); + final _pool = []; + var _nextTaskId = _minId; + var _dynamicSpawning = false; + var _isolatesCount = numberOfProcessors; + + @override + Future init({int? isolatesCount, bool? dynamicSpawning}) async { + if (_pool.isNotEmpty) { + print("worker_manager already warmed up, init is ignored. Dispose before init"); + return; + } + if (isolatesCount != null) { + if (isolatesCount < 0) { + throw Exception("isolatesCount must be greater than 0"); + } + + _isolatesCount = isolatesCount; + } + _dynamicSpawning = dynamicSpawning ?? false; + await _ensureWorkersInitialized(); + super.init(); + } + + @override + Future dispose() async { + _queue.clear(); + for (final worker in _pool) { + worker.kill(); + } + _pool.clear(); + super.dispose(); + } + + Cancelable execute(Execute execution, {WorkPriority priority = WorkPriority.immediately}) { + return _createCancelable(execution: execution, priority: priority); + } + + Cancelable executeNow(ExecuteGentle execution) { + final task = TaskGentle( + id: "", + workPriority: WorkPriority.immediately, + execution: execution, + completer: Completer(), + ); + + Future run() async { + try { + final result = await execution(() => task.canceled); + task.complete(result, null, null); + } catch (error, st) { + task.complete(null, error, st); + } + } + + run(); + return Cancelable(completer: task.completer, onCancel: () => _cancel(task)); + } + + Cancelable executeWithPort( + ExecuteWithPort execution, { + WorkPriority priority = WorkPriority.immediately, + required void Function(T value) onMessage, + }) { + return _createCancelable( + execution: execution, + priority: priority, + onMessage: (message) => onMessage(message as T), + ); + } + + Cancelable executeGentle(ExecuteGentle execution, {WorkPriority priority = WorkPriority.immediately}) { + return _createCancelable(execution: execution, priority: priority); + } + + Cancelable executeGentleWithPort( + ExecuteGentleWithPort execution, { + WorkPriority priority = WorkPriority.immediately, + required void Function(T value) onMessage, + }) { + return _createCancelable( + execution: execution, + priority: priority, + onMessage: (message) => onMessage(message as T), + ); + } + + void _createWorkers() { + for (var i = 0; i < _isolatesCount; i++) { + _pool.add(Worker()); + } + } + + Future _initializeWorkers() async { + await Future.wait(_pool.map((e) => e.initialize())); + } + + Cancelable _createCancelable({ + required Function execution, + WorkPriority priority = WorkPriority.immediately, + void Function(Object value)? onMessage, + }) { + if (_nextTaskId + 1 == _maxId) { + _nextTaskId = _minId; + } + final id = _nextTaskId.toString(); + _nextTaskId++; + late final Task task; + final completer = Completer(); + if (execution is Execute) { + task = TaskRegular(id: id, workPriority: priority, execution: execution, completer: completer); + } else if (execution is ExecuteWithPort) { + task = TaskWithPort( + id: id, + workPriority: priority, + execution: execution, + completer: completer, + onMessage: onMessage!, + ); + } else if (execution is ExecuteGentle) { + task = TaskGentle(id: id, workPriority: priority, execution: execution, completer: completer); + } else if (execution is ExecuteGentleWithPort) { + task = TaskGentleWithPort( + id: id, + workPriority: priority, + execution: execution, + completer: completer, + onMessage: onMessage!, + ); + } + _queue.add(task); + _schedule(); + logTaskAdded(task.id); + return Cancelable(completer: task.completer, onCancel: () => _cancel(task)); + } + + Future _ensureWorkersInitialized() async { + if (_pool.isEmpty) { + _createWorkers(); + if (!_dynamicSpawning) { + await _initializeWorkers(); + final poolSize = _pool.length; + final queueSize = _queue.length; + for (int i = 0; i <= min(poolSize, queueSize); i++) { + _schedule(); + } + } + } + if (_pool.every((worker) => worker.taskId != null)) { + return; + } + if (_dynamicSpawning) { + final freeWorker = _pool.firstWhereOrNull( + (worker) => worker.taskId == null && !worker.initialized && !worker.initializing, + ); + await freeWorker?.initialize(); + _schedule(); + } + } + + void _schedule() { + final availableWorker = _pool.firstWhereOrNull((worker) => worker.taskId == null && worker.initialized); + if (availableWorker == null) { + _ensureWorkersInitialized(); + return; + } + if (_queue.isEmpty) return; + final task = _queue.removeFirst(); + + availableWorker + .work(task) + .then( + (value) { + //could be completed already by cancel and it is normal. + //Assuming that worker finished with error and cleaned gracefully + task.complete(value, null, null); + }, + onError: (error, st) { + task.complete(null, error, st); + }, + ) + .whenComplete(() { + if (_dynamicSpawning && _queue.isEmpty) availableWorker.kill(); + _schedule(); + }); + } + + @override + void _cancel(Task task) { + task.cancel(); + _queue.remove(task); + final targetWorker = _pool.firstWhereOrNull((worker) => worker.taskId == task.id); + if (task is Gentle) { + targetWorker?.cancelGentle(); + } else { + targetWorker?.kill(); + if (!_dynamicSpawning) targetWorker?.initialize(); + } + super._cancel(task); + } +} diff --git a/mobile/mise.toml b/mobile/mise.toml new file mode 100644 index 0000000000..cdafd1cc18 --- /dev/null +++ b/mobile/mise.toml @@ -0,0 +1,185 @@ +[tools] +flutter = "3.35.7" + +[tools."github:CQLabs/homebrew-dcm"] +version = "1.30.0" +bin = "dcm" +postinstall = "chmod +x $MISE_TOOL_INSTALL_PATH/dcm" + +[tasks."codegen:dart"] +alias = "codegen" +description = "Execute build_runner to auto-generate dart code" +sources = [ + "pubspec.yaml", + "build.yaml", + "lib/**/*.dart", + "infrastructure/**/*.drift", +] +outputs = { auto = true } +run = "dart run build_runner build --delete-conflicting-outputs" + +[tasks."codegen:pigeon"] +alias = "pigeon" +description = "Generate pigeon platform code" +depends = [ + "pigeon:native-sync", + "pigeon:thumbnail", + "pigeon:background-worker", + "pigeon:background-worker-lock", + "pigeon:connectivity", +] + +[tasks."codegen:translation"] +alias = "translation" +description = "Generate translations from i18n JSONs" +run = [ + { task = "//i18n:format-fix" }, + { tasks = [ + "i18n:loader", + "i18n:keys", + ] }, +] + +[tasks."codegen:app-icon"] +description = "Generate app icons" +run = "flutter pub run flutter_launcher_icons:main" + +[tasks."codegen:splash"] +description = "Generate splash screen" +run = "flutter pub run flutter_native_splash:create" + +[tasks.test] +description = "Run mobile tests" +run = "flutter test" + +[tasks.lint] +description = "Analyze Dart code" +depends = ["analyze:dart", "analyze:dcm"] + +[tasks."lint-fix"] +description = "Auto-fix Dart code" +depends = ["analyze:fix:dart", "analyze:fix:dcm"] + +[tasks.format] +description = "Format Dart code" +run = "dart format --set-exit-if-changed $(find lib -name '*.dart' -not \\( -name '*.g.dart' -o -name '*.drift.dart' -o -name '*.gr.dart' \\))" + +[tasks."build:android"] +description = "Build Android release" +run = "flutter build appbundle" + +[tasks."drift:migration"] +alias = "migration" +description = "Generate database migrations" +run = "dart run drift_dev make-migrations" + + +# Internal tasks +[tasks."pigeon:native-sync"] +description = "Generate native sync API pigeon code" +hide = true +sources = ["pigeon/native_sync_api.dart"] +outputs = [ + "lib/platform/native_sync_api.g.dart", + "ios/Runner/Sync/Messages.g.swift", + "android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt", +] +run = [ + "dart run pigeon --input pigeon/native_sync_api.dart", + "dart format lib/platform/native_sync_api.g.dart", +] + +[tasks."pigeon:thumbnail"] +description = "Generate thumbnail API pigeon code" +hide = true +sources = ["pigeon/thumbnail_api.dart"] +outputs = [ + "lib/platform/thumbnail_api.g.dart", + "ios/Runner/Images/Thumbnails.g.swift", + "android/app/src/main/kotlin/app/alextran/immich/images/Thumbnails.g.kt", +] +run = [ + "dart run pigeon --input pigeon/thumbnail_api.dart", + "dart format lib/platform/thumbnail_api.g.dart", +] + +[tasks."pigeon:background-worker"] +description = "Generate background worker API pigeon code" +hide = true +sources = ["pigeon/background_worker_api.dart"] +outputs = [ + "lib/platform/background_worker_api.g.dart", + "ios/Runner/Background/BackgroundWorker.g.swift", + "android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.g.kt", +] +run = [ + "dart run pigeon --input pigeon/background_worker_api.dart", + "dart format lib/platform/background_worker_api.g.dart", +] + +[tasks."pigeon:background-worker-lock"] +description = "Generate background worker lock API pigeon code" +hide = true +sources = ["pigeon/background_worker_lock_api.dart"] +outputs = [ + "lib/platform/background_worker_lock_api.g.dart", + "android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorkerLock.g.kt", +] +run = [ + "dart run pigeon --input pigeon/background_worker_lock_api.dart", + "dart format lib/platform/background_worker_lock_api.g.dart", +] + +[tasks."pigeon:connectivity"] +description = "Generate connectivity API pigeon code" +hide = true +sources = ["pigeon/connectivity_api.dart"] +outputs = [ + "lib/platform/connectivity_api.g.dart", + "ios/Runner/Connectivity/Connectivity.g.swift", + "android/app/src/main/kotlin/app/alextran/immich/connectivity/Connectivity.g.kt", +] +run = [ + "dart run pigeon --input pigeon/connectivity_api.dart", + "dart format lib/platform/connectivity_api.g.dart", +] + +[tasks."i18n:loader"] +description = "Generate i18n loader" +hide = true +sources = ["i18n/"] +outputs = "lib/generated/codegen_loader.g.dart" +run = [ + "dart run easy_localization:generate -S ../i18n", + "dart format lib/generated/codegen_loader.g.dart", +] + +[tasks."i18n:keys"] +description = "Generate i18n keys" +hide = true +sources = ["i18n/en.json"] +outputs = "lib/generated/intl_keys.g.dart" +run = [ + "dart run bin/generate_keys.dart", + "dart format lib/generated/intl_keys.g.dart", +] + +[tasks."analyze:dart"] +description = "Run Dart analysis" +hide = true +run = "dart analyze --fatal-infos" + +[tasks."analyze:dcm"] +description = "Run Dart Code Metrics" +hide = true +run = "dcm analyze lib --fatal-style --fatal-warnings" + +[tasks."analyze:fix:dart"] +description = "Auto-fix Dart analysis" +hide = true +run = "dart fix --apply" + +[tasks."analyze:fix:dcm"] +description = "Auto-fix Dart Code Metrics" +hide = true +run = "dcm fix lib" diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index c33828172c..4e34f66a81 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -3,7 +3,7 @@ Immich API This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: -- API version: 1.143.0 +- API version: 2.2.3 - Generator version: 7.8.0 - Build package: org.openapitools.codegen.languages.DartClientCodegen @@ -73,222 +73,235 @@ All URIs are relative to */api* Class | Method | HTTP request | Description ------------ | ------------- | ------------- | ------------- -*APIKeysApi* | [**createApiKey**](doc//APIKeysApi.md#createapikey) | **POST** /api-keys | -*APIKeysApi* | [**deleteApiKey**](doc//APIKeysApi.md#deleteapikey) | **DELETE** /api-keys/{id} | -*APIKeysApi* | [**getApiKey**](doc//APIKeysApi.md#getapikey) | **GET** /api-keys/{id} | -*APIKeysApi* | [**getApiKeys**](doc//APIKeysApi.md#getapikeys) | **GET** /api-keys | -*APIKeysApi* | [**getMyApiKey**](doc//APIKeysApi.md#getmyapikey) | **GET** /api-keys/me | -*APIKeysApi* | [**updateApiKey**](doc//APIKeysApi.md#updateapikey) | **PUT** /api-keys/{id} | -*ActivitiesApi* | [**createActivity**](doc//ActivitiesApi.md#createactivity) | **POST** /activities | -*ActivitiesApi* | [**deleteActivity**](doc//ActivitiesApi.md#deleteactivity) | **DELETE** /activities/{id} | -*ActivitiesApi* | [**getActivities**](doc//ActivitiesApi.md#getactivities) | **GET** /activities | -*ActivitiesApi* | [**getActivityStatistics**](doc//ActivitiesApi.md#getactivitystatistics) | **GET** /activities/statistics | -*AlbumsApi* | [**addAssetsToAlbum**](doc//AlbumsApi.md#addassetstoalbum) | **PUT** /albums/{id}/assets | -*AlbumsApi* | [**addAssetsToAlbums**](doc//AlbumsApi.md#addassetstoalbums) | **PUT** /albums/assets | -*AlbumsApi* | [**addUsersToAlbum**](doc//AlbumsApi.md#adduserstoalbum) | **PUT** /albums/{id}/users | -*AlbumsApi* | [**createAlbum**](doc//AlbumsApi.md#createalbum) | **POST** /albums | -*AlbumsApi* | [**deleteAlbum**](doc//AlbumsApi.md#deletealbum) | **DELETE** /albums/{id} | -*AlbumsApi* | [**getAlbumInfo**](doc//AlbumsApi.md#getalbuminfo) | **GET** /albums/{id} | -*AlbumsApi* | [**getAlbumStatistics**](doc//AlbumsApi.md#getalbumstatistics) | **GET** /albums/statistics | -*AlbumsApi* | [**getAllAlbums**](doc//AlbumsApi.md#getallalbums) | **GET** /albums | -*AlbumsApi* | [**removeAssetFromAlbum**](doc//AlbumsApi.md#removeassetfromalbum) | **DELETE** /albums/{id}/assets | -*AlbumsApi* | [**removeUserFromAlbum**](doc//AlbumsApi.md#removeuserfromalbum) | **DELETE** /albums/{id}/user/{userId} | -*AlbumsApi* | [**updateAlbumInfo**](doc//AlbumsApi.md#updatealbuminfo) | **PATCH** /albums/{id} | -*AlbumsApi* | [**updateAlbumUser**](doc//AlbumsApi.md#updatealbumuser) | **PUT** /albums/{id}/user/{userId} | -*AssetsApi* | [**checkBulkUpload**](doc//AssetsApi.md#checkbulkupload) | **POST** /assets/bulk-upload-check | checkBulkUpload -*AssetsApi* | [**checkExistingAssets**](doc//AssetsApi.md#checkexistingassets) | **POST** /assets/exist | checkExistingAssets -*AssetsApi* | [**deleteAssetMetadata**](doc//AssetsApi.md#deleteassetmetadata) | **DELETE** /assets/{id}/metadata/{key} | -*AssetsApi* | [**deleteAssets**](doc//AssetsApi.md#deleteassets) | **DELETE** /assets | -*AssetsApi* | [**downloadAsset**](doc//AssetsApi.md#downloadasset) | **GET** /assets/{id}/original | -*AssetsApi* | [**getAllUserAssetsByDeviceId**](doc//AssetsApi.md#getalluserassetsbydeviceid) | **GET** /assets/device/{deviceId} | getAllUserAssetsByDeviceId -*AssetsApi* | [**getAssetInfo**](doc//AssetsApi.md#getassetinfo) | **GET** /assets/{id} | -*AssetsApi* | [**getAssetMetadata**](doc//AssetsApi.md#getassetmetadata) | **GET** /assets/{id}/metadata | -*AssetsApi* | [**getAssetMetadataByKey**](doc//AssetsApi.md#getassetmetadatabykey) | **GET** /assets/{id}/metadata/{key} | -*AssetsApi* | [**getAssetStatistics**](doc//AssetsApi.md#getassetstatistics) | **GET** /assets/statistics | -*AssetsApi* | [**getRandom**](doc//AssetsApi.md#getrandom) | **GET** /assets/random | -*AssetsApi* | [**playAssetVideo**](doc//AssetsApi.md#playassetvideo) | **GET** /assets/{id}/video/playback | -*AssetsApi* | [**replaceAsset**](doc//AssetsApi.md#replaceasset) | **PUT** /assets/{id}/original | Replace the asset with new file, without changing its id -*AssetsApi* | [**runAssetJobs**](doc//AssetsApi.md#runassetjobs) | **POST** /assets/jobs | -*AssetsApi* | [**updateAsset**](doc//AssetsApi.md#updateasset) | **PUT** /assets/{id} | -*AssetsApi* | [**updateAssetMetadata**](doc//AssetsApi.md#updateassetmetadata) | **PUT** /assets/{id}/metadata | -*AssetsApi* | [**updateAssets**](doc//AssetsApi.md#updateassets) | **PUT** /assets | -*AssetsApi* | [**uploadAsset**](doc//AssetsApi.md#uploadasset) | **POST** /assets | -*AssetsApi* | [**viewAsset**](doc//AssetsApi.md#viewasset) | **GET** /assets/{id}/thumbnail | -*AuthAdminApi* | [**unlinkAllOAuthAccountsAdmin**](doc//AuthAdminApi.md#unlinkalloauthaccountsadmin) | **POST** /admin/auth/unlink-all | -*AuthenticationApi* | [**changePassword**](doc//AuthenticationApi.md#changepassword) | **POST** /auth/change-password | -*AuthenticationApi* | [**changePinCode**](doc//AuthenticationApi.md#changepincode) | **PUT** /auth/pin-code | -*AuthenticationApi* | [**getAuthStatus**](doc//AuthenticationApi.md#getauthstatus) | **GET** /auth/status | -*AuthenticationApi* | [**lockAuthSession**](doc//AuthenticationApi.md#lockauthsession) | **POST** /auth/session/lock | -*AuthenticationApi* | [**login**](doc//AuthenticationApi.md#login) | **POST** /auth/login | -*AuthenticationApi* | [**logout**](doc//AuthenticationApi.md#logout) | **POST** /auth/logout | -*AuthenticationApi* | [**resetPinCode**](doc//AuthenticationApi.md#resetpincode) | **DELETE** /auth/pin-code | -*AuthenticationApi* | [**setupPinCode**](doc//AuthenticationApi.md#setuppincode) | **POST** /auth/pin-code | -*AuthenticationApi* | [**signUpAdmin**](doc//AuthenticationApi.md#signupadmin) | **POST** /auth/admin-sign-up | -*AuthenticationApi* | [**unlockAuthSession**](doc//AuthenticationApi.md#unlockauthsession) | **POST** /auth/session/unlock | -*AuthenticationApi* | [**validateAccessToken**](doc//AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken | -*DeprecatedApi* | [**createPartnerDeprecated**](doc//DeprecatedApi.md#createpartnerdeprecated) | **POST** /partners/{id} | -*DeprecatedApi* | [**getRandom**](doc//DeprecatedApi.md#getrandom) | **GET** /assets/random | -*DeprecatedApi* | [**replaceAsset**](doc//DeprecatedApi.md#replaceasset) | **PUT** /assets/{id}/original | Replace the asset with new file, without changing its id -*DownloadApi* | [**downloadArchive**](doc//DownloadApi.md#downloadarchive) | **POST** /download/archive | -*DownloadApi* | [**getDownloadInfo**](doc//DownloadApi.md#getdownloadinfo) | **POST** /download/info | -*DuplicatesApi* | [**deleteDuplicate**](doc//DuplicatesApi.md#deleteduplicate) | **DELETE** /duplicates/{id} | -*DuplicatesApi* | [**deleteDuplicates**](doc//DuplicatesApi.md#deleteduplicates) | **DELETE** /duplicates | -*DuplicatesApi* | [**getAssetDuplicates**](doc//DuplicatesApi.md#getassetduplicates) | **GET** /duplicates | -*FacesApi* | [**createFace**](doc//FacesApi.md#createface) | **POST** /faces | -*FacesApi* | [**deleteFace**](doc//FacesApi.md#deleteface) | **DELETE** /faces/{id} | -*FacesApi* | [**getFaces**](doc//FacesApi.md#getfaces) | **GET** /faces | -*FacesApi* | [**reassignFacesById**](doc//FacesApi.md#reassignfacesbyid) | **PUT** /faces/{id} | -*JobsApi* | [**createJob**](doc//JobsApi.md#createjob) | **POST** /jobs | -*JobsApi* | [**getAllJobsStatus**](doc//JobsApi.md#getalljobsstatus) | **GET** /jobs | -*JobsApi* | [**sendJobCommand**](doc//JobsApi.md#sendjobcommand) | **PUT** /jobs/{id} | -*LibrariesApi* | [**createLibrary**](doc//LibrariesApi.md#createlibrary) | **POST** /libraries | -*LibrariesApi* | [**deleteLibrary**](doc//LibrariesApi.md#deletelibrary) | **DELETE** /libraries/{id} | -*LibrariesApi* | [**getAllLibraries**](doc//LibrariesApi.md#getalllibraries) | **GET** /libraries | -*LibrariesApi* | [**getLibrary**](doc//LibrariesApi.md#getlibrary) | **GET** /libraries/{id} | -*LibrariesApi* | [**getLibraryStatistics**](doc//LibrariesApi.md#getlibrarystatistics) | **GET** /libraries/{id}/statistics | -*LibrariesApi* | [**scanLibrary**](doc//LibrariesApi.md#scanlibrary) | **POST** /libraries/{id}/scan | -*LibrariesApi* | [**updateLibrary**](doc//LibrariesApi.md#updatelibrary) | **PUT** /libraries/{id} | -*LibrariesApi* | [**validate**](doc//LibrariesApi.md#validate) | **POST** /libraries/{id}/validate | -*MapApi* | [**getMapMarkers**](doc//MapApi.md#getmapmarkers) | **GET** /map/markers | -*MapApi* | [**reverseGeocode**](doc//MapApi.md#reversegeocode) | **GET** /map/reverse-geocode | -*MemoriesApi* | [**addMemoryAssets**](doc//MemoriesApi.md#addmemoryassets) | **PUT** /memories/{id}/assets | -*MemoriesApi* | [**createMemory**](doc//MemoriesApi.md#creatememory) | **POST** /memories | -*MemoriesApi* | [**deleteMemory**](doc//MemoriesApi.md#deletememory) | **DELETE** /memories/{id} | -*MemoriesApi* | [**getMemory**](doc//MemoriesApi.md#getmemory) | **GET** /memories/{id} | -*MemoriesApi* | [**memoriesStatistics**](doc//MemoriesApi.md#memoriesstatistics) | **GET** /memories/statistics | -*MemoriesApi* | [**removeMemoryAssets**](doc//MemoriesApi.md#removememoryassets) | **DELETE** /memories/{id}/assets | -*MemoriesApi* | [**searchMemories**](doc//MemoriesApi.md#searchmemories) | **GET** /memories | -*MemoriesApi* | [**updateMemory**](doc//MemoriesApi.md#updatememory) | **PUT** /memories/{id} | -*NotificationsApi* | [**deleteNotification**](doc//NotificationsApi.md#deletenotification) | **DELETE** /notifications/{id} | -*NotificationsApi* | [**deleteNotifications**](doc//NotificationsApi.md#deletenotifications) | **DELETE** /notifications | -*NotificationsApi* | [**getNotification**](doc//NotificationsApi.md#getnotification) | **GET** /notifications/{id} | -*NotificationsApi* | [**getNotifications**](doc//NotificationsApi.md#getnotifications) | **GET** /notifications | -*NotificationsApi* | [**updateNotification**](doc//NotificationsApi.md#updatenotification) | **PUT** /notifications/{id} | -*NotificationsApi* | [**updateNotifications**](doc//NotificationsApi.md#updatenotifications) | **PUT** /notifications | -*NotificationsAdminApi* | [**createNotification**](doc//NotificationsAdminApi.md#createnotification) | **POST** /admin/notifications | -*NotificationsAdminApi* | [**getNotificationTemplateAdmin**](doc//NotificationsAdminApi.md#getnotificationtemplateadmin) | **POST** /admin/notifications/templates/{name} | -*NotificationsAdminApi* | [**sendTestEmailAdmin**](doc//NotificationsAdminApi.md#sendtestemailadmin) | **POST** /admin/notifications/test-email | -*OAuthApi* | [**finishOAuth**](doc//OAuthApi.md#finishoauth) | **POST** /oauth/callback | -*OAuthApi* | [**linkOAuthAccount**](doc//OAuthApi.md#linkoauthaccount) | **POST** /oauth/link | -*OAuthApi* | [**redirectOAuthToMobile**](doc//OAuthApi.md#redirectoauthtomobile) | **GET** /oauth/mobile-redirect | -*OAuthApi* | [**startOAuth**](doc//OAuthApi.md#startoauth) | **POST** /oauth/authorize | -*OAuthApi* | [**unlinkOAuthAccount**](doc//OAuthApi.md#unlinkoauthaccount) | **POST** /oauth/unlink | -*PartnersApi* | [**createPartner**](doc//PartnersApi.md#createpartner) | **POST** /partners | -*PartnersApi* | [**createPartnerDeprecated**](doc//PartnersApi.md#createpartnerdeprecated) | **POST** /partners/{id} | -*PartnersApi* | [**getPartners**](doc//PartnersApi.md#getpartners) | **GET** /partners | -*PartnersApi* | [**removePartner**](doc//PartnersApi.md#removepartner) | **DELETE** /partners/{id} | -*PartnersApi* | [**updatePartner**](doc//PartnersApi.md#updatepartner) | **PUT** /partners/{id} | -*PeopleApi* | [**createPerson**](doc//PeopleApi.md#createperson) | **POST** /people | -*PeopleApi* | [**deletePeople**](doc//PeopleApi.md#deletepeople) | **DELETE** /people | -*PeopleApi* | [**deletePerson**](doc//PeopleApi.md#deleteperson) | **DELETE** /people/{id} | -*PeopleApi* | [**getAllPeople**](doc//PeopleApi.md#getallpeople) | **GET** /people | -*PeopleApi* | [**getPerson**](doc//PeopleApi.md#getperson) | **GET** /people/{id} | -*PeopleApi* | [**getPersonStatistics**](doc//PeopleApi.md#getpersonstatistics) | **GET** /people/{id}/statistics | -*PeopleApi* | [**getPersonThumbnail**](doc//PeopleApi.md#getpersonthumbnail) | **GET** /people/{id}/thumbnail | -*PeopleApi* | [**mergePerson**](doc//PeopleApi.md#mergeperson) | **POST** /people/{id}/merge | -*PeopleApi* | [**reassignFaces**](doc//PeopleApi.md#reassignfaces) | **PUT** /people/{id}/reassign | -*PeopleApi* | [**updatePeople**](doc//PeopleApi.md#updatepeople) | **PUT** /people | -*PeopleApi* | [**updatePerson**](doc//PeopleApi.md#updateperson) | **PUT** /people/{id} | -*SearchApi* | [**getAssetsByCity**](doc//SearchApi.md#getassetsbycity) | **GET** /search/cities | -*SearchApi* | [**getExploreData**](doc//SearchApi.md#getexploredata) | **GET** /search/explore | -*SearchApi* | [**getSearchSuggestions**](doc//SearchApi.md#getsearchsuggestions) | **GET** /search/suggestions | -*SearchApi* | [**searchAssetStatistics**](doc//SearchApi.md#searchassetstatistics) | **POST** /search/statistics | -*SearchApi* | [**searchAssets**](doc//SearchApi.md#searchassets) | **POST** /search/metadata | -*SearchApi* | [**searchLargeAssets**](doc//SearchApi.md#searchlargeassets) | **POST** /search/large-assets | -*SearchApi* | [**searchPerson**](doc//SearchApi.md#searchperson) | **GET** /search/person | -*SearchApi* | [**searchPlaces**](doc//SearchApi.md#searchplaces) | **GET** /search/places | -*SearchApi* | [**searchRandom**](doc//SearchApi.md#searchrandom) | **POST** /search/random | -*SearchApi* | [**searchSmart**](doc//SearchApi.md#searchsmart) | **POST** /search/smart | -*ServerApi* | [**deleteServerLicense**](doc//ServerApi.md#deleteserverlicense) | **DELETE** /server/license | -*ServerApi* | [**getAboutInfo**](doc//ServerApi.md#getaboutinfo) | **GET** /server/about | -*ServerApi* | [**getApkLinks**](doc//ServerApi.md#getapklinks) | **GET** /server/apk-links | -*ServerApi* | [**getServerConfig**](doc//ServerApi.md#getserverconfig) | **GET** /server/config | -*ServerApi* | [**getServerFeatures**](doc//ServerApi.md#getserverfeatures) | **GET** /server/features | -*ServerApi* | [**getServerLicense**](doc//ServerApi.md#getserverlicense) | **GET** /server/license | -*ServerApi* | [**getServerStatistics**](doc//ServerApi.md#getserverstatistics) | **GET** /server/statistics | -*ServerApi* | [**getServerVersion**](doc//ServerApi.md#getserverversion) | **GET** /server/version | -*ServerApi* | [**getStorage**](doc//ServerApi.md#getstorage) | **GET** /server/storage | -*ServerApi* | [**getSupportedMediaTypes**](doc//ServerApi.md#getsupportedmediatypes) | **GET** /server/media-types | -*ServerApi* | [**getTheme**](doc//ServerApi.md#gettheme) | **GET** /server/theme | -*ServerApi* | [**getVersionCheck**](doc//ServerApi.md#getversioncheck) | **GET** /server/version-check | -*ServerApi* | [**getVersionHistory**](doc//ServerApi.md#getversionhistory) | **GET** /server/version-history | -*ServerApi* | [**pingServer**](doc//ServerApi.md#pingserver) | **GET** /server/ping | -*ServerApi* | [**setServerLicense**](doc//ServerApi.md#setserverlicense) | **PUT** /server/license | -*SessionsApi* | [**createSession**](doc//SessionsApi.md#createsession) | **POST** /sessions | -*SessionsApi* | [**deleteAllSessions**](doc//SessionsApi.md#deleteallsessions) | **DELETE** /sessions | -*SessionsApi* | [**deleteSession**](doc//SessionsApi.md#deletesession) | **DELETE** /sessions/{id} | -*SessionsApi* | [**getSessions**](doc//SessionsApi.md#getsessions) | **GET** /sessions | -*SessionsApi* | [**lockSession**](doc//SessionsApi.md#locksession) | **POST** /sessions/{id}/lock | -*SessionsApi* | [**updateSession**](doc//SessionsApi.md#updatesession) | **PUT** /sessions/{id} | -*SharedLinksApi* | [**addSharedLinkAssets**](doc//SharedLinksApi.md#addsharedlinkassets) | **PUT** /shared-links/{id}/assets | -*SharedLinksApi* | [**createSharedLink**](doc//SharedLinksApi.md#createsharedlink) | **POST** /shared-links | -*SharedLinksApi* | [**getAllSharedLinks**](doc//SharedLinksApi.md#getallsharedlinks) | **GET** /shared-links | -*SharedLinksApi* | [**getMySharedLink**](doc//SharedLinksApi.md#getmysharedlink) | **GET** /shared-links/me | -*SharedLinksApi* | [**getSharedLinkById**](doc//SharedLinksApi.md#getsharedlinkbyid) | **GET** /shared-links/{id} | -*SharedLinksApi* | [**removeSharedLink**](doc//SharedLinksApi.md#removesharedlink) | **DELETE** /shared-links/{id} | -*SharedLinksApi* | [**removeSharedLinkAssets**](doc//SharedLinksApi.md#removesharedlinkassets) | **DELETE** /shared-links/{id}/assets | -*SharedLinksApi* | [**updateSharedLink**](doc//SharedLinksApi.md#updatesharedlink) | **PATCH** /shared-links/{id} | -*StacksApi* | [**createStack**](doc//StacksApi.md#createstack) | **POST** /stacks | -*StacksApi* | [**deleteStack**](doc//StacksApi.md#deletestack) | **DELETE** /stacks/{id} | -*StacksApi* | [**deleteStacks**](doc//StacksApi.md#deletestacks) | **DELETE** /stacks | -*StacksApi* | [**getStack**](doc//StacksApi.md#getstack) | **GET** /stacks/{id} | -*StacksApi* | [**removeAssetFromStack**](doc//StacksApi.md#removeassetfromstack) | **DELETE** /stacks/{id}/assets/{assetId} | -*StacksApi* | [**searchStacks**](doc//StacksApi.md#searchstacks) | **GET** /stacks | -*StacksApi* | [**updateStack**](doc//StacksApi.md#updatestack) | **PUT** /stacks/{id} | -*SyncApi* | [**deleteSyncAck**](doc//SyncApi.md#deletesyncack) | **DELETE** /sync/ack | -*SyncApi* | [**getDeltaSync**](doc//SyncApi.md#getdeltasync) | **POST** /sync/delta-sync | -*SyncApi* | [**getFullSyncForUser**](doc//SyncApi.md#getfullsyncforuser) | **POST** /sync/full-sync | -*SyncApi* | [**getSyncAck**](doc//SyncApi.md#getsyncack) | **GET** /sync/ack | -*SyncApi* | [**getSyncStream**](doc//SyncApi.md#getsyncstream) | **POST** /sync/stream | -*SyncApi* | [**sendSyncAck**](doc//SyncApi.md#sendsyncack) | **POST** /sync/ack | -*SystemConfigApi* | [**getConfig**](doc//SystemConfigApi.md#getconfig) | **GET** /system-config | -*SystemConfigApi* | [**getConfigDefaults**](doc//SystemConfigApi.md#getconfigdefaults) | **GET** /system-config/defaults | -*SystemConfigApi* | [**getStorageTemplateOptions**](doc//SystemConfigApi.md#getstoragetemplateoptions) | **GET** /system-config/storage-template-options | -*SystemConfigApi* | [**updateConfig**](doc//SystemConfigApi.md#updateconfig) | **PUT** /system-config | -*SystemMetadataApi* | [**getAdminOnboarding**](doc//SystemMetadataApi.md#getadminonboarding) | **GET** /system-metadata/admin-onboarding | -*SystemMetadataApi* | [**getReverseGeocodingState**](doc//SystemMetadataApi.md#getreversegeocodingstate) | **GET** /system-metadata/reverse-geocoding-state | -*SystemMetadataApi* | [**getVersionCheckState**](doc//SystemMetadataApi.md#getversioncheckstate) | **GET** /system-metadata/version-check-state | -*SystemMetadataApi* | [**updateAdminOnboarding**](doc//SystemMetadataApi.md#updateadminonboarding) | **POST** /system-metadata/admin-onboarding | -*TagsApi* | [**bulkTagAssets**](doc//TagsApi.md#bulktagassets) | **PUT** /tags/assets | -*TagsApi* | [**createTag**](doc//TagsApi.md#createtag) | **POST** /tags | -*TagsApi* | [**deleteTag**](doc//TagsApi.md#deletetag) | **DELETE** /tags/{id} | -*TagsApi* | [**getAllTags**](doc//TagsApi.md#getalltags) | **GET** /tags | -*TagsApi* | [**getTagById**](doc//TagsApi.md#gettagbyid) | **GET** /tags/{id} | -*TagsApi* | [**tagAssets**](doc//TagsApi.md#tagassets) | **PUT** /tags/{id}/assets | -*TagsApi* | [**untagAssets**](doc//TagsApi.md#untagassets) | **DELETE** /tags/{id}/assets | -*TagsApi* | [**updateTag**](doc//TagsApi.md#updatetag) | **PUT** /tags/{id} | -*TagsApi* | [**upsertTags**](doc//TagsApi.md#upserttags) | **PUT** /tags | -*TimelineApi* | [**getTimeBucket**](doc//TimelineApi.md#gettimebucket) | **GET** /timeline/bucket | -*TimelineApi* | [**getTimeBuckets**](doc//TimelineApi.md#gettimebuckets) | **GET** /timeline/buckets | -*TrashApi* | [**emptyTrash**](doc//TrashApi.md#emptytrash) | **POST** /trash/empty | -*TrashApi* | [**restoreAssets**](doc//TrashApi.md#restoreassets) | **POST** /trash/restore/assets | -*TrashApi* | [**restoreTrash**](doc//TrashApi.md#restoretrash) | **POST** /trash/restore | -*UsersApi* | [**createProfileImage**](doc//UsersApi.md#createprofileimage) | **POST** /users/profile-image | -*UsersApi* | [**deleteProfileImage**](doc//UsersApi.md#deleteprofileimage) | **DELETE** /users/profile-image | -*UsersApi* | [**deleteUserLicense**](doc//UsersApi.md#deleteuserlicense) | **DELETE** /users/me/license | -*UsersApi* | [**deleteUserOnboarding**](doc//UsersApi.md#deleteuseronboarding) | **DELETE** /users/me/onboarding | -*UsersApi* | [**getMyPreferences**](doc//UsersApi.md#getmypreferences) | **GET** /users/me/preferences | -*UsersApi* | [**getMyUser**](doc//UsersApi.md#getmyuser) | **GET** /users/me | -*UsersApi* | [**getProfileImage**](doc//UsersApi.md#getprofileimage) | **GET** /users/{id}/profile-image | -*UsersApi* | [**getUser**](doc//UsersApi.md#getuser) | **GET** /users/{id} | -*UsersApi* | [**getUserLicense**](doc//UsersApi.md#getuserlicense) | **GET** /users/me/license | -*UsersApi* | [**getUserOnboarding**](doc//UsersApi.md#getuseronboarding) | **GET** /users/me/onboarding | -*UsersApi* | [**searchUsers**](doc//UsersApi.md#searchusers) | **GET** /users | -*UsersApi* | [**setUserLicense**](doc//UsersApi.md#setuserlicense) | **PUT** /users/me/license | -*UsersApi* | [**setUserOnboarding**](doc//UsersApi.md#setuseronboarding) | **PUT** /users/me/onboarding | -*UsersApi* | [**updateMyPreferences**](doc//UsersApi.md#updatemypreferences) | **PUT** /users/me/preferences | -*UsersApi* | [**updateMyUser**](doc//UsersApi.md#updatemyuser) | **PUT** /users/me | -*UsersAdminApi* | [**createUserAdmin**](doc//UsersAdminApi.md#createuseradmin) | **POST** /admin/users | -*UsersAdminApi* | [**deleteUserAdmin**](doc//UsersAdminApi.md#deleteuseradmin) | **DELETE** /admin/users/{id} | -*UsersAdminApi* | [**getUserAdmin**](doc//UsersAdminApi.md#getuseradmin) | **GET** /admin/users/{id} | -*UsersAdminApi* | [**getUserPreferencesAdmin**](doc//UsersAdminApi.md#getuserpreferencesadmin) | **GET** /admin/users/{id}/preferences | -*UsersAdminApi* | [**getUserStatisticsAdmin**](doc//UsersAdminApi.md#getuserstatisticsadmin) | **GET** /admin/users/{id}/statistics | -*UsersAdminApi* | [**restoreUserAdmin**](doc//UsersAdminApi.md#restoreuseradmin) | **POST** /admin/users/{id}/restore | -*UsersAdminApi* | [**searchUsersAdmin**](doc//UsersAdminApi.md#searchusersadmin) | **GET** /admin/users | -*UsersAdminApi* | [**updateUserAdmin**](doc//UsersAdminApi.md#updateuseradmin) | **PUT** /admin/users/{id} | -*UsersAdminApi* | [**updateUserPreferencesAdmin**](doc//UsersAdminApi.md#updateuserpreferencesadmin) | **PUT** /admin/users/{id}/preferences | -*ViewApi* | [**getAssetsByOriginalPath**](doc//ViewApi.md#getassetsbyoriginalpath) | **GET** /view/folder | -*ViewApi* | [**getUniqueOriginalPaths**](doc//ViewApi.md#getuniqueoriginalpaths) | **GET** /view/folder/unique-paths | +*APIKeysApi* | [**createApiKey**](doc//APIKeysApi.md#createapikey) | **POST** /api-keys | Create an API key +*APIKeysApi* | [**deleteApiKey**](doc//APIKeysApi.md#deleteapikey) | **DELETE** /api-keys/{id} | Delete an API key +*APIKeysApi* | [**getApiKey**](doc//APIKeysApi.md#getapikey) | **GET** /api-keys/{id} | Retrieve an API key +*APIKeysApi* | [**getApiKeys**](doc//APIKeysApi.md#getapikeys) | **GET** /api-keys | List all API keys +*APIKeysApi* | [**getMyApiKey**](doc//APIKeysApi.md#getmyapikey) | **GET** /api-keys/me | Retrieve the current API key +*APIKeysApi* | [**updateApiKey**](doc//APIKeysApi.md#updateapikey) | **PUT** /api-keys/{id} | Update an API key +*ActivitiesApi* | [**createActivity**](doc//ActivitiesApi.md#createactivity) | **POST** /activities | Create an activity +*ActivitiesApi* | [**deleteActivity**](doc//ActivitiesApi.md#deleteactivity) | **DELETE** /activities/{id} | Delete an activity +*ActivitiesApi* | [**getActivities**](doc//ActivitiesApi.md#getactivities) | **GET** /activities | List all activities +*ActivitiesApi* | [**getActivityStatistics**](doc//ActivitiesApi.md#getactivitystatistics) | **GET** /activities/statistics | Retrieve activity statistics +*AlbumsApi* | [**addAssetsToAlbum**](doc//AlbumsApi.md#addassetstoalbum) | **PUT** /albums/{id}/assets | Add assets to an album +*AlbumsApi* | [**addAssetsToAlbums**](doc//AlbumsApi.md#addassetstoalbums) | **PUT** /albums/assets | Add assets to albums +*AlbumsApi* | [**addUsersToAlbum**](doc//AlbumsApi.md#adduserstoalbum) | **PUT** /albums/{id}/users | Share album with users +*AlbumsApi* | [**createAlbum**](doc//AlbumsApi.md#createalbum) | **POST** /albums | Create an album +*AlbumsApi* | [**deleteAlbum**](doc//AlbumsApi.md#deletealbum) | **DELETE** /albums/{id} | Delete an album +*AlbumsApi* | [**getAlbumInfo**](doc//AlbumsApi.md#getalbuminfo) | **GET** /albums/{id} | Retrieve an album +*AlbumsApi* | [**getAlbumStatistics**](doc//AlbumsApi.md#getalbumstatistics) | **GET** /albums/statistics | Retrieve album statistics +*AlbumsApi* | [**getAllAlbums**](doc//AlbumsApi.md#getallalbums) | **GET** /albums | List all albums +*AlbumsApi* | [**removeAssetFromAlbum**](doc//AlbumsApi.md#removeassetfromalbum) | **DELETE** /albums/{id}/assets | Remove assets from an album +*AlbumsApi* | [**removeUserFromAlbum**](doc//AlbumsApi.md#removeuserfromalbum) | **DELETE** /albums/{id}/user/{userId} | Remove user from album +*AlbumsApi* | [**updateAlbumInfo**](doc//AlbumsApi.md#updatealbuminfo) | **PATCH** /albums/{id} | Update an album +*AlbumsApi* | [**updateAlbumUser**](doc//AlbumsApi.md#updatealbumuser) | **PUT** /albums/{id}/user/{userId} | Update user role +*AssetsApi* | [**checkBulkUpload**](doc//AssetsApi.md#checkbulkupload) | **POST** /assets/bulk-upload-check | Check bulk upload +*AssetsApi* | [**checkExistingAssets**](doc//AssetsApi.md#checkexistingassets) | **POST** /assets/exist | Check existing assets +*AssetsApi* | [**copyAsset**](doc//AssetsApi.md#copyasset) | **PUT** /assets/copy | Copy asset +*AssetsApi* | [**deleteAssetMetadata**](doc//AssetsApi.md#deleteassetmetadata) | **DELETE** /assets/{id}/metadata/{key} | Delete asset metadata by key +*AssetsApi* | [**deleteAssets**](doc//AssetsApi.md#deleteassets) | **DELETE** /assets | Delete assets +*AssetsApi* | [**downloadAsset**](doc//AssetsApi.md#downloadasset) | **GET** /assets/{id}/original | Download original asset +*AssetsApi* | [**getAllUserAssetsByDeviceId**](doc//AssetsApi.md#getalluserassetsbydeviceid) | **GET** /assets/device/{deviceId} | Retrieve assets by device ID +*AssetsApi* | [**getAssetInfo**](doc//AssetsApi.md#getassetinfo) | **GET** /assets/{id} | Retrieve an asset +*AssetsApi* | [**getAssetMetadata**](doc//AssetsApi.md#getassetmetadata) | **GET** /assets/{id}/metadata | Get asset metadata +*AssetsApi* | [**getAssetMetadataByKey**](doc//AssetsApi.md#getassetmetadatabykey) | **GET** /assets/{id}/metadata/{key} | Retrieve asset metadata by key +*AssetsApi* | [**getAssetOcr**](doc//AssetsApi.md#getassetocr) | **GET** /assets/{id}/ocr | Retrieve asset OCR data +*AssetsApi* | [**getAssetStatistics**](doc//AssetsApi.md#getassetstatistics) | **GET** /assets/statistics | Get asset statistics +*AssetsApi* | [**getRandom**](doc//AssetsApi.md#getrandom) | **GET** /assets/random | Get random assets +*AssetsApi* | [**playAssetVideo**](doc//AssetsApi.md#playassetvideo) | **GET** /assets/{id}/video/playback | Play asset video +*AssetsApi* | [**replaceAsset**](doc//AssetsApi.md#replaceasset) | **PUT** /assets/{id}/original | Replace asset +*AssetsApi* | [**runAssetJobs**](doc//AssetsApi.md#runassetjobs) | **POST** /assets/jobs | Run an asset job +*AssetsApi* | [**updateAsset**](doc//AssetsApi.md#updateasset) | **PUT** /assets/{id} | Update an asset +*AssetsApi* | [**updateAssetMetadata**](doc//AssetsApi.md#updateassetmetadata) | **PUT** /assets/{id}/metadata | Update asset metadata +*AssetsApi* | [**updateAssets**](doc//AssetsApi.md#updateassets) | **PUT** /assets | Update assets +*AssetsApi* | [**uploadAsset**](doc//AssetsApi.md#uploadasset) | **POST** /assets | Upload asset +*AssetsApi* | [**viewAsset**](doc//AssetsApi.md#viewasset) | **GET** /assets/{id}/thumbnail | View asset thumbnail +*AuthenticationApi* | [**changePassword**](doc//AuthenticationApi.md#changepassword) | **POST** /auth/change-password | Change password +*AuthenticationApi* | [**changePinCode**](doc//AuthenticationApi.md#changepincode) | **PUT** /auth/pin-code | Change pin code +*AuthenticationApi* | [**finishOAuth**](doc//AuthenticationApi.md#finishoauth) | **POST** /oauth/callback | Finish OAuth +*AuthenticationApi* | [**getAuthStatus**](doc//AuthenticationApi.md#getauthstatus) | **GET** /auth/status | Retrieve auth status +*AuthenticationApi* | [**linkOAuthAccount**](doc//AuthenticationApi.md#linkoauthaccount) | **POST** /oauth/link | Link OAuth account +*AuthenticationApi* | [**lockAuthSession**](doc//AuthenticationApi.md#lockauthsession) | **POST** /auth/session/lock | Lock auth session +*AuthenticationApi* | [**login**](doc//AuthenticationApi.md#login) | **POST** /auth/login | Login +*AuthenticationApi* | [**logout**](doc//AuthenticationApi.md#logout) | **POST** /auth/logout | Logout +*AuthenticationApi* | [**redirectOAuthToMobile**](doc//AuthenticationApi.md#redirectoauthtomobile) | **GET** /oauth/mobile-redirect | Redirect OAuth to mobile +*AuthenticationApi* | [**resetPinCode**](doc//AuthenticationApi.md#resetpincode) | **DELETE** /auth/pin-code | Reset pin code +*AuthenticationApi* | [**setupPinCode**](doc//AuthenticationApi.md#setuppincode) | **POST** /auth/pin-code | Setup pin code +*AuthenticationApi* | [**signUpAdmin**](doc//AuthenticationApi.md#signupadmin) | **POST** /auth/admin-sign-up | Register admin +*AuthenticationApi* | [**startOAuth**](doc//AuthenticationApi.md#startoauth) | **POST** /oauth/authorize | Start OAuth +*AuthenticationApi* | [**unlinkOAuthAccount**](doc//AuthenticationApi.md#unlinkoauthaccount) | **POST** /oauth/unlink | Unlink OAuth account +*AuthenticationApi* | [**unlockAuthSession**](doc//AuthenticationApi.md#unlockauthsession) | **POST** /auth/session/unlock | Unlock auth session +*AuthenticationApi* | [**validateAccessToken**](doc//AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken | Validate access token +*AuthenticationAdminApi* | [**unlinkAllOAuthAccountsAdmin**](doc//AuthenticationAdminApi.md#unlinkalloauthaccountsadmin) | **POST** /admin/auth/unlink-all | Unlink all OAuth accounts +*DeprecatedApi* | [**createPartnerDeprecated**](doc//DeprecatedApi.md#createpartnerdeprecated) | **POST** /partners/{id} | Create a partner +*DeprecatedApi* | [**getAllUserAssetsByDeviceId**](doc//DeprecatedApi.md#getalluserassetsbydeviceid) | **GET** /assets/device/{deviceId} | Retrieve assets by device ID +*DeprecatedApi* | [**getDeltaSync**](doc//DeprecatedApi.md#getdeltasync) | **POST** /sync/delta-sync | Get delta sync for user +*DeprecatedApi* | [**getFullSyncForUser**](doc//DeprecatedApi.md#getfullsyncforuser) | **POST** /sync/full-sync | Get full sync for user +*DeprecatedApi* | [**getRandom**](doc//DeprecatedApi.md#getrandom) | **GET** /assets/random | Get random assets +*DeprecatedApi* | [**replaceAsset**](doc//DeprecatedApi.md#replaceasset) | **PUT** /assets/{id}/original | Replace asset +*DownloadApi* | [**downloadArchive**](doc//DownloadApi.md#downloadarchive) | **POST** /download/archive | Download asset archive +*DownloadApi* | [**getDownloadInfo**](doc//DownloadApi.md#getdownloadinfo) | **POST** /download/info | Retrieve download information +*DuplicatesApi* | [**deleteDuplicate**](doc//DuplicatesApi.md#deleteduplicate) | **DELETE** /duplicates/{id} | Delete a duplicate +*DuplicatesApi* | [**deleteDuplicates**](doc//DuplicatesApi.md#deleteduplicates) | **DELETE** /duplicates | Delete duplicates +*DuplicatesApi* | [**getAssetDuplicates**](doc//DuplicatesApi.md#getassetduplicates) | **GET** /duplicates | Retrieve duplicates +*FacesApi* | [**createFace**](doc//FacesApi.md#createface) | **POST** /faces | Create a face +*FacesApi* | [**deleteFace**](doc//FacesApi.md#deleteface) | **DELETE** /faces/{id} | Delete a face +*FacesApi* | [**getFaces**](doc//FacesApi.md#getfaces) | **GET** /faces | Retrieve faces for asset +*FacesApi* | [**reassignFacesById**](doc//FacesApi.md#reassignfacesbyid) | **PUT** /faces/{id} | Re-assign a face to another person +*JobsApi* | [**createJob**](doc//JobsApi.md#createjob) | **POST** /jobs | Create a manual job +*JobsApi* | [**getQueuesLegacy**](doc//JobsApi.md#getqueueslegacy) | **GET** /jobs | Retrieve queue counts and status +*JobsApi* | [**runQueueCommandLegacy**](doc//JobsApi.md#runqueuecommandlegacy) | **PUT** /jobs/{name} | Run jobs +*LibrariesApi* | [**createLibrary**](doc//LibrariesApi.md#createlibrary) | **POST** /libraries | Create a library +*LibrariesApi* | [**deleteLibrary**](doc//LibrariesApi.md#deletelibrary) | **DELETE** /libraries/{id} | Delete a library +*LibrariesApi* | [**getAllLibraries**](doc//LibrariesApi.md#getalllibraries) | **GET** /libraries | Retrieve libraries +*LibrariesApi* | [**getLibrary**](doc//LibrariesApi.md#getlibrary) | **GET** /libraries/{id} | Retrieve a library +*LibrariesApi* | [**getLibraryStatistics**](doc//LibrariesApi.md#getlibrarystatistics) | **GET** /libraries/{id}/statistics | Retrieve library statistics +*LibrariesApi* | [**scanLibrary**](doc//LibrariesApi.md#scanlibrary) | **POST** /libraries/{id}/scan | Scan a library +*LibrariesApi* | [**updateLibrary**](doc//LibrariesApi.md#updatelibrary) | **PUT** /libraries/{id} | Update a library +*LibrariesApi* | [**validate**](doc//LibrariesApi.md#validate) | **POST** /libraries/{id}/validate | Validate library settings +*MapApi* | [**getMapMarkers**](doc//MapApi.md#getmapmarkers) | **GET** /map/markers | Retrieve map markers +*MapApi* | [**reverseGeocode**](doc//MapApi.md#reversegeocode) | **GET** /map/reverse-geocode | Reverse geocode coordinates +*MemoriesApi* | [**addMemoryAssets**](doc//MemoriesApi.md#addmemoryassets) | **PUT** /memories/{id}/assets | Add assets to a memory +*MemoriesApi* | [**createMemory**](doc//MemoriesApi.md#creatememory) | **POST** /memories | Create a memory +*MemoriesApi* | [**deleteMemory**](doc//MemoriesApi.md#deletememory) | **DELETE** /memories/{id} | Delete a memory +*MemoriesApi* | [**getMemory**](doc//MemoriesApi.md#getmemory) | **GET** /memories/{id} | Retrieve a memory +*MemoriesApi* | [**memoriesStatistics**](doc//MemoriesApi.md#memoriesstatistics) | **GET** /memories/statistics | Retrieve memories statistics +*MemoriesApi* | [**removeMemoryAssets**](doc//MemoriesApi.md#removememoryassets) | **DELETE** /memories/{id}/assets | Remove assets from a memory +*MemoriesApi* | [**searchMemories**](doc//MemoriesApi.md#searchmemories) | **GET** /memories | Retrieve memories +*MemoriesApi* | [**updateMemory**](doc//MemoriesApi.md#updatememory) | **PUT** /memories/{id} | Update a memory +*NotificationsApi* | [**deleteNotification**](doc//NotificationsApi.md#deletenotification) | **DELETE** /notifications/{id} | Delete a notification +*NotificationsApi* | [**deleteNotifications**](doc//NotificationsApi.md#deletenotifications) | **DELETE** /notifications | Delete notifications +*NotificationsApi* | [**getNotification**](doc//NotificationsApi.md#getnotification) | **GET** /notifications/{id} | Get a notification +*NotificationsApi* | [**getNotifications**](doc//NotificationsApi.md#getnotifications) | **GET** /notifications | Retrieve notifications +*NotificationsApi* | [**updateNotification**](doc//NotificationsApi.md#updatenotification) | **PUT** /notifications/{id} | Update a notification +*NotificationsApi* | [**updateNotifications**](doc//NotificationsApi.md#updatenotifications) | **PUT** /notifications | Update notifications +*NotificationsAdminApi* | [**createNotification**](doc//NotificationsAdminApi.md#createnotification) | **POST** /admin/notifications | Create a notification +*NotificationsAdminApi* | [**getNotificationTemplateAdmin**](doc//NotificationsAdminApi.md#getnotificationtemplateadmin) | **POST** /admin/notifications/templates/{name} | Render email template +*NotificationsAdminApi* | [**sendTestEmailAdmin**](doc//NotificationsAdminApi.md#sendtestemailadmin) | **POST** /admin/notifications/test-email | Send test email +*PartnersApi* | [**createPartner**](doc//PartnersApi.md#createpartner) | **POST** /partners | Create a partner +*PartnersApi* | [**createPartnerDeprecated**](doc//PartnersApi.md#createpartnerdeprecated) | **POST** /partners/{id} | Create a partner +*PartnersApi* | [**getPartners**](doc//PartnersApi.md#getpartners) | **GET** /partners | Retrieve partners +*PartnersApi* | [**removePartner**](doc//PartnersApi.md#removepartner) | **DELETE** /partners/{id} | Remove a partner +*PartnersApi* | [**updatePartner**](doc//PartnersApi.md#updatepartner) | **PUT** /partners/{id} | Update a partner +*PeopleApi* | [**createPerson**](doc//PeopleApi.md#createperson) | **POST** /people | Create a person +*PeopleApi* | [**deletePeople**](doc//PeopleApi.md#deletepeople) | **DELETE** /people | Delete people +*PeopleApi* | [**deletePerson**](doc//PeopleApi.md#deleteperson) | **DELETE** /people/{id} | Delete person +*PeopleApi* | [**getAllPeople**](doc//PeopleApi.md#getallpeople) | **GET** /people | Get all people +*PeopleApi* | [**getPerson**](doc//PeopleApi.md#getperson) | **GET** /people/{id} | Get a person +*PeopleApi* | [**getPersonStatistics**](doc//PeopleApi.md#getpersonstatistics) | **GET** /people/{id}/statistics | Get person statistics +*PeopleApi* | [**getPersonThumbnail**](doc//PeopleApi.md#getpersonthumbnail) | **GET** /people/{id}/thumbnail | Get person thumbnail +*PeopleApi* | [**mergePerson**](doc//PeopleApi.md#mergeperson) | **POST** /people/{id}/merge | Merge people +*PeopleApi* | [**reassignFaces**](doc//PeopleApi.md#reassignfaces) | **PUT** /people/{id}/reassign | Reassign faces +*PeopleApi* | [**updatePeople**](doc//PeopleApi.md#updatepeople) | **PUT** /people | Update people +*PeopleApi* | [**updatePerson**](doc//PeopleApi.md#updateperson) | **PUT** /people/{id} | Update person +*PluginsApi* | [**getPlugin**](doc//PluginsApi.md#getplugin) | **GET** /plugins/{id} | Retrieve a plugin +*PluginsApi* | [**getPlugins**](doc//PluginsApi.md#getplugins) | **GET** /plugins | List all plugins +*SearchApi* | [**getAssetsByCity**](doc//SearchApi.md#getassetsbycity) | **GET** /search/cities | Retrieve assets by city +*SearchApi* | [**getExploreData**](doc//SearchApi.md#getexploredata) | **GET** /search/explore | Retrieve explore data +*SearchApi* | [**getSearchSuggestions**](doc//SearchApi.md#getsearchsuggestions) | **GET** /search/suggestions | Retrieve search suggestions +*SearchApi* | [**searchAssetStatistics**](doc//SearchApi.md#searchassetstatistics) | **POST** /search/statistics | Search asset statistics +*SearchApi* | [**searchAssets**](doc//SearchApi.md#searchassets) | **POST** /search/metadata | Search assets by metadata +*SearchApi* | [**searchLargeAssets**](doc//SearchApi.md#searchlargeassets) | **POST** /search/large-assets | Search large assets +*SearchApi* | [**searchPerson**](doc//SearchApi.md#searchperson) | **GET** /search/person | Search people +*SearchApi* | [**searchPlaces**](doc//SearchApi.md#searchplaces) | **GET** /search/places | Search places +*SearchApi* | [**searchRandom**](doc//SearchApi.md#searchrandom) | **POST** /search/random | Search random assets +*SearchApi* | [**searchSmart**](doc//SearchApi.md#searchsmart) | **POST** /search/smart | Smart asset search +*ServerApi* | [**deleteServerLicense**](doc//ServerApi.md#deleteserverlicense) | **DELETE** /server/license | Delete server product key +*ServerApi* | [**getAboutInfo**](doc//ServerApi.md#getaboutinfo) | **GET** /server/about | Get server information +*ServerApi* | [**getApkLinks**](doc//ServerApi.md#getapklinks) | **GET** /server/apk-links | Get APK links +*ServerApi* | [**getServerConfig**](doc//ServerApi.md#getserverconfig) | **GET** /server/config | Get config +*ServerApi* | [**getServerFeatures**](doc//ServerApi.md#getserverfeatures) | **GET** /server/features | Get features +*ServerApi* | [**getServerLicense**](doc//ServerApi.md#getserverlicense) | **GET** /server/license | Get product key +*ServerApi* | [**getServerStatistics**](doc//ServerApi.md#getserverstatistics) | **GET** /server/statistics | Get statistics +*ServerApi* | [**getServerVersion**](doc//ServerApi.md#getserverversion) | **GET** /server/version | Get server version +*ServerApi* | [**getStorage**](doc//ServerApi.md#getstorage) | **GET** /server/storage | Get storage +*ServerApi* | [**getSupportedMediaTypes**](doc//ServerApi.md#getsupportedmediatypes) | **GET** /server/media-types | Get supported media types +*ServerApi* | [**getTheme**](doc//ServerApi.md#gettheme) | **GET** /server/theme | Get theme +*ServerApi* | [**getVersionCheck**](doc//ServerApi.md#getversioncheck) | **GET** /server/version-check | Get version check status +*ServerApi* | [**getVersionHistory**](doc//ServerApi.md#getversionhistory) | **GET** /server/version-history | Get version history +*ServerApi* | [**pingServer**](doc//ServerApi.md#pingserver) | **GET** /server/ping | Ping +*ServerApi* | [**setServerLicense**](doc//ServerApi.md#setserverlicense) | **PUT** /server/license | Set server product key +*SessionsApi* | [**createSession**](doc//SessionsApi.md#createsession) | **POST** /sessions | Create a session +*SessionsApi* | [**deleteAllSessions**](doc//SessionsApi.md#deleteallsessions) | **DELETE** /sessions | Delete all sessions +*SessionsApi* | [**deleteSession**](doc//SessionsApi.md#deletesession) | **DELETE** /sessions/{id} | Delete a session +*SessionsApi* | [**getSessions**](doc//SessionsApi.md#getsessions) | **GET** /sessions | Retrieve sessions +*SessionsApi* | [**lockSession**](doc//SessionsApi.md#locksession) | **POST** /sessions/{id}/lock | Lock a session +*SessionsApi* | [**updateSession**](doc//SessionsApi.md#updatesession) | **PUT** /sessions/{id} | Update a session +*SharedLinksApi* | [**addSharedLinkAssets**](doc//SharedLinksApi.md#addsharedlinkassets) | **PUT** /shared-links/{id}/assets | Add assets to a shared link +*SharedLinksApi* | [**createSharedLink**](doc//SharedLinksApi.md#createsharedlink) | **POST** /shared-links | Create a shared link +*SharedLinksApi* | [**getAllSharedLinks**](doc//SharedLinksApi.md#getallsharedlinks) | **GET** /shared-links | Retrieve all shared links +*SharedLinksApi* | [**getMySharedLink**](doc//SharedLinksApi.md#getmysharedlink) | **GET** /shared-links/me | Retrieve current shared link +*SharedLinksApi* | [**getSharedLinkById**](doc//SharedLinksApi.md#getsharedlinkbyid) | **GET** /shared-links/{id} | Retrieve a shared link +*SharedLinksApi* | [**removeSharedLink**](doc//SharedLinksApi.md#removesharedlink) | **DELETE** /shared-links/{id} | Delete a shared link +*SharedLinksApi* | [**removeSharedLinkAssets**](doc//SharedLinksApi.md#removesharedlinkassets) | **DELETE** /shared-links/{id}/assets | Remove assets from a shared link +*SharedLinksApi* | [**updateSharedLink**](doc//SharedLinksApi.md#updatesharedlink) | **PATCH** /shared-links/{id} | Update a shared link +*StacksApi* | [**createStack**](doc//StacksApi.md#createstack) | **POST** /stacks | Create a stack +*StacksApi* | [**deleteStack**](doc//StacksApi.md#deletestack) | **DELETE** /stacks/{id} | Delete a stack +*StacksApi* | [**deleteStacks**](doc//StacksApi.md#deletestacks) | **DELETE** /stacks | Delete stacks +*StacksApi* | [**getStack**](doc//StacksApi.md#getstack) | **GET** /stacks/{id} | Retrieve a stack +*StacksApi* | [**removeAssetFromStack**](doc//StacksApi.md#removeassetfromstack) | **DELETE** /stacks/{id}/assets/{assetId} | Remove an asset from a stack +*StacksApi* | [**searchStacks**](doc//StacksApi.md#searchstacks) | **GET** /stacks | Retrieve stacks +*StacksApi* | [**updateStack**](doc//StacksApi.md#updatestack) | **PUT** /stacks/{id} | Update a stack +*SyncApi* | [**deleteSyncAck**](doc//SyncApi.md#deletesyncack) | **DELETE** /sync/ack | Delete acknowledgements +*SyncApi* | [**getDeltaSync**](doc//SyncApi.md#getdeltasync) | **POST** /sync/delta-sync | Get delta sync for user +*SyncApi* | [**getFullSyncForUser**](doc//SyncApi.md#getfullsyncforuser) | **POST** /sync/full-sync | Get full sync for user +*SyncApi* | [**getSyncAck**](doc//SyncApi.md#getsyncack) | **GET** /sync/ack | Retrieve acknowledgements +*SyncApi* | [**getSyncStream**](doc//SyncApi.md#getsyncstream) | **POST** /sync/stream | Stream sync changes +*SyncApi* | [**sendSyncAck**](doc//SyncApi.md#sendsyncack) | **POST** /sync/ack | Acknowledge changes +*SystemConfigApi* | [**getConfig**](doc//SystemConfigApi.md#getconfig) | **GET** /system-config | Get system configuration +*SystemConfigApi* | [**getConfigDefaults**](doc//SystemConfigApi.md#getconfigdefaults) | **GET** /system-config/defaults | Get system configuration defaults +*SystemConfigApi* | [**getStorageTemplateOptions**](doc//SystemConfigApi.md#getstoragetemplateoptions) | **GET** /system-config/storage-template-options | Get storage template options +*SystemConfigApi* | [**updateConfig**](doc//SystemConfigApi.md#updateconfig) | **PUT** /system-config | Update system configuration +*SystemMetadataApi* | [**getAdminOnboarding**](doc//SystemMetadataApi.md#getadminonboarding) | **GET** /system-metadata/admin-onboarding | Retrieve admin onboarding +*SystemMetadataApi* | [**getReverseGeocodingState**](doc//SystemMetadataApi.md#getreversegeocodingstate) | **GET** /system-metadata/reverse-geocoding-state | Retrieve reverse geocoding state +*SystemMetadataApi* | [**getVersionCheckState**](doc//SystemMetadataApi.md#getversioncheckstate) | **GET** /system-metadata/version-check-state | Retrieve version check state +*SystemMetadataApi* | [**updateAdminOnboarding**](doc//SystemMetadataApi.md#updateadminonboarding) | **POST** /system-metadata/admin-onboarding | Update admin onboarding +*TagsApi* | [**bulkTagAssets**](doc//TagsApi.md#bulktagassets) | **PUT** /tags/assets | Tag assets +*TagsApi* | [**createTag**](doc//TagsApi.md#createtag) | **POST** /tags | Create a tag +*TagsApi* | [**deleteTag**](doc//TagsApi.md#deletetag) | **DELETE** /tags/{id} | Delete a tag +*TagsApi* | [**getAllTags**](doc//TagsApi.md#getalltags) | **GET** /tags | Retrieve tags +*TagsApi* | [**getTagById**](doc//TagsApi.md#gettagbyid) | **GET** /tags/{id} | Retrieve a tag +*TagsApi* | [**tagAssets**](doc//TagsApi.md#tagassets) | **PUT** /tags/{id}/assets | Tag assets +*TagsApi* | [**untagAssets**](doc//TagsApi.md#untagassets) | **DELETE** /tags/{id}/assets | Untag assets +*TagsApi* | [**updateTag**](doc//TagsApi.md#updatetag) | **PUT** /tags/{id} | Update a tag +*TagsApi* | [**upsertTags**](doc//TagsApi.md#upserttags) | **PUT** /tags | Upsert tags +*TimelineApi* | [**getTimeBucket**](doc//TimelineApi.md#gettimebucket) | **GET** /timeline/bucket | Get time bucket +*TimelineApi* | [**getTimeBuckets**](doc//TimelineApi.md#gettimebuckets) | **GET** /timeline/buckets | Get time buckets +*TrashApi* | [**emptyTrash**](doc//TrashApi.md#emptytrash) | **POST** /trash/empty | Empty trash +*TrashApi* | [**restoreAssets**](doc//TrashApi.md#restoreassets) | **POST** /trash/restore/assets | Restore assets +*TrashApi* | [**restoreTrash**](doc//TrashApi.md#restoretrash) | **POST** /trash/restore | Restore trash +*UsersApi* | [**createProfileImage**](doc//UsersApi.md#createprofileimage) | **POST** /users/profile-image | Create user profile image +*UsersApi* | [**deleteProfileImage**](doc//UsersApi.md#deleteprofileimage) | **DELETE** /users/profile-image | Delete user profile image +*UsersApi* | [**deleteUserLicense**](doc//UsersApi.md#deleteuserlicense) | **DELETE** /users/me/license | Delete user product key +*UsersApi* | [**deleteUserOnboarding**](doc//UsersApi.md#deleteuseronboarding) | **DELETE** /users/me/onboarding | Delete user onboarding +*UsersApi* | [**getMyPreferences**](doc//UsersApi.md#getmypreferences) | **GET** /users/me/preferences | Get my preferences +*UsersApi* | [**getMyUser**](doc//UsersApi.md#getmyuser) | **GET** /users/me | Get current user +*UsersApi* | [**getProfileImage**](doc//UsersApi.md#getprofileimage) | **GET** /users/{id}/profile-image | Retrieve user profile image +*UsersApi* | [**getUser**](doc//UsersApi.md#getuser) | **GET** /users/{id} | Retrieve a user +*UsersApi* | [**getUserLicense**](doc//UsersApi.md#getuserlicense) | **GET** /users/me/license | Retrieve user product key +*UsersApi* | [**getUserOnboarding**](doc//UsersApi.md#getuseronboarding) | **GET** /users/me/onboarding | Retrieve user onboarding +*UsersApi* | [**searchUsers**](doc//UsersApi.md#searchusers) | **GET** /users | Get all users +*UsersApi* | [**setUserLicense**](doc//UsersApi.md#setuserlicense) | **PUT** /users/me/license | Set user product key +*UsersApi* | [**setUserOnboarding**](doc//UsersApi.md#setuseronboarding) | **PUT** /users/me/onboarding | Update user onboarding +*UsersApi* | [**updateMyPreferences**](doc//UsersApi.md#updatemypreferences) | **PUT** /users/me/preferences | Update my preferences +*UsersApi* | [**updateMyUser**](doc//UsersApi.md#updatemyuser) | **PUT** /users/me | Update current user +*UsersAdminApi* | [**createUserAdmin**](doc//UsersAdminApi.md#createuseradmin) | **POST** /admin/users | Create a user +*UsersAdminApi* | [**deleteUserAdmin**](doc//UsersAdminApi.md#deleteuseradmin) | **DELETE** /admin/users/{id} | Delete a user +*UsersAdminApi* | [**getUserAdmin**](doc//UsersAdminApi.md#getuseradmin) | **GET** /admin/users/{id} | Retrieve a user +*UsersAdminApi* | [**getUserPreferencesAdmin**](doc//UsersAdminApi.md#getuserpreferencesadmin) | **GET** /admin/users/{id}/preferences | Retrieve user preferences +*UsersAdminApi* | [**getUserSessionsAdmin**](doc//UsersAdminApi.md#getusersessionsadmin) | **GET** /admin/users/{id}/sessions | Retrieve user sessions +*UsersAdminApi* | [**getUserStatisticsAdmin**](doc//UsersAdminApi.md#getuserstatisticsadmin) | **GET** /admin/users/{id}/statistics | Retrieve user statistics +*UsersAdminApi* | [**restoreUserAdmin**](doc//UsersAdminApi.md#restoreuseradmin) | **POST** /admin/users/{id}/restore | Restore a deleted user +*UsersAdminApi* | [**searchUsersAdmin**](doc//UsersAdminApi.md#searchusersadmin) | **GET** /admin/users | Search users +*UsersAdminApi* | [**updateUserAdmin**](doc//UsersAdminApi.md#updateuseradmin) | **PUT** /admin/users/{id} | Update a user +*UsersAdminApi* | [**updateUserPreferencesAdmin**](doc//UsersAdminApi.md#updateuserpreferencesadmin) | **PUT** /admin/users/{id}/preferences | Update user preferences +*ViewsApi* | [**getAssetsByOriginalPath**](doc//ViewsApi.md#getassetsbyoriginalpath) | **GET** /view/folder | Retrieve assets by original path +*ViewsApi* | [**getUniqueOriginalPaths**](doc//ViewsApi.md#getuniqueoriginalpaths) | **GET** /view/folder/unique-paths | Retrieve unique paths +*WorkflowsApi* | [**createWorkflow**](doc//WorkflowsApi.md#createworkflow) | **POST** /workflows | Create a workflow +*WorkflowsApi* | [**deleteWorkflow**](doc//WorkflowsApi.md#deleteworkflow) | **DELETE** /workflows/{id} | Delete a workflow +*WorkflowsApi* | [**getWorkflow**](doc//WorkflowsApi.md#getworkflow) | **GET** /workflows/{id} | Retrieve a workflow +*WorkflowsApi* | [**getWorkflows**](doc//WorkflowsApi.md#getworkflows) | **GET** /workflows | List all workflows +*WorkflowsApi* | [**updateWorkflow**](doc//WorkflowsApi.md#updateworkflow) | **PUT** /workflows/{id} | Update a workflow ## Documentation For Models @@ -312,13 +325,13 @@ Class | Method | HTTP request | Description - [AlbumsAddAssetsResponseDto](doc//AlbumsAddAssetsResponseDto.md) - [AlbumsResponse](doc//AlbumsResponse.md) - [AlbumsUpdate](doc//AlbumsUpdate.md) - - [AllJobStatusResponseDto](doc//AllJobStatusResponseDto.md) - [AssetBulkDeleteDto](doc//AssetBulkDeleteDto.md) - [AssetBulkUpdateDto](doc//AssetBulkUpdateDto.md) - [AssetBulkUploadCheckDto](doc//AssetBulkUploadCheckDto.md) - [AssetBulkUploadCheckItem](doc//AssetBulkUploadCheckItem.md) - [AssetBulkUploadCheckResponseDto](doc//AssetBulkUploadCheckResponseDto.md) - [AssetBulkUploadCheckResult](doc//AssetBulkUploadCheckResult.md) + - [AssetCopyDto](doc//AssetCopyDto.md) - [AssetDeltaSyncDto](doc//AssetDeltaSyncDto.md) - [AssetDeltaSyncResponseDto](doc//AssetDeltaSyncResponseDto.md) - [AssetFaceCreateDto](doc//AssetFaceCreateDto.md) @@ -339,6 +352,7 @@ Class | Method | HTTP request | Description - [AssetMetadataResponseDto](doc//AssetMetadataResponseDto.md) - [AssetMetadataUpsertDto](doc//AssetMetadataUpsertDto.md) - [AssetMetadataUpsertItemDto](doc//AssetMetadataUpsertItemDto.md) + - [AssetOcrResponseDto](doc//AssetOcrResponseDto.md) - [AssetOrder](doc//AssetOrder.md) - [AssetResponseDto](doc//AssetResponseDto.md) - [AssetStackResponseDto](doc//AssetStackResponseDto.md) @@ -359,6 +373,7 @@ Class | Method | HTTP request | Description - [CheckExistingAssetsDto](doc//CheckExistingAssetsDto.md) - [CheckExistingAssetsResponseDto](doc//CheckExistingAssetsResponseDto.md) - [Colorspace](doc//Colorspace.md) + - [ContributorCountResponseDto](doc//ContributorCountResponseDto.md) - [CreateAlbumDto](doc//CreateAlbumDto.md) - [CreateLibraryDto](doc//CreateLibraryDto.md) - [CreateProfileImageResponseDto](doc//CreateProfileImageResponseDto.md) @@ -378,13 +393,8 @@ Class | Method | HTTP request | Description - [FoldersResponse](doc//FoldersResponse.md) - [FoldersUpdate](doc//FoldersUpdate.md) - [ImageFormat](doc//ImageFormat.md) - - [JobCommand](doc//JobCommand.md) - - [JobCommandDto](doc//JobCommandDto.md) - - [JobCountsDto](doc//JobCountsDto.md) - [JobCreateDto](doc//JobCreateDto.md) - - [JobName](doc//JobName.md) - [JobSettingsDto](doc//JobSettingsDto.md) - - [JobStatusDto](doc//JobStatusDto.md) - [LibraryResponseDto](doc//LibraryResponseDto.md) - [LibraryStatsResponseDto](doc//LibraryStatsResponseDto.md) - [LicenseKeyDto](doc//LicenseKeyDto.md) @@ -401,6 +411,7 @@ Class | Method | HTTP request | Description - [MemoriesUpdate](doc//MemoriesUpdate.md) - [MemoryCreateDto](doc//MemoryCreateDto.md) - [MemoryResponseDto](doc//MemoryResponseDto.md) + - [MemorySearchOrder](doc//MemorySearchOrder.md) - [MemoryStatisticsResponseDto](doc//MemoryStatisticsResponseDto.md) - [MemoryType](doc//MemoryType.md) - [MemoryUpdateDto](doc//MemoryUpdateDto.md) @@ -417,6 +428,7 @@ Class | Method | HTTP request | Description - [OAuthCallbackDto](doc//OAuthCallbackDto.md) - [OAuthConfigDto](doc//OAuthConfigDto.md) - [OAuthTokenEndpointAuthMethod](doc//OAuthTokenEndpointAuthMethod.md) + - [OcrConfig](doc//OcrConfig.md) - [OnThisDayDto](doc//OnThisDayDto.md) - [OnboardingDto](doc//OnboardingDto.md) - [OnboardingResponseDto](doc//OnboardingResponseDto.md) @@ -439,9 +451,20 @@ Class | Method | HTTP request | Description - [PinCodeResetDto](doc//PinCodeResetDto.md) - [PinCodeSetupDto](doc//PinCodeSetupDto.md) - [PlacesResponseDto](doc//PlacesResponseDto.md) + - [PluginActionResponseDto](doc//PluginActionResponseDto.md) + - [PluginContext](doc//PluginContext.md) + - [PluginFilterResponseDto](doc//PluginFilterResponseDto.md) + - [PluginResponseDto](doc//PluginResponseDto.md) + - [PluginTriggerType](doc//PluginTriggerType.md) - [PurchaseResponse](doc//PurchaseResponse.md) - [PurchaseUpdate](doc//PurchaseUpdate.md) + - [QueueCommand](doc//QueueCommand.md) + - [QueueCommandDto](doc//QueueCommandDto.md) + - [QueueName](doc//QueueName.md) + - [QueueResponseDto](doc//QueueResponseDto.md) + - [QueueStatisticsDto](doc//QueueStatisticsDto.md) - [QueueStatusDto](doc//QueueStatusDto.md) + - [QueuesResponseDto](doc//QueuesResponseDto.md) - [RandomSearchDto](doc//RandomSearchDto.md) - [RatingsResponse](doc//RatingsResponse.md) - [RatingsUpdate](doc//RatingsUpdate.md) @@ -592,6 +615,13 @@ Class | Method | HTTP request | Description - [VersionCheckStateResponseDto](doc//VersionCheckStateResponseDto.md) - [VideoCodec](doc//VideoCodec.md) - [VideoContainer](doc//VideoContainer.md) + - [WorkflowActionItemDto](doc//WorkflowActionItemDto.md) + - [WorkflowActionResponseDto](doc//WorkflowActionResponseDto.md) + - [WorkflowCreateDto](doc//WorkflowCreateDto.md) + - [WorkflowFilterItemDto](doc//WorkflowFilterItemDto.md) + - [WorkflowFilterResponseDto](doc//WorkflowFilterResponseDto.md) + - [WorkflowResponseDto](doc//WorkflowResponseDto.md) + - [WorkflowUpdateDto](doc//WorkflowUpdateDto.md) ## Documentation For Authorization diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index df2c2226b1..f3db370c92 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -34,8 +34,8 @@ part 'api/api_keys_api.dart'; part 'api/activities_api.dart'; part 'api/albums_api.dart'; part 'api/assets_api.dart'; -part 'api/auth_admin_api.dart'; part 'api/authentication_api.dart'; +part 'api/authentication_admin_api.dart'; part 'api/deprecated_api.dart'; part 'api/download_api.dart'; part 'api/duplicates_api.dart'; @@ -46,9 +46,9 @@ part 'api/map_api.dart'; part 'api/memories_api.dart'; part 'api/notifications_api.dart'; part 'api/notifications_admin_api.dart'; -part 'api/o_auth_api.dart'; part 'api/partners_api.dart'; part 'api/people_api.dart'; +part 'api/plugins_api.dart'; part 'api/search_api.dart'; part 'api/server_api.dart'; part 'api/sessions_api.dart'; @@ -62,7 +62,8 @@ part 'api/timeline_api.dart'; part 'api/trash_api.dart'; part 'api/users_api.dart'; part 'api/users_admin_api.dart'; -part 'api/view_api.dart'; +part 'api/views_api.dart'; +part 'api/workflows_api.dart'; part 'model/api_key_create_dto.dart'; part 'model/api_key_create_response_dto.dart'; @@ -83,13 +84,13 @@ part 'model/albums_add_assets_dto.dart'; part 'model/albums_add_assets_response_dto.dart'; part 'model/albums_response.dart'; part 'model/albums_update.dart'; -part 'model/all_job_status_response_dto.dart'; part 'model/asset_bulk_delete_dto.dart'; part 'model/asset_bulk_update_dto.dart'; part 'model/asset_bulk_upload_check_dto.dart'; part 'model/asset_bulk_upload_check_item.dart'; part 'model/asset_bulk_upload_check_response_dto.dart'; part 'model/asset_bulk_upload_check_result.dart'; +part 'model/asset_copy_dto.dart'; part 'model/asset_delta_sync_dto.dart'; part 'model/asset_delta_sync_response_dto.dart'; part 'model/asset_face_create_dto.dart'; @@ -110,6 +111,7 @@ part 'model/asset_metadata_key.dart'; part 'model/asset_metadata_response_dto.dart'; part 'model/asset_metadata_upsert_dto.dart'; part 'model/asset_metadata_upsert_item_dto.dart'; +part 'model/asset_ocr_response_dto.dart'; part 'model/asset_order.dart'; part 'model/asset_response_dto.dart'; part 'model/asset_stack_response_dto.dart'; @@ -130,6 +132,7 @@ part 'model/change_password_dto.dart'; part 'model/check_existing_assets_dto.dart'; part 'model/check_existing_assets_response_dto.dart'; part 'model/colorspace.dart'; +part 'model/contributor_count_response_dto.dart'; part 'model/create_album_dto.dart'; part 'model/create_library_dto.dart'; part 'model/create_profile_image_response_dto.dart'; @@ -149,13 +152,8 @@ part 'model/facial_recognition_config.dart'; part 'model/folders_response.dart'; part 'model/folders_update.dart'; part 'model/image_format.dart'; -part 'model/job_command.dart'; -part 'model/job_command_dto.dart'; -part 'model/job_counts_dto.dart'; part 'model/job_create_dto.dart'; -part 'model/job_name.dart'; part 'model/job_settings_dto.dart'; -part 'model/job_status_dto.dart'; part 'model/library_response_dto.dart'; part 'model/library_stats_response_dto.dart'; part 'model/license_key_dto.dart'; @@ -172,6 +170,7 @@ part 'model/memories_response.dart'; part 'model/memories_update.dart'; part 'model/memory_create_dto.dart'; part 'model/memory_response_dto.dart'; +part 'model/memory_search_order.dart'; part 'model/memory_statistics_response_dto.dart'; part 'model/memory_type.dart'; part 'model/memory_update_dto.dart'; @@ -188,6 +187,7 @@ part 'model/o_auth_authorize_response_dto.dart'; part 'model/o_auth_callback_dto.dart'; part 'model/o_auth_config_dto.dart'; part 'model/o_auth_token_endpoint_auth_method.dart'; +part 'model/ocr_config.dart'; part 'model/on_this_day_dto.dart'; part 'model/onboarding_dto.dart'; part 'model/onboarding_response_dto.dart'; @@ -210,9 +210,20 @@ part 'model/pin_code_change_dto.dart'; part 'model/pin_code_reset_dto.dart'; part 'model/pin_code_setup_dto.dart'; part 'model/places_response_dto.dart'; +part 'model/plugin_action_response_dto.dart'; +part 'model/plugin_context.dart'; +part 'model/plugin_filter_response_dto.dart'; +part 'model/plugin_response_dto.dart'; +part 'model/plugin_trigger_type.dart'; part 'model/purchase_response.dart'; part 'model/purchase_update.dart'; +part 'model/queue_command.dart'; +part 'model/queue_command_dto.dart'; +part 'model/queue_name.dart'; +part 'model/queue_response_dto.dart'; +part 'model/queue_statistics_dto.dart'; part 'model/queue_status_dto.dart'; +part 'model/queues_response_dto.dart'; part 'model/random_search_dto.dart'; part 'model/ratings_response.dart'; part 'model/ratings_update.dart'; @@ -363,6 +374,13 @@ part 'model/validate_library_response_dto.dart'; part 'model/version_check_state_response_dto.dart'; part 'model/video_codec.dart'; part 'model/video_container.dart'; +part 'model/workflow_action_item_dto.dart'; +part 'model/workflow_action_response_dto.dart'; +part 'model/workflow_create_dto.dart'; +part 'model/workflow_filter_item_dto.dart'; +part 'model/workflow_filter_response_dto.dart'; +part 'model/workflow_response_dto.dart'; +part 'model/workflow_update_dto.dart'; /// An [ApiClient] instance that uses the default values obtained from diff --git a/mobile/openapi/lib/api/activities_api.dart b/mobile/openapi/lib/api/activities_api.dart index 67015499fa..b92f95be72 100644 --- a/mobile/openapi/lib/api/activities_api.dart +++ b/mobile/openapi/lib/api/activities_api.dart @@ -16,7 +16,9 @@ class ActivitiesApi { final ApiClient apiClient; - /// This endpoint requires the `activity.create` permission. + /// Create an activity + /// + /// Create a like or a comment for an album, or an asset in an album. /// /// Note: This method returns the HTTP [Response]. /// @@ -48,7 +50,9 @@ class ActivitiesApi { ); } - /// This endpoint requires the `activity.create` permission. + /// Create an activity + /// + /// Create a like or a comment for an album, or an asset in an album. /// /// Parameters: /// @@ -68,7 +72,9 @@ class ActivitiesApi { return null; } - /// This endpoint requires the `activity.delete` permission. + /// Delete an activity + /// + /// Removes a like or comment from a given album or asset in an album. /// /// Note: This method returns the HTTP [Response]. /// @@ -101,7 +107,9 @@ class ActivitiesApi { ); } - /// This endpoint requires the `activity.delete` permission. + /// Delete an activity + /// + /// Removes a like or comment from a given album or asset in an album. /// /// Parameters: /// @@ -113,7 +121,9 @@ class ActivitiesApi { } } - /// This endpoint requires the `activity.read` permission. + /// List all activities + /// + /// Returns a list of activities for the selected asset or album. The activities are returned in sorted order, with the oldest activities appearing first. /// /// Note: This method returns the HTTP [Response]. /// @@ -167,7 +177,9 @@ class ActivitiesApi { ); } - /// This endpoint requires the `activity.read` permission. + /// List all activities + /// + /// Returns a list of activities for the selected asset or album. The activities are returned in sorted order, with the oldest activities appearing first. /// /// Parameters: /// @@ -198,7 +210,9 @@ class ActivitiesApi { return null; } - /// This endpoint requires the `activity.statistics` permission. + /// Retrieve activity statistics + /// + /// Returns the number of likes and comments for a given album or asset in an album. /// /// Note: This method returns the HTTP [Response]. /// @@ -237,7 +251,9 @@ class ActivitiesApi { ); } - /// This endpoint requires the `activity.statistics` permission. + /// Retrieve activity statistics + /// + /// Returns the number of likes and comments for a given album or asset in an album. /// /// Parameters: /// diff --git a/mobile/openapi/lib/api/albums_api.dart b/mobile/openapi/lib/api/albums_api.dart index a45083669c..1042a2850f 100644 --- a/mobile/openapi/lib/api/albums_api.dart +++ b/mobile/openapi/lib/api/albums_api.dart @@ -16,7 +16,9 @@ class AlbumsApi { final ApiClient apiClient; - /// This endpoint requires the `albumAsset.create` permission. + /// Add assets to an album + /// + /// Add multiple assets to a specific album by its ID. /// /// Note: This method returns the HTTP [Response]. /// @@ -62,7 +64,9 @@ class AlbumsApi { ); } - /// This endpoint requires the `albumAsset.create` permission. + /// Add assets to an album + /// + /// Add multiple assets to a specific album by its ID. /// /// Parameters: /// @@ -91,7 +95,9 @@ class AlbumsApi { return null; } - /// This endpoint requires the `albumAsset.create` permission. + /// Add assets to albums + /// + /// Send a list of asset IDs and album IDs to add each asset to each album. /// /// Note: This method returns the HTTP [Response]. /// @@ -134,7 +140,9 @@ class AlbumsApi { ); } - /// This endpoint requires the `albumAsset.create` permission. + /// Add assets to albums + /// + /// Send a list of asset IDs and album IDs to add each asset to each album. /// /// Parameters: /// @@ -158,7 +166,9 @@ class AlbumsApi { return null; } - /// This endpoint requires the `albumUser.create` permission. + /// Share album with users + /// + /// Share an album with multiple users. Each user can be given a specific role in the album. /// /// Note: This method returns the HTTP [Response]. /// @@ -193,7 +203,9 @@ class AlbumsApi { ); } - /// This endpoint requires the `albumUser.create` permission. + /// Share album with users + /// + /// Share an album with multiple users. Each user can be given a specific role in the album. /// /// Parameters: /// @@ -215,7 +227,9 @@ class AlbumsApi { return null; } - /// This endpoint requires the `album.create` permission. + /// Create an album + /// + /// Create a new album. The album can also be created with initial users and assets. /// /// Note: This method returns the HTTP [Response]. /// @@ -247,7 +261,9 @@ class AlbumsApi { ); } - /// This endpoint requires the `album.create` permission. + /// Create an album + /// + /// Create a new album. The album can also be created with initial users and assets. /// /// Parameters: /// @@ -267,7 +283,9 @@ class AlbumsApi { return null; } - /// This endpoint requires the `album.delete` permission. + /// Delete an album + /// + /// Delete a specific album by its ID. Note the album is initially trashed and then immediately scheduled for deletion, but relies on a background job to complete the process. /// /// Note: This method returns the HTTP [Response]. /// @@ -300,7 +318,9 @@ class AlbumsApi { ); } - /// This endpoint requires the `album.delete` permission. + /// Delete an album + /// + /// Delete a specific album by its ID. Note the album is initially trashed and then immediately scheduled for deletion, but relies on a background job to complete the process. /// /// Parameters: /// @@ -312,7 +332,9 @@ class AlbumsApi { } } - /// This endpoint requires the `album.read` permission. + /// Retrieve an album + /// + /// Retrieve information about a specific album by its ID. /// /// Note: This method returns the HTTP [Response]. /// @@ -361,7 +383,9 @@ class AlbumsApi { ); } - /// This endpoint requires the `album.read` permission. + /// Retrieve an album + /// + /// Retrieve information about a specific album by its ID. /// /// Parameters: /// @@ -387,7 +411,9 @@ class AlbumsApi { return null; } - /// This endpoint requires the `album.statistics` permission. + /// Retrieve album statistics + /// + /// Returns statistics about the albums available to the authenticated user. /// /// Note: This method returns the HTTP [Response]. Future getAlbumStatisticsWithHttpInfo() async { @@ -415,7 +441,9 @@ class AlbumsApi { ); } - /// This endpoint requires the `album.statistics` permission. + /// Retrieve album statistics + /// + /// Returns statistics about the albums available to the authenticated user. Future getAlbumStatistics() async { final response = await getAlbumStatisticsWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -431,7 +459,9 @@ class AlbumsApi { return null; } - /// This endpoint requires the `album.read` permission. + /// List all albums + /// + /// Retrieve a list of albums available to the authenticated user. /// /// Note: This method returns the HTTP [Response]. /// @@ -473,7 +503,9 @@ class AlbumsApi { ); } - /// This endpoint requires the `album.read` permission. + /// List all albums + /// + /// Retrieve a list of albums available to the authenticated user. /// /// Parameters: /// @@ -499,7 +531,9 @@ class AlbumsApi { return null; } - /// This endpoint requires the `albumAsset.delete` permission. + /// Remove assets from an album + /// + /// Remove multiple assets from a specific album by its ID. /// /// Note: This method returns the HTTP [Response]. /// @@ -534,7 +568,9 @@ class AlbumsApi { ); } - /// This endpoint requires the `albumAsset.delete` permission. + /// Remove assets from an album + /// + /// Remove multiple assets from a specific album by its ID. /// /// Parameters: /// @@ -559,7 +595,9 @@ class AlbumsApi { return null; } - /// This endpoint requires the `albumUser.delete` permission. + /// Remove user from album + /// + /// Remove a user from an album. Use an ID of \"me\" to leave a shared album. /// /// Note: This method returns the HTTP [Response]. /// @@ -595,7 +633,9 @@ class AlbumsApi { ); } - /// This endpoint requires the `albumUser.delete` permission. + /// Remove user from album + /// + /// Remove a user from an album. Use an ID of \"me\" to leave a shared album. /// /// Parameters: /// @@ -609,7 +649,9 @@ class AlbumsApi { } } - /// This endpoint requires the `album.update` permission. + /// Update an album + /// + /// Update the information of a specific album by its ID. This endpoint can be used to update the album name, description, sort order, etc. However, it is not used to add or remove assets or users from the album. /// /// Note: This method returns the HTTP [Response]. /// @@ -644,7 +686,9 @@ class AlbumsApi { ); } - /// This endpoint requires the `album.update` permission. + /// Update an album + /// + /// Update the information of a specific album by its ID. This endpoint can be used to update the album name, description, sort order, etc. However, it is not used to add or remove assets or users from the album. /// /// Parameters: /// @@ -666,7 +710,9 @@ class AlbumsApi { return null; } - /// This endpoint requires the `albumUser.update` permission. + /// Update user role + /// + /// Change the role for a specific user in a specific album. /// /// Note: This method returns the HTTP [Response]. /// @@ -704,7 +750,9 @@ class AlbumsApi { ); } - /// This endpoint requires the `albumUser.update` permission. + /// Update user role + /// + /// Change the role for a specific user in a specific album. /// /// Parameters: /// diff --git a/mobile/openapi/lib/api/api_keys_api.dart b/mobile/openapi/lib/api/api_keys_api.dart index 3ac829c30c..0bd26575c6 100644 --- a/mobile/openapi/lib/api/api_keys_api.dart +++ b/mobile/openapi/lib/api/api_keys_api.dart @@ -16,7 +16,9 @@ class APIKeysApi { final ApiClient apiClient; - /// This endpoint requires the `apiKey.create` permission. + /// Create an API key + /// + /// Creates a new API key. It will be limited to the permissions specified. /// /// Note: This method returns the HTTP [Response]. /// @@ -48,7 +50,9 @@ class APIKeysApi { ); } - /// This endpoint requires the `apiKey.create` permission. + /// Create an API key + /// + /// Creates a new API key. It will be limited to the permissions specified. /// /// Parameters: /// @@ -68,7 +72,9 @@ class APIKeysApi { return null; } - /// This endpoint requires the `apiKey.delete` permission. + /// Delete an API key + /// + /// Deletes an API key identified by its ID. The current user must own this API key. /// /// Note: This method returns the HTTP [Response]. /// @@ -101,7 +107,9 @@ class APIKeysApi { ); } - /// This endpoint requires the `apiKey.delete` permission. + /// Delete an API key + /// + /// Deletes an API key identified by its ID. The current user must own this API key. /// /// Parameters: /// @@ -113,7 +121,9 @@ class APIKeysApi { } } - /// This endpoint requires the `apiKey.read` permission. + /// Retrieve an API key + /// + /// Retrieve an API key by its ID. The current user must own this API key. /// /// Note: This method returns the HTTP [Response]. /// @@ -146,7 +156,9 @@ class APIKeysApi { ); } - /// This endpoint requires the `apiKey.read` permission. + /// Retrieve an API key + /// + /// Retrieve an API key by its ID. The current user must own this API key. /// /// Parameters: /// @@ -166,7 +178,9 @@ class APIKeysApi { return null; } - /// This endpoint requires the `apiKey.read` permission. + /// List all API keys + /// + /// Retrieve all API keys of the current user. /// /// Note: This method returns the HTTP [Response]. Future getApiKeysWithHttpInfo() async { @@ -194,7 +208,9 @@ class APIKeysApi { ); } - /// This endpoint requires the `apiKey.read` permission. + /// List all API keys + /// + /// Retrieve all API keys of the current user. Future?> getApiKeys() async { final response = await getApiKeysWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -213,7 +229,11 @@ class APIKeysApi { return null; } - /// Performs an HTTP 'GET /api-keys/me' operation and returns the [Response]. + /// Retrieve the current API key + /// + /// Retrieve the API key that is used to access this endpoint. + /// + /// Note: This method returns the HTTP [Response]. Future getMyApiKeyWithHttpInfo() async { // ignore: prefer_const_declarations final apiPath = r'/api-keys/me'; @@ -239,6 +259,9 @@ class APIKeysApi { ); } + /// Retrieve the current API key + /// + /// Retrieve the API key that is used to access this endpoint. Future getMyApiKey() async { final response = await getMyApiKeyWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -254,7 +277,9 @@ class APIKeysApi { return null; } - /// This endpoint requires the `apiKey.update` permission. + /// Update an API key + /// + /// Updates the name and permissions of an API key by its ID. The current user must own this API key. /// /// Note: This method returns the HTTP [Response]. /// @@ -289,7 +314,9 @@ class APIKeysApi { ); } - /// This endpoint requires the `apiKey.update` permission. + /// Update an API key + /// + /// Updates the name and permissions of an API key by its ID. The current user must own this API key. /// /// Parameters: /// diff --git a/mobile/openapi/lib/api/assets_api.dart b/mobile/openapi/lib/api/assets_api.dart index 063f9ea43b..5020afc4b2 100644 --- a/mobile/openapi/lib/api/assets_api.dart +++ b/mobile/openapi/lib/api/assets_api.dart @@ -16,9 +16,9 @@ class AssetsApi { final ApiClient apiClient; - /// checkBulkUpload + /// Check bulk upload /// - /// Checks if assets exist by checksums. This endpoint requires the `asset.upload` permission. + /// Determine which assets have already been uploaded to the server based on their SHA1 checksums. /// /// Note: This method returns the HTTP [Response]. /// @@ -50,9 +50,9 @@ class AssetsApi { ); } - /// checkBulkUpload + /// Check bulk upload /// - /// Checks if assets exist by checksums. This endpoint requires the `asset.upload` permission. + /// Determine which assets have already been uploaded to the server based on their SHA1 checksums. /// /// Parameters: /// @@ -72,7 +72,7 @@ class AssetsApi { return null; } - /// checkExistingAssets + /// Check existing assets /// /// Checks if multiple assets exist on the server and returns all existing - used by background backup /// @@ -106,7 +106,7 @@ class AssetsApi { ); } - /// checkExistingAssets + /// Check existing assets /// /// Checks if multiple assets exist on the server and returns all existing - used by background backup /// @@ -128,7 +128,57 @@ class AssetsApi { return null; } - /// This endpoint requires the `asset.update` permission. + /// Copy asset + /// + /// Copy asset information like albums, tags, etc. from one asset to another. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [AssetCopyDto] assetCopyDto (required): + Future copyAssetWithHttpInfo(AssetCopyDto assetCopyDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/copy'; + + // ignore: prefer_final_locals + Object? postBody = assetCopyDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Copy asset + /// + /// Copy asset information like albums, tags, etc. from one asset to another. + /// + /// Parameters: + /// + /// * [AssetCopyDto] assetCopyDto (required): + Future copyAsset(AssetCopyDto assetCopyDto,) async { + final response = await copyAssetWithHttpInfo(assetCopyDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Delete asset metadata by key + /// + /// Delete a specific metadata key-value pair associated with the specified asset. /// /// Note: This method returns the HTTP [Response]. /// @@ -164,7 +214,9 @@ class AssetsApi { ); } - /// This endpoint requires the `asset.update` permission. + /// Delete asset metadata by key + /// + /// Delete a specific metadata key-value pair associated with the specified asset. /// /// Parameters: /// @@ -178,7 +230,9 @@ class AssetsApi { } } - /// This endpoint requires the `asset.delete` permission. + /// Delete assets + /// + /// Deletes multiple assets at the same time. /// /// Note: This method returns the HTTP [Response]. /// @@ -210,7 +264,9 @@ class AssetsApi { ); } - /// This endpoint requires the `asset.delete` permission. + /// Delete assets + /// + /// Deletes multiple assets at the same time. /// /// Parameters: /// @@ -222,7 +278,9 @@ class AssetsApi { } } - /// This endpoint requires the `asset.download` permission. + /// Download original asset + /// + /// Downloads the original file of the specified asset. /// /// Note: This method returns the HTTP [Response]. /// @@ -266,7 +324,9 @@ class AssetsApi { ); } - /// This endpoint requires the `asset.download` permission. + /// Download original asset + /// + /// Downloads the original file of the specified asset. /// /// Parameters: /// @@ -290,7 +350,7 @@ class AssetsApi { return null; } - /// getAllUserAssetsByDeviceId + /// Retrieve assets by device ID /// /// Get all asset of a device that are in the database, ID only. /// @@ -325,7 +385,7 @@ class AssetsApi { ); } - /// getAllUserAssetsByDeviceId + /// Retrieve assets by device ID /// /// Get all asset of a device that are in the database, ID only. /// @@ -350,7 +410,9 @@ class AssetsApi { return null; } - /// This endpoint requires the `asset.read` permission. + /// Retrieve an asset + /// + /// Retrieve detailed information about a specific asset. /// /// Note: This method returns the HTTP [Response]. /// @@ -394,7 +456,9 @@ class AssetsApi { ); } - /// This endpoint requires the `asset.read` permission. + /// Retrieve an asset + /// + /// Retrieve detailed information about a specific asset. /// /// Parameters: /// @@ -418,7 +482,9 @@ class AssetsApi { return null; } - /// This endpoint requires the `asset.read` permission. + /// Get asset metadata + /// + /// Retrieve all metadata key-value pairs associated with the specified asset. /// /// Note: This method returns the HTTP [Response]. /// @@ -451,7 +517,9 @@ class AssetsApi { ); } - /// This endpoint requires the `asset.read` permission. + /// Get asset metadata + /// + /// Retrieve all metadata key-value pairs associated with the specified asset. /// /// Parameters: /// @@ -474,7 +542,9 @@ class AssetsApi { return null; } - /// This endpoint requires the `asset.read` permission. + /// Retrieve asset metadata by key + /// + /// Retrieve the value of a specific metadata key associated with the specified asset. /// /// Note: This method returns the HTTP [Response]. /// @@ -510,7 +580,9 @@ class AssetsApi { ); } - /// This endpoint requires the `asset.read` permission. + /// Retrieve asset metadata by key + /// + /// Retrieve the value of a specific metadata key associated with the specified asset. /// /// Parameters: /// @@ -532,7 +604,69 @@ class AssetsApi { return null; } - /// This endpoint requires the `asset.statistics` permission. + /// Retrieve asset OCR data + /// + /// Retrieve all OCR (Optical Character Recognition) data associated with the specified asset. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getAssetOcrWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/{id}/ocr' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve asset OCR data + /// + /// Retrieve all OCR (Optical Character Recognition) data associated with the specified asset. + /// + /// Parameters: + /// + /// * [String] id (required): + Future?> getAssetOcr(String id,) async { + final response = await getAssetOcrWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Get asset statistics + /// + /// Retrieve various statistics about the assets owned by the authenticated user. /// /// Note: This method returns the HTTP [Response]. /// @@ -578,7 +712,9 @@ class AssetsApi { ); } - /// This endpoint requires the `asset.statistics` permission. + /// Get asset statistics + /// + /// Retrieve various statistics about the assets owned by the authenticated user. /// /// Parameters: /// @@ -602,7 +738,9 @@ class AssetsApi { return null; } - /// This property was deprecated in v1.116.0. This endpoint requires the `asset.read` permission. + /// Get random assets + /// + /// Retrieve a specified number of random assets for the authenticated user. /// /// Note: This method returns the HTTP [Response]. /// @@ -638,7 +776,9 @@ class AssetsApi { ); } - /// This property was deprecated in v1.116.0. This endpoint requires the `asset.read` permission. + /// Get random assets + /// + /// Retrieve a specified number of random assets for the authenticated user. /// /// Parameters: /// @@ -661,7 +801,9 @@ class AssetsApi { return null; } - /// This endpoint requires the `asset.view` permission. + /// Play asset video + /// + /// Streams the video file for the specified asset. This endpoint also supports byte range requests. /// /// Note: This method returns the HTTP [Response]. /// @@ -705,7 +847,9 @@ class AssetsApi { ); } - /// This endpoint requires the `asset.view` permission. + /// Play asset video + /// + /// Streams the video file for the specified asset. This endpoint also supports byte range requests. /// /// Parameters: /// @@ -729,9 +873,9 @@ class AssetsApi { return null; } - /// Replace the asset with new file, without changing its id + /// Replace asset /// - /// This property was deprecated in v1.142.0. Replace the asset with new file, without changing its id. This endpoint requires the `asset.replace` permission. + /// Replace the asset with new file, without changing its id. /// /// Note: This method returns the HTTP [Response]. /// @@ -823,9 +967,9 @@ class AssetsApi { ); } - /// Replace the asset with new file, without changing its id + /// Replace asset /// - /// This property was deprecated in v1.142.0. Replace the asset with new file, without changing its id. This endpoint requires the `asset.replace` permission. + /// Replace the asset with new file, without changing its id. /// /// Parameters: /// @@ -863,7 +1007,12 @@ class AssetsApi { return null; } - /// Performs an HTTP 'POST /assets/jobs' operation and returns the [Response]. + /// Run an asset job + /// + /// Run a specific job on a set of assets. + /// + /// Note: This method returns the HTTP [Response]. + /// /// Parameters: /// /// * [AssetJobsDto] assetJobsDto (required): @@ -892,6 +1041,10 @@ class AssetsApi { ); } + /// Run an asset job + /// + /// Run a specific job on a set of assets. + /// /// Parameters: /// /// * [AssetJobsDto] assetJobsDto (required): @@ -902,7 +1055,9 @@ class AssetsApi { } } - /// This endpoint requires the `asset.update` permission. + /// Update an asset + /// + /// Update information of a specific asset. /// /// Note: This method returns the HTTP [Response]. /// @@ -937,7 +1092,9 @@ class AssetsApi { ); } - /// This endpoint requires the `asset.update` permission. + /// Update an asset + /// + /// Update information of a specific asset. /// /// Parameters: /// @@ -959,7 +1116,9 @@ class AssetsApi { return null; } - /// This endpoint requires the `asset.update` permission. + /// Update asset metadata + /// + /// Update or add metadata key-value pairs for the specified asset. /// /// Note: This method returns the HTTP [Response]. /// @@ -994,7 +1153,9 @@ class AssetsApi { ); } - /// This endpoint requires the `asset.update` permission. + /// Update asset metadata + /// + /// Update or add metadata key-value pairs for the specified asset. /// /// Parameters: /// @@ -1019,7 +1180,9 @@ class AssetsApi { return null; } - /// This endpoint requires the `asset.update` permission. + /// Update assets + /// + /// Updates multiple assets at the same time. /// /// Note: This method returns the HTTP [Response]. /// @@ -1051,7 +1214,9 @@ class AssetsApi { ); } - /// This endpoint requires the `asset.update` permission. + /// Update assets + /// + /// Updates multiple assets at the same time. /// /// Parameters: /// @@ -1063,7 +1228,9 @@ class AssetsApi { } } - /// This endpoint requires the `asset.upload` permission. + /// Upload asset + /// + /// Uploads a new asset to the server. /// /// Note: This method returns the HTTP [Response]. /// @@ -1190,7 +1357,9 @@ class AssetsApi { ); } - /// This endpoint requires the `asset.upload` permission. + /// Upload asset + /// + /// Uploads a new asset to the server. /// /// Parameters: /// @@ -1239,7 +1408,9 @@ class AssetsApi { return null; } - /// This endpoint requires the `asset.view` permission. + /// View asset thumbnail + /// + /// Retrieve the thumbnail image for the specified asset. /// /// Note: This method returns the HTTP [Response]. /// @@ -1288,7 +1459,9 @@ class AssetsApi { ); } - /// This endpoint requires the `asset.view` permission. + /// View asset thumbnail + /// + /// Retrieve the thumbnail image for the specified asset. /// /// Parameters: /// diff --git a/mobile/openapi/lib/api/auth_admin_api.dart b/mobile/openapi/lib/api/authentication_admin_api.dart similarity index 77% rename from mobile/openapi/lib/api/auth_admin_api.dart rename to mobile/openapi/lib/api/authentication_admin_api.dart index d22b449aab..0a4b91ebc3 100644 --- a/mobile/openapi/lib/api/auth_admin_api.dart +++ b/mobile/openapi/lib/api/authentication_admin_api.dart @@ -11,12 +11,14 @@ part of openapi.api; -class AuthAdminApi { - AuthAdminApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; +class AuthenticationAdminApi { + AuthenticationAdminApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; final ApiClient apiClient; - /// This endpoint is an admin-only route, and requires the `adminAuth.unlinkAll` permission. + /// Unlink all OAuth accounts + /// + /// Unlinks all OAuth accounts associated with user accounts in the system. /// /// Note: This method returns the HTTP [Response]. Future unlinkAllOAuthAccountsAdminWithHttpInfo() async { @@ -44,7 +46,9 @@ class AuthAdminApi { ); } - /// This endpoint is an admin-only route, and requires the `adminAuth.unlinkAll` permission. + /// Unlink all OAuth accounts + /// + /// Unlinks all OAuth accounts associated with user accounts in the system. Future unlinkAllOAuthAccountsAdmin() async { final response = await unlinkAllOAuthAccountsAdminWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { diff --git a/mobile/openapi/lib/api/authentication_api.dart b/mobile/openapi/lib/api/authentication_api.dart index a74af33a43..52d46a525b 100644 --- a/mobile/openapi/lib/api/authentication_api.dart +++ b/mobile/openapi/lib/api/authentication_api.dart @@ -16,7 +16,9 @@ class AuthenticationApi { final ApiClient apiClient; - /// This endpoint requires the `auth.changePassword` permission. + /// Change password + /// + /// Change the password of the current user. /// /// Note: This method returns the HTTP [Response]. /// @@ -48,7 +50,9 @@ class AuthenticationApi { ); } - /// This endpoint requires the `auth.changePassword` permission. + /// Change password + /// + /// Change the password of the current user. /// /// Parameters: /// @@ -68,7 +72,9 @@ class AuthenticationApi { return null; } - /// This endpoint requires the `pinCode.update` permission. + /// Change pin code + /// + /// Change the pin code for the current user. /// /// Note: This method returns the HTTP [Response]. /// @@ -100,7 +106,9 @@ class AuthenticationApi { ); } - /// This endpoint requires the `pinCode.update` permission. + /// Change pin code + /// + /// Change the pin code for the current user. /// /// Parameters: /// @@ -112,7 +120,67 @@ class AuthenticationApi { } } - /// Performs an HTTP 'GET /auth/status' operation and returns the [Response]. + /// Finish OAuth + /// + /// Complete the OAuth authorization process by exchanging the authorization code for a session token. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [OAuthCallbackDto] oAuthCallbackDto (required): + Future finishOAuthWithHttpInfo(OAuthCallbackDto oAuthCallbackDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/oauth/callback'; + + // ignore: prefer_final_locals + Object? postBody = oAuthCallbackDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Finish OAuth + /// + /// Complete the OAuth authorization process by exchanging the authorization code for a session token. + /// + /// Parameters: + /// + /// * [OAuthCallbackDto] oAuthCallbackDto (required): + Future finishOAuth(OAuthCallbackDto oAuthCallbackDto,) async { + final response = await finishOAuthWithHttpInfo(oAuthCallbackDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'LoginResponseDto',) as LoginResponseDto; + + } + return null; + } + + /// Retrieve auth status + /// + /// Get information about the current session, including whether the user has a password, and if the session can access locked assets. + /// + /// Note: This method returns the HTTP [Response]. Future getAuthStatusWithHttpInfo() async { // ignore: prefer_const_declarations final apiPath = r'/auth/status'; @@ -138,6 +206,9 @@ class AuthenticationApi { ); } + /// Retrieve auth status + /// + /// Get information about the current session, including whether the user has a password, and if the session can access locked assets. Future getAuthStatus() async { final response = await getAuthStatusWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -153,7 +224,67 @@ class AuthenticationApi { return null; } - /// Performs an HTTP 'POST /auth/session/lock' operation and returns the [Response]. + /// Link OAuth account + /// + /// Link an OAuth account to the authenticated user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [OAuthCallbackDto] oAuthCallbackDto (required): + Future linkOAuthAccountWithHttpInfo(OAuthCallbackDto oAuthCallbackDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/oauth/link'; + + // ignore: prefer_final_locals + Object? postBody = oAuthCallbackDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Link OAuth account + /// + /// Link an OAuth account to the authenticated user. + /// + /// Parameters: + /// + /// * [OAuthCallbackDto] oAuthCallbackDto (required): + Future linkOAuthAccount(OAuthCallbackDto oAuthCallbackDto,) async { + final response = await linkOAuthAccountWithHttpInfo(oAuthCallbackDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto; + + } + return null; + } + + /// Lock auth session + /// + /// Remove elevated access to locked assets from the current session. + /// + /// Note: This method returns the HTTP [Response]. Future lockAuthSessionWithHttpInfo() async { // ignore: prefer_const_declarations final apiPath = r'/auth/session/lock'; @@ -179,6 +310,9 @@ class AuthenticationApi { ); } + /// Lock auth session + /// + /// Remove elevated access to locked assets from the current session. Future lockAuthSession() async { final response = await lockAuthSessionWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -186,7 +320,12 @@ class AuthenticationApi { } } - /// Performs an HTTP 'POST /auth/login' operation and returns the [Response]. + /// Login + /// + /// Login with username and password and receive a session token. + /// + /// Note: This method returns the HTTP [Response]. + /// /// Parameters: /// /// * [LoginCredentialDto] loginCredentialDto (required): @@ -215,6 +354,10 @@ class AuthenticationApi { ); } + /// Login + /// + /// Login with username and password and receive a session token. + /// /// Parameters: /// /// * [LoginCredentialDto] loginCredentialDto (required): @@ -233,7 +376,11 @@ class AuthenticationApi { return null; } - /// Performs an HTTP 'POST /auth/logout' operation and returns the [Response]. + /// Logout + /// + /// Logout the current user and invalidate the session token. + /// + /// Note: This method returns the HTTP [Response]. Future logoutWithHttpInfo() async { // ignore: prefer_const_declarations final apiPath = r'/auth/logout'; @@ -259,6 +406,9 @@ class AuthenticationApi { ); } + /// Logout + /// + /// Logout the current user and invalidate the session token. Future logout() async { final response = await logoutWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -274,7 +424,49 @@ class AuthenticationApi { return null; } - /// This endpoint requires the `pinCode.delete` permission. + /// Redirect OAuth to mobile + /// + /// Requests to this URL are automatically forwarded to the mobile app, and is used in some cases for OAuth redirecting. + /// + /// Note: This method returns the HTTP [Response]. + Future redirectOAuthToMobileWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/oauth/mobile-redirect'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Redirect OAuth to mobile + /// + /// Requests to this URL are automatically forwarded to the mobile app, and is used in some cases for OAuth redirecting. + Future redirectOAuthToMobile() async { + final response = await redirectOAuthToMobileWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Reset pin code + /// + /// Reset the pin code for the current user by providing the account password /// /// Note: This method returns the HTTP [Response]. /// @@ -306,7 +498,9 @@ class AuthenticationApi { ); } - /// This endpoint requires the `pinCode.delete` permission. + /// Reset pin code + /// + /// Reset the pin code for the current user by providing the account password /// /// Parameters: /// @@ -318,7 +512,9 @@ class AuthenticationApi { } } - /// This endpoint requires the `pinCode.create` permission. + /// Setup pin code + /// + /// Setup a new pin code for the current user. /// /// Note: This method returns the HTTP [Response]. /// @@ -350,7 +546,9 @@ class AuthenticationApi { ); } - /// This endpoint requires the `pinCode.create` permission. + /// Setup pin code + /// + /// Setup a new pin code for the current user. /// /// Parameters: /// @@ -362,7 +560,12 @@ class AuthenticationApi { } } - /// Performs an HTTP 'POST /auth/admin-sign-up' operation and returns the [Response]. + /// Register admin + /// + /// Create the first admin user in the system. + /// + /// Note: This method returns the HTTP [Response]. + /// /// Parameters: /// /// * [SignUpDto] signUpDto (required): @@ -391,6 +594,10 @@ class AuthenticationApi { ); } + /// Register admin + /// + /// Create the first admin user in the system. + /// /// Parameters: /// /// * [SignUpDto] signUpDto (required): @@ -409,7 +616,116 @@ class AuthenticationApi { return null; } - /// Performs an HTTP 'POST /auth/session/unlock' operation and returns the [Response]. + /// Start OAuth + /// + /// Initiate the OAuth authorization process. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [OAuthConfigDto] oAuthConfigDto (required): + Future startOAuthWithHttpInfo(OAuthConfigDto oAuthConfigDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/oauth/authorize'; + + // ignore: prefer_final_locals + Object? postBody = oAuthConfigDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Start OAuth + /// + /// Initiate the OAuth authorization process. + /// + /// Parameters: + /// + /// * [OAuthConfigDto] oAuthConfigDto (required): + Future startOAuth(OAuthConfigDto oAuthConfigDto,) async { + final response = await startOAuthWithHttpInfo(oAuthConfigDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'OAuthAuthorizeResponseDto',) as OAuthAuthorizeResponseDto; + + } + return null; + } + + /// Unlink OAuth account + /// + /// Unlink the OAuth account from the authenticated user. + /// + /// Note: This method returns the HTTP [Response]. + Future unlinkOAuthAccountWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/oauth/unlink'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Unlink OAuth account + /// + /// Unlink the OAuth account from the authenticated user. + Future unlinkOAuthAccount() async { + final response = await unlinkOAuthAccountWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto; + + } + return null; + } + + /// Unlock auth session + /// + /// Temporarily grant the session elevated access to locked assets by providing the correct PIN code. + /// + /// Note: This method returns the HTTP [Response]. + /// /// Parameters: /// /// * [SessionUnlockDto] sessionUnlockDto (required): @@ -438,6 +754,10 @@ class AuthenticationApi { ); } + /// Unlock auth session + /// + /// Temporarily grant the session elevated access to locked assets by providing the correct PIN code. + /// /// Parameters: /// /// * [SessionUnlockDto] sessionUnlockDto (required): @@ -448,7 +768,11 @@ class AuthenticationApi { } } - /// Performs an HTTP 'POST /auth/validateToken' operation and returns the [Response]. + /// Validate access token + /// + /// Validate the current authorization method is still valid. + /// + /// Note: This method returns the HTTP [Response]. Future validateAccessTokenWithHttpInfo() async { // ignore: prefer_const_declarations final apiPath = r'/auth/validateToken'; @@ -474,6 +798,9 @@ class AuthenticationApi { ); } + /// Validate access token + /// + /// Validate the current authorization method is still valid. Future validateAccessToken() async { final response = await validateAccessTokenWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { diff --git a/mobile/openapi/lib/api/deprecated_api.dart b/mobile/openapi/lib/api/deprecated_api.dart index 9246998ca2..aaf7c074b9 100644 --- a/mobile/openapi/lib/api/deprecated_api.dart +++ b/mobile/openapi/lib/api/deprecated_api.dart @@ -16,7 +16,9 @@ class DeprecatedApi { final ApiClient apiClient; - /// This property was deprecated in v1.141.0. This endpoint requires the `partner.create` permission. + /// Create a partner + /// + /// Create a new partner to share assets with. /// /// Note: This method returns the HTTP [Response]. /// @@ -49,7 +51,9 @@ class DeprecatedApi { ); } - /// This property was deprecated in v1.141.0. This endpoint requires the `partner.create` permission. + /// Create a partner + /// + /// Create a new partner to share assets with. /// /// Parameters: /// @@ -69,7 +73,184 @@ class DeprecatedApi { return null; } - /// This property was deprecated in v1.116.0. This endpoint requires the `asset.read` permission. + /// Retrieve assets by device ID + /// + /// Get all asset of a device that are in the database, ID only. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] deviceId (required): + Future getAllUserAssetsByDeviceIdWithHttpInfo(String deviceId,) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/device/{deviceId}' + .replaceAll('{deviceId}', deviceId); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve assets by device ID + /// + /// Get all asset of a device that are in the database, ID only. + /// + /// Parameters: + /// + /// * [String] deviceId (required): + Future?> getAllUserAssetsByDeviceId(String deviceId,) async { + final response = await getAllUserAssetsByDeviceIdWithHttpInfo(deviceId,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Get delta sync for user + /// + /// Retrieve changed assets since the last sync for the authenticated user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [AssetDeltaSyncDto] assetDeltaSyncDto (required): + Future getDeltaSyncWithHttpInfo(AssetDeltaSyncDto assetDeltaSyncDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/sync/delta-sync'; + + // ignore: prefer_final_locals + Object? postBody = assetDeltaSyncDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get delta sync for user + /// + /// Retrieve changed assets since the last sync for the authenticated user. + /// + /// Parameters: + /// + /// * [AssetDeltaSyncDto] assetDeltaSyncDto (required): + Future getDeltaSync(AssetDeltaSyncDto assetDeltaSyncDto,) async { + final response = await getDeltaSyncWithHttpInfo(assetDeltaSyncDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetDeltaSyncResponseDto',) as AssetDeltaSyncResponseDto; + + } + return null; + } + + /// Get full sync for user + /// + /// Retrieve all assets for a full synchronization for the authenticated user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [AssetFullSyncDto] assetFullSyncDto (required): + Future getFullSyncForUserWithHttpInfo(AssetFullSyncDto assetFullSyncDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/sync/full-sync'; + + // ignore: prefer_final_locals + Object? postBody = assetFullSyncDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get full sync for user + /// + /// Retrieve all assets for a full synchronization for the authenticated user. + /// + /// Parameters: + /// + /// * [AssetFullSyncDto] assetFullSyncDto (required): + Future?> getFullSyncForUser(AssetFullSyncDto assetFullSyncDto,) async { + final response = await getFullSyncForUserWithHttpInfo(assetFullSyncDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Get random assets + /// + /// Retrieve a specified number of random assets for the authenticated user. /// /// Note: This method returns the HTTP [Response]. /// @@ -105,7 +286,9 @@ class DeprecatedApi { ); } - /// This property was deprecated in v1.116.0. This endpoint requires the `asset.read` permission. + /// Get random assets + /// + /// Retrieve a specified number of random assets for the authenticated user. /// /// Parameters: /// @@ -128,9 +311,9 @@ class DeprecatedApi { return null; } - /// Replace the asset with new file, without changing its id + /// Replace asset /// - /// This property was deprecated in v1.142.0. Replace the asset with new file, without changing its id. This endpoint requires the `asset.replace` permission. + /// Replace the asset with new file, without changing its id. /// /// Note: This method returns the HTTP [Response]. /// @@ -222,9 +405,9 @@ class DeprecatedApi { ); } - /// Replace the asset with new file, without changing its id + /// Replace asset /// - /// This property was deprecated in v1.142.0. Replace the asset with new file, without changing its id. This endpoint requires the `asset.replace` permission. + /// Replace the asset with new file, without changing its id. /// /// Parameters: /// diff --git a/mobile/openapi/lib/api/download_api.dart b/mobile/openapi/lib/api/download_api.dart index 62c97bfc9c..5245622753 100644 --- a/mobile/openapi/lib/api/download_api.dart +++ b/mobile/openapi/lib/api/download_api.dart @@ -16,7 +16,9 @@ class DownloadApi { final ApiClient apiClient; - /// This endpoint requires the `asset.download` permission. + /// Download asset archive + /// + /// Download a ZIP archive containing the specified assets. The assets must have been previously requested via the \"getDownloadInfo\" endpoint. /// /// Note: This method returns the HTTP [Response]. /// @@ -59,7 +61,9 @@ class DownloadApi { ); } - /// This endpoint requires the `asset.download` permission. + /// Download asset archive + /// + /// Download a ZIP archive containing the specified assets. The assets must have been previously requested via the \"getDownloadInfo\" endpoint. /// /// Parameters: /// @@ -83,7 +87,9 @@ class DownloadApi { return null; } - /// This endpoint requires the `asset.download` permission. + /// Retrieve download information + /// + /// Retrieve information about how to request a download for the specified assets or album. The response includes groups of assets that can be downloaded together. /// /// Note: This method returns the HTTP [Response]. /// @@ -126,7 +132,9 @@ class DownloadApi { ); } - /// This endpoint requires the `asset.download` permission. + /// Retrieve download information + /// + /// Retrieve information about how to request a download for the specified assets or album. The response includes groups of assets that can be downloaded together. /// /// Parameters: /// diff --git a/mobile/openapi/lib/api/duplicates_api.dart b/mobile/openapi/lib/api/duplicates_api.dart index 9df6e46586..7fa7b368b5 100644 --- a/mobile/openapi/lib/api/duplicates_api.dart +++ b/mobile/openapi/lib/api/duplicates_api.dart @@ -16,7 +16,9 @@ class DuplicatesApi { final ApiClient apiClient; - /// This endpoint requires the `duplicate.delete` permission. + /// Delete a duplicate + /// + /// Delete a single duplicate asset specified by its ID. /// /// Note: This method returns the HTTP [Response]. /// @@ -49,7 +51,9 @@ class DuplicatesApi { ); } - /// This endpoint requires the `duplicate.delete` permission. + /// Delete a duplicate + /// + /// Delete a single duplicate asset specified by its ID. /// /// Parameters: /// @@ -61,7 +65,9 @@ class DuplicatesApi { } } - /// This endpoint requires the `duplicate.delete` permission. + /// Delete duplicates + /// + /// Delete multiple duplicate assets specified by their IDs. /// /// Note: This method returns the HTTP [Response]. /// @@ -93,7 +99,9 @@ class DuplicatesApi { ); } - /// This endpoint requires the `duplicate.delete` permission. + /// Delete duplicates + /// + /// Delete multiple duplicate assets specified by their IDs. /// /// Parameters: /// @@ -105,7 +113,9 @@ class DuplicatesApi { } } - /// This endpoint requires the `duplicate.read` permission. + /// Retrieve duplicates + /// + /// Retrieve a list of duplicate assets available to the authenticated user. /// /// Note: This method returns the HTTP [Response]. Future getAssetDuplicatesWithHttpInfo() async { @@ -133,7 +143,9 @@ class DuplicatesApi { ); } - /// This endpoint requires the `duplicate.read` permission. + /// Retrieve duplicates + /// + /// Retrieve a list of duplicate assets available to the authenticated user. Future?> getAssetDuplicates() async { final response = await getAssetDuplicatesWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { diff --git a/mobile/openapi/lib/api/faces_api.dart b/mobile/openapi/lib/api/faces_api.dart index 2f8e6be60d..1d2e7401e8 100644 --- a/mobile/openapi/lib/api/faces_api.dart +++ b/mobile/openapi/lib/api/faces_api.dart @@ -16,7 +16,9 @@ class FacesApi { final ApiClient apiClient; - /// This endpoint requires the `face.create` permission. + /// Create a face + /// + /// Create a new face that has not been discovered by facial recognition. The content of the bounding box is considered a face. /// /// Note: This method returns the HTTP [Response]. /// @@ -48,7 +50,9 @@ class FacesApi { ); } - /// This endpoint requires the `face.create` permission. + /// Create a face + /// + /// Create a new face that has not been discovered by facial recognition. The content of the bounding box is considered a face. /// /// Parameters: /// @@ -60,7 +64,9 @@ class FacesApi { } } - /// This endpoint requires the `face.delete` permission. + /// Delete a face + /// + /// Delete a face identified by the id. Optionally can be force deleted. /// /// Note: This method returns the HTTP [Response]. /// @@ -95,7 +101,9 @@ class FacesApi { ); } - /// This endpoint requires the `face.delete` permission. + /// Delete a face + /// + /// Delete a face identified by the id. Optionally can be force deleted. /// /// Parameters: /// @@ -109,7 +117,9 @@ class FacesApi { } } - /// This endpoint requires the `face.read` permission. + /// Retrieve faces for asset + /// + /// Retrieve all faces belonging to an asset. /// /// Note: This method returns the HTTP [Response]. /// @@ -143,7 +153,9 @@ class FacesApi { ); } - /// This endpoint requires the `face.read` permission. + /// Retrieve faces for asset + /// + /// Retrieve all faces belonging to an asset. /// /// Parameters: /// @@ -166,7 +178,9 @@ class FacesApi { return null; } - /// This endpoint requires the `face.update` permission. + /// Re-assign a face to another person + /// + /// Re-assign the face provided in the body to the person identified by the id in the path parameter. /// /// Note: This method returns the HTTP [Response]. /// @@ -201,7 +215,9 @@ class FacesApi { ); } - /// This endpoint requires the `face.update` permission. + /// Re-assign a face to another person + /// + /// Re-assign the face provided in the body to the person identified by the id in the path parameter. /// /// Parameters: /// diff --git a/mobile/openapi/lib/api/jobs_api.dart b/mobile/openapi/lib/api/jobs_api.dart index 4c935828a0..906dce6d37 100644 --- a/mobile/openapi/lib/api/jobs_api.dart +++ b/mobile/openapi/lib/api/jobs_api.dart @@ -16,7 +16,9 @@ class JobsApi { final ApiClient apiClient; - /// This endpoint is an admin-only route, and requires the `job.create` permission. + /// Create a manual job + /// + /// Run a specific job. Most jobs are queued automatically, but this endpoint allows for manual creation of a handful of jobs, including various cleanup tasks, as well as creating a new database backup. /// /// Note: This method returns the HTTP [Response]. /// @@ -48,7 +50,9 @@ class JobsApi { ); } - /// This endpoint is an admin-only route, and requires the `job.create` permission. + /// Create a manual job + /// + /// Run a specific job. Most jobs are queued automatically, but this endpoint allows for manual creation of a handful of jobs, including various cleanup tasks, as well as creating a new database backup. /// /// Parameters: /// @@ -60,10 +64,12 @@ class JobsApi { } } - /// This endpoint is an admin-only route, and requires the `job.read` permission. + /// Retrieve queue counts and status + /// + /// Retrieve the counts of the current queue, as well as the current status. /// /// Note: This method returns the HTTP [Response]. - Future getAllJobsStatusWithHttpInfo() async { + Future getQueuesLegacyWithHttpInfo() async { // ignore: prefer_const_declarations final apiPath = r'/jobs'; @@ -88,9 +94,11 @@ class JobsApi { ); } - /// This endpoint is an admin-only route, and requires the `job.read` permission. - Future getAllJobsStatus() async { - final response = await getAllJobsStatusWithHttpInfo(); + /// Retrieve queue counts and status + /// + /// Retrieve the counts of the current queue, as well as the current status. + Future getQueuesLegacy() async { + final response = await getQueuesLegacyWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -98,28 +106,30 @@ class JobsApi { // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" // FormatException when trying to decode an empty string. if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { - return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AllJobStatusResponseDto',) as AllJobStatusResponseDto; + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'QueuesResponseDto',) as QueuesResponseDto; } return null; } - /// This endpoint is an admin-only route, and requires the `job.create` permission. + /// Run jobs + /// + /// Queue all assets for a specific job type. Defaults to only queueing assets that have not yet been processed, but the force command can be used to re-process all assets. /// /// Note: This method returns the HTTP [Response]. /// /// Parameters: /// - /// * [JobName] id (required): + /// * [QueueName] name (required): /// - /// * [JobCommandDto] jobCommandDto (required): - Future sendJobCommandWithHttpInfo(JobName id, JobCommandDto jobCommandDto,) async { + /// * [QueueCommandDto] queueCommandDto (required): + Future runQueueCommandLegacyWithHttpInfo(QueueName name, QueueCommandDto queueCommandDto,) async { // ignore: prefer_const_declarations - final apiPath = r'/jobs/{id}' - .replaceAll('{id}', id.toString()); + final apiPath = r'/jobs/{name}' + .replaceAll('{name}', name.toString()); // ignore: prefer_final_locals - Object? postBody = jobCommandDto; + Object? postBody = queueCommandDto; final queryParams = []; final headerParams = {}; @@ -139,15 +149,17 @@ class JobsApi { ); } - /// This endpoint is an admin-only route, and requires the `job.create` permission. + /// Run jobs + /// + /// Queue all assets for a specific job type. Defaults to only queueing assets that have not yet been processed, but the force command can be used to re-process all assets. /// /// Parameters: /// - /// * [JobName] id (required): + /// * [QueueName] name (required): /// - /// * [JobCommandDto] jobCommandDto (required): - Future sendJobCommand(JobName id, JobCommandDto jobCommandDto,) async { - final response = await sendJobCommandWithHttpInfo(id, jobCommandDto,); + /// * [QueueCommandDto] queueCommandDto (required): + Future runQueueCommandLegacy(QueueName name, QueueCommandDto queueCommandDto,) async { + final response = await runQueueCommandLegacyWithHttpInfo(name, queueCommandDto,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -155,7 +167,7 @@ class JobsApi { // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" // FormatException when trying to decode an empty string. if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { - return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'JobStatusDto',) as JobStatusDto; + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'QueueResponseDto',) as QueueResponseDto; } return null; diff --git a/mobile/openapi/lib/api/libraries_api.dart b/mobile/openapi/lib/api/libraries_api.dart index 9258f8e3eb..ca59f823fe 100644 --- a/mobile/openapi/lib/api/libraries_api.dart +++ b/mobile/openapi/lib/api/libraries_api.dart @@ -16,7 +16,9 @@ class LibrariesApi { final ApiClient apiClient; - /// This endpoint is an admin-only route, and requires the `library.create` permission. + /// Create a library + /// + /// Create a new external library. /// /// Note: This method returns the HTTP [Response]. /// @@ -48,7 +50,9 @@ class LibrariesApi { ); } - /// This endpoint is an admin-only route, and requires the `library.create` permission. + /// Create a library + /// + /// Create a new external library. /// /// Parameters: /// @@ -68,7 +72,9 @@ class LibrariesApi { return null; } - /// This endpoint is an admin-only route, and requires the `library.delete` permission. + /// Delete a library + /// + /// Delete an external library by its ID. /// /// Note: This method returns the HTTP [Response]. /// @@ -101,7 +107,9 @@ class LibrariesApi { ); } - /// This endpoint is an admin-only route, and requires the `library.delete` permission. + /// Delete a library + /// + /// Delete an external library by its ID. /// /// Parameters: /// @@ -113,7 +121,9 @@ class LibrariesApi { } } - /// This endpoint is an admin-only route, and requires the `library.read` permission. + /// Retrieve libraries + /// + /// Retrieve a list of external libraries. /// /// Note: This method returns the HTTP [Response]. Future getAllLibrariesWithHttpInfo() async { @@ -141,7 +151,9 @@ class LibrariesApi { ); } - /// This endpoint is an admin-only route, and requires the `library.read` permission. + /// Retrieve libraries + /// + /// Retrieve a list of external libraries. Future?> getAllLibraries() async { final response = await getAllLibrariesWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -160,7 +172,9 @@ class LibrariesApi { return null; } - /// This endpoint is an admin-only route, and requires the `library.read` permission. + /// Retrieve a library + /// + /// Retrieve an external library by its ID. /// /// Note: This method returns the HTTP [Response]. /// @@ -193,7 +207,9 @@ class LibrariesApi { ); } - /// This endpoint is an admin-only route, and requires the `library.read` permission. + /// Retrieve a library + /// + /// Retrieve an external library by its ID. /// /// Parameters: /// @@ -213,7 +229,9 @@ class LibrariesApi { return null; } - /// This endpoint is an admin-only route, and requires the `library.statistics` permission. + /// Retrieve library statistics + /// + /// Retrieve statistics for a specific external library, including number of videos, images, and storage usage. /// /// Note: This method returns the HTTP [Response]. /// @@ -246,7 +264,9 @@ class LibrariesApi { ); } - /// This endpoint is an admin-only route, and requires the `library.statistics` permission. + /// Retrieve library statistics + /// + /// Retrieve statistics for a specific external library, including number of videos, images, and storage usage. /// /// Parameters: /// @@ -266,7 +286,9 @@ class LibrariesApi { return null; } - /// This endpoint is an admin-only route, and requires the `library.update` permission. + /// Scan a library + /// + /// Queue a scan for the external library to find and import new assets. /// /// Note: This method returns the HTTP [Response]. /// @@ -299,7 +321,9 @@ class LibrariesApi { ); } - /// This endpoint is an admin-only route, and requires the `library.update` permission. + /// Scan a library + /// + /// Queue a scan for the external library to find and import new assets. /// /// Parameters: /// @@ -311,7 +335,9 @@ class LibrariesApi { } } - /// This endpoint is an admin-only route, and requires the `library.update` permission. + /// Update a library + /// + /// Update an existing external library. /// /// Note: This method returns the HTTP [Response]. /// @@ -346,7 +372,9 @@ class LibrariesApi { ); } - /// This endpoint is an admin-only route, and requires the `library.update` permission. + /// Update a library + /// + /// Update an existing external library. /// /// Parameters: /// @@ -368,7 +396,12 @@ class LibrariesApi { return null; } - /// Performs an HTTP 'POST /libraries/{id}/validate' operation and returns the [Response]. + /// Validate library settings + /// + /// Validate the settings of an external library. + /// + /// Note: This method returns the HTTP [Response]. + /// /// Parameters: /// /// * [String] id (required): @@ -400,6 +433,10 @@ class LibrariesApi { ); } + /// Validate library settings + /// + /// Validate the settings of an external library. + /// /// Parameters: /// /// * [String] id (required): diff --git a/mobile/openapi/lib/api/map_api.dart b/mobile/openapi/lib/api/map_api.dart index da4f3dffcc..6302ac304e 100644 --- a/mobile/openapi/lib/api/map_api.dart +++ b/mobile/openapi/lib/api/map_api.dart @@ -16,21 +16,26 @@ class MapApi { final ApiClient apiClient; - /// Performs an HTTP 'GET /map/markers' operation and returns the [Response]. + /// Retrieve map markers + /// + /// Retrieve a list of latitude and longitude coordinates for every asset with location data. + /// + /// Note: This method returns the HTTP [Response]. + /// /// Parameters: /// - /// * [bool] isArchived: - /// - /// * [bool] isFavorite: - /// /// * [DateTime] fileCreatedAfter: /// /// * [DateTime] fileCreatedBefore: /// + /// * [bool] isArchived: + /// + /// * [bool] isFavorite: + /// /// * [bool] withPartners: /// /// * [bool] withSharedAlbums: - Future getMapMarkersWithHttpInfo({ bool? isArchived, bool? isFavorite, DateTime? fileCreatedAfter, DateTime? fileCreatedBefore, bool? withPartners, bool? withSharedAlbums, }) async { + Future getMapMarkersWithHttpInfo({ DateTime? fileCreatedAfter, DateTime? fileCreatedBefore, bool? isArchived, bool? isFavorite, bool? withPartners, bool? withSharedAlbums, }) async { // ignore: prefer_const_declarations final apiPath = r'/map/markers'; @@ -41,18 +46,18 @@ class MapApi { final headerParams = {}; final formParams = {}; - if (isArchived != null) { - queryParams.addAll(_queryParams('', 'isArchived', isArchived)); - } - if (isFavorite != null) { - queryParams.addAll(_queryParams('', 'isFavorite', isFavorite)); - } if (fileCreatedAfter != null) { queryParams.addAll(_queryParams('', 'fileCreatedAfter', fileCreatedAfter)); } if (fileCreatedBefore != null) { queryParams.addAll(_queryParams('', 'fileCreatedBefore', fileCreatedBefore)); } + if (isArchived != null) { + queryParams.addAll(_queryParams('', 'isArchived', isArchived)); + } + if (isFavorite != null) { + queryParams.addAll(_queryParams('', 'isFavorite', isFavorite)); + } if (withPartners != null) { queryParams.addAll(_queryParams('', 'withPartners', withPartners)); } @@ -74,21 +79,25 @@ class MapApi { ); } + /// Retrieve map markers + /// + /// Retrieve a list of latitude and longitude coordinates for every asset with location data. + /// /// Parameters: /// - /// * [bool] isArchived: - /// - /// * [bool] isFavorite: - /// /// * [DateTime] fileCreatedAfter: /// /// * [DateTime] fileCreatedBefore: /// + /// * [bool] isArchived: + /// + /// * [bool] isFavorite: + /// /// * [bool] withPartners: /// /// * [bool] withSharedAlbums: - Future?> getMapMarkers({ bool? isArchived, bool? isFavorite, DateTime? fileCreatedAfter, DateTime? fileCreatedBefore, bool? withPartners, bool? withSharedAlbums, }) async { - final response = await getMapMarkersWithHttpInfo( isArchived: isArchived, isFavorite: isFavorite, fileCreatedAfter: fileCreatedAfter, fileCreatedBefore: fileCreatedBefore, withPartners: withPartners, withSharedAlbums: withSharedAlbums, ); + Future?> getMapMarkers({ DateTime? fileCreatedAfter, DateTime? fileCreatedBefore, bool? isArchived, bool? isFavorite, bool? withPartners, bool? withSharedAlbums, }) async { + final response = await getMapMarkersWithHttpInfo( fileCreatedAfter: fileCreatedAfter, fileCreatedBefore: fileCreatedBefore, isArchived: isArchived, isFavorite: isFavorite, withPartners: withPartners, withSharedAlbums: withSharedAlbums, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -105,7 +114,12 @@ class MapApi { return null; } - /// Performs an HTTP 'GET /map/reverse-geocode' operation and returns the [Response]. + /// Reverse geocode coordinates + /// + /// Retrieve location information (e.g., city, country) for given latitude and longitude coordinates. + /// + /// Note: This method returns the HTTP [Response]. + /// /// Parameters: /// /// * [double] lat (required): @@ -139,6 +153,10 @@ class MapApi { ); } + /// Reverse geocode coordinates + /// + /// Retrieve location information (e.g., city, country) for given latitude and longitude coordinates. + /// /// Parameters: /// /// * [double] lat (required): diff --git a/mobile/openapi/lib/api/memories_api.dart b/mobile/openapi/lib/api/memories_api.dart index f9280101e6..314595e84e 100644 --- a/mobile/openapi/lib/api/memories_api.dart +++ b/mobile/openapi/lib/api/memories_api.dart @@ -16,7 +16,9 @@ class MemoriesApi { final ApiClient apiClient; - /// This endpoint requires the `memoryAsset.create` permission. + /// Add assets to a memory + /// + /// Add a list of asset IDs to a specific memory. /// /// Note: This method returns the HTTP [Response]. /// @@ -51,7 +53,9 @@ class MemoriesApi { ); } - /// This endpoint requires the `memoryAsset.create` permission. + /// Add assets to a memory + /// + /// Add a list of asset IDs to a specific memory. /// /// Parameters: /// @@ -76,7 +80,9 @@ class MemoriesApi { return null; } - /// This endpoint requires the `memory.create` permission. + /// Create a memory + /// + /// Create a new memory by providing a name, description, and a list of asset IDs to include in the memory. /// /// Note: This method returns the HTTP [Response]. /// @@ -108,7 +114,9 @@ class MemoriesApi { ); } - /// This endpoint requires the `memory.create` permission. + /// Create a memory + /// + /// Create a new memory by providing a name, description, and a list of asset IDs to include in the memory. /// /// Parameters: /// @@ -128,7 +136,9 @@ class MemoriesApi { return null; } - /// This endpoint requires the `memory.delete` permission. + /// Delete a memory + /// + /// Delete a specific memory by its ID. /// /// Note: This method returns the HTTP [Response]. /// @@ -161,7 +171,9 @@ class MemoriesApi { ); } - /// This endpoint requires the `memory.delete` permission. + /// Delete a memory + /// + /// Delete a specific memory by its ID. /// /// Parameters: /// @@ -173,7 +185,9 @@ class MemoriesApi { } } - /// This endpoint requires the `memory.read` permission. + /// Retrieve a memory + /// + /// Retrieve a specific memory by its ID. /// /// Note: This method returns the HTTP [Response]. /// @@ -206,7 +220,9 @@ class MemoriesApi { ); } - /// This endpoint requires the `memory.read` permission. + /// Retrieve a memory + /// + /// Retrieve a specific memory by its ID. /// /// Parameters: /// @@ -226,7 +242,9 @@ class MemoriesApi { return null; } - /// This endpoint requires the `memory.statistics` permission. + /// Retrieve memories statistics + /// + /// Retrieve statistics about memories, such as total count and other relevant metrics. /// /// Note: This method returns the HTTP [Response]. /// @@ -238,8 +256,13 @@ class MemoriesApi { /// /// * [bool] isTrashed: /// + /// * [MemorySearchOrder] order: + /// + /// * [int] size: + /// Number of memories to return + /// /// * [MemoryType] type: - Future memoriesStatisticsWithHttpInfo({ DateTime? for_, bool? isSaved, bool? isTrashed, MemoryType? type, }) async { + Future memoriesStatisticsWithHttpInfo({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, }) async { // ignore: prefer_const_declarations final apiPath = r'/memories/statistics'; @@ -259,6 +282,12 @@ class MemoriesApi { if (isTrashed != null) { queryParams.addAll(_queryParams('', 'isTrashed', isTrashed)); } + if (order != null) { + queryParams.addAll(_queryParams('', 'order', order)); + } + if (size != null) { + queryParams.addAll(_queryParams('', 'size', size)); + } if (type != null) { queryParams.addAll(_queryParams('', 'type', type)); } @@ -277,7 +306,9 @@ class MemoriesApi { ); } - /// This endpoint requires the `memory.statistics` permission. + /// Retrieve memories statistics + /// + /// Retrieve statistics about memories, such as total count and other relevant metrics. /// /// Parameters: /// @@ -287,9 +318,14 @@ class MemoriesApi { /// /// * [bool] isTrashed: /// + /// * [MemorySearchOrder] order: + /// + /// * [int] size: + /// Number of memories to return + /// /// * [MemoryType] type: - Future memoriesStatistics({ DateTime? for_, bool? isSaved, bool? isTrashed, MemoryType? type, }) async { - final response = await memoriesStatisticsWithHttpInfo( for_: for_, isSaved: isSaved, isTrashed: isTrashed, type: type, ); + Future memoriesStatistics({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, }) async { + final response = await memoriesStatisticsWithHttpInfo( for_: for_, isSaved: isSaved, isTrashed: isTrashed, order: order, size: size, type: type, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -303,7 +339,9 @@ class MemoriesApi { return null; } - /// This endpoint requires the `memoryAsset.delete` permission. + /// Remove assets from a memory + /// + /// Remove a list of asset IDs from a specific memory. /// /// Note: This method returns the HTTP [Response]. /// @@ -338,7 +376,9 @@ class MemoriesApi { ); } - /// This endpoint requires the `memoryAsset.delete` permission. + /// Remove assets from a memory + /// + /// Remove a list of asset IDs from a specific memory. /// /// Parameters: /// @@ -363,7 +403,9 @@ class MemoriesApi { return null; } - /// This endpoint requires the `memory.read` permission. + /// Retrieve memories + /// + /// Retrieve a list of memories. Memories are sorted descending by creation date by default, although they can also be sorted in ascending order, or randomly. /// /// Note: This method returns the HTTP [Response]. /// @@ -375,8 +417,13 @@ class MemoriesApi { /// /// * [bool] isTrashed: /// + /// * [MemorySearchOrder] order: + /// + /// * [int] size: + /// Number of memories to return + /// /// * [MemoryType] type: - Future searchMemoriesWithHttpInfo({ DateTime? for_, bool? isSaved, bool? isTrashed, MemoryType? type, }) async { + Future searchMemoriesWithHttpInfo({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, }) async { // ignore: prefer_const_declarations final apiPath = r'/memories'; @@ -396,6 +443,12 @@ class MemoriesApi { if (isTrashed != null) { queryParams.addAll(_queryParams('', 'isTrashed', isTrashed)); } + if (order != null) { + queryParams.addAll(_queryParams('', 'order', order)); + } + if (size != null) { + queryParams.addAll(_queryParams('', 'size', size)); + } if (type != null) { queryParams.addAll(_queryParams('', 'type', type)); } @@ -414,7 +467,9 @@ class MemoriesApi { ); } - /// This endpoint requires the `memory.read` permission. + /// Retrieve memories + /// + /// Retrieve a list of memories. Memories are sorted descending by creation date by default, although they can also be sorted in ascending order, or randomly. /// /// Parameters: /// @@ -424,9 +479,14 @@ class MemoriesApi { /// /// * [bool] isTrashed: /// + /// * [MemorySearchOrder] order: + /// + /// * [int] size: + /// Number of memories to return + /// /// * [MemoryType] type: - Future?> searchMemories({ DateTime? for_, bool? isSaved, bool? isTrashed, MemoryType? type, }) async { - final response = await searchMemoriesWithHttpInfo( for_: for_, isSaved: isSaved, isTrashed: isTrashed, type: type, ); + Future?> searchMemories({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, }) async { + final response = await searchMemoriesWithHttpInfo( for_: for_, isSaved: isSaved, isTrashed: isTrashed, order: order, size: size, type: type, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -443,7 +503,9 @@ class MemoriesApi { return null; } - /// This endpoint requires the `memory.update` permission. + /// Update a memory + /// + /// Update an existing memory by its ID. /// /// Note: This method returns the HTTP [Response]. /// @@ -478,7 +540,9 @@ class MemoriesApi { ); } - /// This endpoint requires the `memory.update` permission. + /// Update a memory + /// + /// Update an existing memory by its ID. /// /// Parameters: /// diff --git a/mobile/openapi/lib/api/notifications_admin_api.dart b/mobile/openapi/lib/api/notifications_admin_api.dart index 409683a950..7821553d30 100644 --- a/mobile/openapi/lib/api/notifications_admin_api.dart +++ b/mobile/openapi/lib/api/notifications_admin_api.dart @@ -16,7 +16,12 @@ class NotificationsAdminApi { final ApiClient apiClient; - /// Performs an HTTP 'POST /admin/notifications' operation and returns the [Response]. + /// Create a notification + /// + /// Create a new notification for a specific user. + /// + /// Note: This method returns the HTTP [Response]. + /// /// Parameters: /// /// * [NotificationCreateDto] notificationCreateDto (required): @@ -45,6 +50,10 @@ class NotificationsAdminApi { ); } + /// Create a notification + /// + /// Create a new notification for a specific user. + /// /// Parameters: /// /// * [NotificationCreateDto] notificationCreateDto (required): @@ -63,7 +72,12 @@ class NotificationsAdminApi { return null; } - /// Performs an HTTP 'POST /admin/notifications/templates/{name}' operation and returns the [Response]. + /// Render email template + /// + /// Retrieve a preview of the provided email template. + /// + /// Note: This method returns the HTTP [Response]. + /// /// Parameters: /// /// * [String] name (required): @@ -95,6 +109,10 @@ class NotificationsAdminApi { ); } + /// Render email template + /// + /// Retrieve a preview of the provided email template. + /// /// Parameters: /// /// * [String] name (required): @@ -115,7 +133,12 @@ class NotificationsAdminApi { return null; } - /// Performs an HTTP 'POST /admin/notifications/test-email' operation and returns the [Response]. + /// Send test email + /// + /// Send a test email using the provided SMTP configuration. + /// + /// Note: This method returns the HTTP [Response]. + /// /// Parameters: /// /// * [SystemConfigSmtpDto] systemConfigSmtpDto (required): @@ -144,6 +167,10 @@ class NotificationsAdminApi { ); } + /// Send test email + /// + /// Send a test email using the provided SMTP configuration. + /// /// Parameters: /// /// * [SystemConfigSmtpDto] systemConfigSmtpDto (required): diff --git a/mobile/openapi/lib/api/notifications_api.dart b/mobile/openapi/lib/api/notifications_api.dart index 1d276efaaf..2de59a0a76 100644 --- a/mobile/openapi/lib/api/notifications_api.dart +++ b/mobile/openapi/lib/api/notifications_api.dart @@ -16,7 +16,9 @@ class NotificationsApi { final ApiClient apiClient; - /// This endpoint requires the `notification.delete` permission. + /// Delete a notification + /// + /// Delete a specific notification. /// /// Note: This method returns the HTTP [Response]. /// @@ -49,7 +51,9 @@ class NotificationsApi { ); } - /// This endpoint requires the `notification.delete` permission. + /// Delete a notification + /// + /// Delete a specific notification. /// /// Parameters: /// @@ -61,7 +65,9 @@ class NotificationsApi { } } - /// This endpoint requires the `notification.delete` permission. + /// Delete notifications + /// + /// Delete a list of notifications at once. /// /// Note: This method returns the HTTP [Response]. /// @@ -93,7 +99,9 @@ class NotificationsApi { ); } - /// This endpoint requires the `notification.delete` permission. + /// Delete notifications + /// + /// Delete a list of notifications at once. /// /// Parameters: /// @@ -105,7 +113,9 @@ class NotificationsApi { } } - /// This endpoint requires the `notification.read` permission. + /// Get a notification + /// + /// Retrieve a specific notification identified by id. /// /// Note: This method returns the HTTP [Response]. /// @@ -138,7 +148,9 @@ class NotificationsApi { ); } - /// This endpoint requires the `notification.read` permission. + /// Get a notification + /// + /// Retrieve a specific notification identified by id. /// /// Parameters: /// @@ -158,7 +170,9 @@ class NotificationsApi { return null; } - /// This endpoint requires the `notification.read` permission. + /// Retrieve notifications + /// + /// Retrieve a list of notifications. /// /// Note: This method returns the HTTP [Response]. /// @@ -209,7 +223,9 @@ class NotificationsApi { ); } - /// This endpoint requires the `notification.read` permission. + /// Retrieve notifications + /// + /// Retrieve a list of notifications. /// /// Parameters: /// @@ -238,7 +254,9 @@ class NotificationsApi { return null; } - /// This endpoint requires the `notification.update` permission. + /// Update a notification + /// + /// Update a specific notification to set its read status. /// /// Note: This method returns the HTTP [Response]. /// @@ -273,7 +291,9 @@ class NotificationsApi { ); } - /// This endpoint requires the `notification.update` permission. + /// Update a notification + /// + /// Update a specific notification to set its read status. /// /// Parameters: /// @@ -295,7 +315,9 @@ class NotificationsApi { return null; } - /// This endpoint requires the `notification.update` permission. + /// Update notifications + /// + /// Update a list of notifications. Allows to bulk-set the read status of notifications. /// /// Note: This method returns the HTTP [Response]. /// @@ -327,7 +349,9 @@ class NotificationsApi { ); } - /// This endpoint requires the `notification.update` permission. + /// Update notifications + /// + /// Update a list of notifications. Allows to bulk-set the read status of notifications. /// /// Parameters: /// diff --git a/mobile/openapi/lib/api/partners_api.dart b/mobile/openapi/lib/api/partners_api.dart index a5fdf53ab5..7d18f6d867 100644 --- a/mobile/openapi/lib/api/partners_api.dart +++ b/mobile/openapi/lib/api/partners_api.dart @@ -16,7 +16,9 @@ class PartnersApi { final ApiClient apiClient; - /// This endpoint requires the `partner.create` permission. + /// Create a partner + /// + /// Create a new partner to share assets with. /// /// Note: This method returns the HTTP [Response]. /// @@ -48,7 +50,9 @@ class PartnersApi { ); } - /// This endpoint requires the `partner.create` permission. + /// Create a partner + /// + /// Create a new partner to share assets with. /// /// Parameters: /// @@ -68,7 +72,9 @@ class PartnersApi { return null; } - /// This property was deprecated in v1.141.0. This endpoint requires the `partner.create` permission. + /// Create a partner + /// + /// Create a new partner to share assets with. /// /// Note: This method returns the HTTP [Response]. /// @@ -101,7 +107,9 @@ class PartnersApi { ); } - /// This property was deprecated in v1.141.0. This endpoint requires the `partner.create` permission. + /// Create a partner + /// + /// Create a new partner to share assets with. /// /// Parameters: /// @@ -121,7 +129,9 @@ class PartnersApi { return null; } - /// This endpoint requires the `partner.read` permission. + /// Retrieve partners + /// + /// Retrieve a list of partners with whom assets are shared. /// /// Note: This method returns the HTTP [Response]. /// @@ -155,7 +165,9 @@ class PartnersApi { ); } - /// This endpoint requires the `partner.read` permission. + /// Retrieve partners + /// + /// Retrieve a list of partners with whom assets are shared. /// /// Parameters: /// @@ -178,7 +190,9 @@ class PartnersApi { return null; } - /// This endpoint requires the `partner.delete` permission. + /// Remove a partner + /// + /// Stop sharing assets with a partner. /// /// Note: This method returns the HTTP [Response]. /// @@ -211,7 +225,9 @@ class PartnersApi { ); } - /// This endpoint requires the `partner.delete` permission. + /// Remove a partner + /// + /// Stop sharing assets with a partner. /// /// Parameters: /// @@ -223,7 +239,9 @@ class PartnersApi { } } - /// This endpoint requires the `partner.update` permission. + /// Update a partner + /// + /// Specify whether a partner's assets should appear in the user's timeline. /// /// Note: This method returns the HTTP [Response]. /// @@ -258,7 +276,9 @@ class PartnersApi { ); } - /// This endpoint requires the `partner.update` permission. + /// Update a partner + /// + /// Specify whether a partner's assets should appear in the user's timeline. /// /// Parameters: /// diff --git a/mobile/openapi/lib/api/people_api.dart b/mobile/openapi/lib/api/people_api.dart index 68c16785cc..c38e61584e 100644 --- a/mobile/openapi/lib/api/people_api.dart +++ b/mobile/openapi/lib/api/people_api.dart @@ -16,7 +16,9 @@ class PeopleApi { final ApiClient apiClient; - /// This endpoint requires the `person.create` permission. + /// Create a person + /// + /// Create a new person that can have multiple faces assigned to them. /// /// Note: This method returns the HTTP [Response]. /// @@ -48,7 +50,9 @@ class PeopleApi { ); } - /// This endpoint requires the `person.create` permission. + /// Create a person + /// + /// Create a new person that can have multiple faces assigned to them. /// /// Parameters: /// @@ -68,7 +72,9 @@ class PeopleApi { return null; } - /// This endpoint requires the `person.delete` permission. + /// Delete people + /// + /// Bulk delete a list of people at once. /// /// Note: This method returns the HTTP [Response]. /// @@ -100,7 +106,9 @@ class PeopleApi { ); } - /// This endpoint requires the `person.delete` permission. + /// Delete people + /// + /// Bulk delete a list of people at once. /// /// Parameters: /// @@ -112,7 +120,9 @@ class PeopleApi { } } - /// This endpoint requires the `person.delete` permission. + /// Delete person + /// + /// Delete an individual person. /// /// Note: This method returns the HTTP [Response]. /// @@ -145,7 +155,9 @@ class PeopleApi { ); } - /// This endpoint requires the `person.delete` permission. + /// Delete person + /// + /// Delete an individual person. /// /// Parameters: /// @@ -157,7 +169,9 @@ class PeopleApi { } } - /// This endpoint requires the `person.read` permission. + /// Get all people + /// + /// Retrieve a list of all people. /// /// Note: This method returns the HTTP [Response]. /// @@ -215,7 +229,9 @@ class PeopleApi { ); } - /// This endpoint requires the `person.read` permission. + /// Get all people + /// + /// Retrieve a list of all people. /// /// Parameters: /// @@ -245,7 +261,9 @@ class PeopleApi { return null; } - /// This endpoint requires the `person.read` permission. + /// Get a person + /// + /// Retrieve a person by id. /// /// Note: This method returns the HTTP [Response]. /// @@ -278,7 +296,9 @@ class PeopleApi { ); } - /// This endpoint requires the `person.read` permission. + /// Get a person + /// + /// Retrieve a person by id. /// /// Parameters: /// @@ -298,7 +318,9 @@ class PeopleApi { return null; } - /// This endpoint requires the `person.statistics` permission. + /// Get person statistics + /// + /// Retrieve statistics about a specific person. /// /// Note: This method returns the HTTP [Response]. /// @@ -331,7 +353,9 @@ class PeopleApi { ); } - /// This endpoint requires the `person.statistics` permission. + /// Get person statistics + /// + /// Retrieve statistics about a specific person. /// /// Parameters: /// @@ -351,7 +375,9 @@ class PeopleApi { return null; } - /// This endpoint requires the `person.read` permission. + /// Get person thumbnail + /// + /// Retrieve the thumbnail file for a person. /// /// Note: This method returns the HTTP [Response]. /// @@ -384,7 +410,9 @@ class PeopleApi { ); } - /// This endpoint requires the `person.read` permission. + /// Get person thumbnail + /// + /// Retrieve the thumbnail file for a person. /// /// Parameters: /// @@ -404,7 +432,9 @@ class PeopleApi { return null; } - /// This endpoint requires the `person.merge` permission. + /// Merge people + /// + /// Merge a list of people into the person specified in the path parameter. /// /// Note: This method returns the HTTP [Response]. /// @@ -439,7 +469,9 @@ class PeopleApi { ); } - /// This endpoint requires the `person.merge` permission. + /// Merge people + /// + /// Merge a list of people into the person specified in the path parameter. /// /// Parameters: /// @@ -464,7 +496,9 @@ class PeopleApi { return null; } - /// This endpoint requires the `person.reassign` permission. + /// Reassign faces + /// + /// Bulk reassign a list of faces to a different person. /// /// Note: This method returns the HTTP [Response]. /// @@ -499,7 +533,9 @@ class PeopleApi { ); } - /// This endpoint requires the `person.reassign` permission. + /// Reassign faces + /// + /// Bulk reassign a list of faces to a different person. /// /// Parameters: /// @@ -524,7 +560,9 @@ class PeopleApi { return null; } - /// This endpoint requires the `person.update` permission. + /// Update people + /// + /// Bulk update multiple people at once. /// /// Note: This method returns the HTTP [Response]. /// @@ -556,7 +594,9 @@ class PeopleApi { ); } - /// This endpoint requires the `person.update` permission. + /// Update people + /// + /// Bulk update multiple people at once. /// /// Parameters: /// @@ -579,7 +619,9 @@ class PeopleApi { return null; } - /// This endpoint requires the `person.update` permission. + /// Update person + /// + /// Update an individual person. /// /// Note: This method returns the HTTP [Response]. /// @@ -614,7 +656,9 @@ class PeopleApi { ); } - /// This endpoint requires the `person.update` permission. + /// Update person + /// + /// Update an individual person. /// /// Parameters: /// diff --git a/mobile/openapi/lib/api/plugins_api.dart b/mobile/openapi/lib/api/plugins_api.dart new file mode 100644 index 0000000000..264d3049e8 --- /dev/null +++ b/mobile/openapi/lib/api/plugins_api.dart @@ -0,0 +1,126 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class PluginsApi { + PluginsApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Retrieve a plugin + /// + /// Retrieve information about a specific plugin by its ID. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getPluginWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/plugins/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve a plugin + /// + /// Retrieve information about a specific plugin by its ID. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getPlugin(String id,) async { + final response = await getPluginWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'PluginResponseDto',) as PluginResponseDto; + + } + return null; + } + + /// List all plugins + /// + /// Retrieve a list of plugins available to the authenticated user. + /// + /// Note: This method returns the HTTP [Response]. + Future getPluginsWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/plugins'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// List all plugins + /// + /// Retrieve a list of plugins available to the authenticated user. + Future?> getPlugins() async { + final response = await getPluginsWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/search_api.dart b/mobile/openapi/lib/api/search_api.dart index 4d9e1172b8..ee5f64753c 100644 --- a/mobile/openapi/lib/api/search_api.dart +++ b/mobile/openapi/lib/api/search_api.dart @@ -16,7 +16,9 @@ class SearchApi { final ApiClient apiClient; - /// This endpoint requires the `asset.read` permission. + /// Retrieve assets by city + /// + /// Retrieve a list of assets with each asset belonging to a different city. This endpoint is used on the places pages to show a single thumbnail for each city the user has assets in. /// /// Note: This method returns the HTTP [Response]. Future getAssetsByCityWithHttpInfo() async { @@ -44,7 +46,9 @@ class SearchApi { ); } - /// This endpoint requires the `asset.read` permission. + /// Retrieve assets by city + /// + /// Retrieve a list of assets with each asset belonging to a different city. This endpoint is used on the places pages to show a single thumbnail for each city the user has assets in. Future?> getAssetsByCity() async { final response = await getAssetsByCityWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -63,7 +67,9 @@ class SearchApi { return null; } - /// This endpoint requires the `asset.read` permission. + /// Retrieve explore data + /// + /// Retrieve data for the explore section, such as popular people and places. /// /// Note: This method returns the HTTP [Response]. Future getExploreDataWithHttpInfo() async { @@ -91,7 +97,9 @@ class SearchApi { ); } - /// This endpoint requires the `asset.read` permission. + /// Retrieve explore data + /// + /// Retrieve data for the explore section, such as popular people and places. Future?> getExploreData() async { final response = await getExploreDataWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -110,7 +118,9 @@ class SearchApi { return null; } - /// This endpoint requires the `asset.read` permission. + /// Retrieve search suggestions + /// + /// Retrieve search suggestions based on partial input. This endpoint is used for typeahead search features. /// /// Note: This method returns the HTTP [Response]. /// @@ -121,14 +131,15 @@ class SearchApi { /// * [String] country: /// /// * [bool] includeNull: - /// This property was added in v111.0.0 + /// + /// * [String] lensModel: /// /// * [String] make: /// /// * [String] model: /// /// * [String] state: - Future getSearchSuggestionsWithHttpInfo(SearchSuggestionType type, { String? country, bool? includeNull, String? make, String? model, String? state, }) async { + Future getSearchSuggestionsWithHttpInfo(SearchSuggestionType type, { String? country, bool? includeNull, String? lensModel, String? make, String? model, String? state, }) async { // ignore: prefer_const_declarations final apiPath = r'/search/suggestions'; @@ -145,6 +156,9 @@ class SearchApi { if (includeNull != null) { queryParams.addAll(_queryParams('', 'includeNull', includeNull)); } + if (lensModel != null) { + queryParams.addAll(_queryParams('', 'lensModel', lensModel)); + } if (make != null) { queryParams.addAll(_queryParams('', 'make', make)); } @@ -170,7 +184,9 @@ class SearchApi { ); } - /// This endpoint requires the `asset.read` permission. + /// Retrieve search suggestions + /// + /// Retrieve search suggestions based on partial input. This endpoint is used for typeahead search features. /// /// Parameters: /// @@ -179,15 +195,16 @@ class SearchApi { /// * [String] country: /// /// * [bool] includeNull: - /// This property was added in v111.0.0 + /// + /// * [String] lensModel: /// /// * [String] make: /// /// * [String] model: /// /// * [String] state: - Future?> getSearchSuggestions(SearchSuggestionType type, { String? country, bool? includeNull, String? make, String? model, String? state, }) async { - final response = await getSearchSuggestionsWithHttpInfo(type, country: country, includeNull: includeNull, make: make, model: model, state: state, ); + Future?> getSearchSuggestions(SearchSuggestionType type, { String? country, bool? includeNull, String? lensModel, String? make, String? model, String? state, }) async { + final response = await getSearchSuggestionsWithHttpInfo(type, country: country, includeNull: includeNull, lensModel: lensModel, make: make, model: model, state: state, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -204,7 +221,9 @@ class SearchApi { return null; } - /// This endpoint requires the `asset.statistics` permission. + /// Search asset statistics + /// + /// Retrieve statistical data about assets based on search criteria, such as the total matching count. /// /// Note: This method returns the HTTP [Response]. /// @@ -236,7 +255,9 @@ class SearchApi { ); } - /// This endpoint requires the `asset.statistics` permission. + /// Search asset statistics + /// + /// Retrieve statistical data about assets based on search criteria, such as the total matching count. /// /// Parameters: /// @@ -256,7 +277,9 @@ class SearchApi { return null; } - /// This endpoint requires the `asset.read` permission. + /// Search assets by metadata + /// + /// Search for assets based on various metadata criteria. /// /// Note: This method returns the HTTP [Response]. /// @@ -288,7 +311,9 @@ class SearchApi { ); } - /// This endpoint requires the `asset.read` permission. + /// Search assets by metadata + /// + /// Search for assets based on various metadata criteria. /// /// Parameters: /// @@ -308,7 +333,9 @@ class SearchApi { return null; } - /// This endpoint requires the `asset.read` permission. + /// Search large assets + /// + /// Search for assets that are considered large based on specified criteria. /// /// Note: This method returns the HTTP [Response]. /// @@ -346,6 +373,8 @@ class SearchApi { /// /// * [String] model: /// + /// * [String] ocr: + /// /// * [List] personIds: /// /// * [num] rating: @@ -375,7 +404,7 @@ class SearchApi { /// * [bool] withDeleted: /// /// * [bool] withExif: - Future searchLargeAssetsWithHttpInfo({ List? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceId, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, List? personIds, num? rating, num? size, String? state, List? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, }) async { + Future searchLargeAssetsWithHttpInfo({ List? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceId, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, String? ocr, List? personIds, num? rating, num? size, String? state, List? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, }) async { // ignore: prefer_const_declarations final apiPath = r'/search/large-assets'; @@ -434,6 +463,9 @@ class SearchApi { if (model != null) { queryParams.addAll(_queryParams('', 'model', model)); } + if (ocr != null) { + queryParams.addAll(_queryParams('', 'ocr', ocr)); + } if (personIds != null) { queryParams.addAll(_queryParams('multi', 'personIds', personIds)); } @@ -494,7 +526,9 @@ class SearchApi { ); } - /// This endpoint requires the `asset.read` permission. + /// Search large assets + /// + /// Search for assets that are considered large based on specified criteria. /// /// Parameters: /// @@ -530,6 +564,8 @@ class SearchApi { /// /// * [String] model: /// + /// * [String] ocr: + /// /// * [List] personIds: /// /// * [num] rating: @@ -559,8 +595,8 @@ class SearchApi { /// * [bool] withDeleted: /// /// * [bool] withExif: - Future?> searchLargeAssets({ List? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceId, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, List? personIds, num? rating, num? size, String? state, List? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, }) async { - final response = await searchLargeAssetsWithHttpInfo( albumIds: albumIds, city: city, country: country, createdAfter: createdAfter, createdBefore: createdBefore, deviceId: deviceId, isEncoded: isEncoded, isFavorite: isFavorite, isMotion: isMotion, isNotInAlbum: isNotInAlbum, isOffline: isOffline, lensModel: lensModel, libraryId: libraryId, make: make, minFileSize: minFileSize, model: model, personIds: personIds, rating: rating, size: size, state: state, tagIds: tagIds, takenAfter: takenAfter, takenBefore: takenBefore, trashedAfter: trashedAfter, trashedBefore: trashedBefore, type: type, updatedAfter: updatedAfter, updatedBefore: updatedBefore, visibility: visibility, withDeleted: withDeleted, withExif: withExif, ); + Future?> searchLargeAssets({ List? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceId, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, String? ocr, List? personIds, num? rating, num? size, String? state, List? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, }) async { + final response = await searchLargeAssetsWithHttpInfo( albumIds: albumIds, city: city, country: country, createdAfter: createdAfter, createdBefore: createdBefore, deviceId: deviceId, isEncoded: isEncoded, isFavorite: isFavorite, isMotion: isMotion, isNotInAlbum: isNotInAlbum, isOffline: isOffline, lensModel: lensModel, libraryId: libraryId, make: make, minFileSize: minFileSize, model: model, ocr: ocr, personIds: personIds, rating: rating, size: size, state: state, tagIds: tagIds, takenAfter: takenAfter, takenBefore: takenBefore, trashedAfter: trashedAfter, trashedBefore: trashedBefore, type: type, updatedAfter: updatedAfter, updatedBefore: updatedBefore, visibility: visibility, withDeleted: withDeleted, withExif: withExif, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -577,7 +613,9 @@ class SearchApi { return null; } - /// This endpoint requires the `person.read` permission. + /// Search people + /// + /// Search for people by name. /// /// Note: This method returns the HTTP [Response]. /// @@ -616,7 +654,9 @@ class SearchApi { ); } - /// This endpoint requires the `person.read` permission. + /// Search people + /// + /// Search for people by name. /// /// Parameters: /// @@ -641,7 +681,9 @@ class SearchApi { return null; } - /// This endpoint requires the `asset.read` permission. + /// Search places + /// + /// Search for places by name. /// /// Note: This method returns the HTTP [Response]. /// @@ -675,7 +717,9 @@ class SearchApi { ); } - /// This endpoint requires the `asset.read` permission. + /// Search places + /// + /// Search for places by name. /// /// Parameters: /// @@ -698,7 +742,9 @@ class SearchApi { return null; } - /// This endpoint requires the `asset.read` permission. + /// Search random assets + /// + /// Retrieve a random selection of assets based on the provided criteria. /// /// Note: This method returns the HTTP [Response]. /// @@ -730,7 +776,9 @@ class SearchApi { ); } - /// This endpoint requires the `asset.read` permission. + /// Search random assets + /// + /// Retrieve a random selection of assets based on the provided criteria. /// /// Parameters: /// @@ -753,7 +801,9 @@ class SearchApi { return null; } - /// This endpoint requires the `asset.read` permission. + /// Smart asset search + /// + /// Perform a smart search for assets by using machine learning vectors to determine relevance. /// /// Note: This method returns the HTTP [Response]. /// @@ -785,7 +835,9 @@ class SearchApi { ); } - /// This endpoint requires the `asset.read` permission. + /// Smart asset search + /// + /// Perform a smart search for assets by using machine learning vectors to determine relevance. /// /// Parameters: /// diff --git a/mobile/openapi/lib/api/server_api.dart b/mobile/openapi/lib/api/server_api.dart index 9fa8f2016d..f5b70a9ea4 100644 --- a/mobile/openapi/lib/api/server_api.dart +++ b/mobile/openapi/lib/api/server_api.dart @@ -16,7 +16,9 @@ class ServerApi { final ApiClient apiClient; - /// This endpoint is an admin-only route, and requires the `serverLicense.delete` permission. + /// Delete server product key + /// + /// Delete the currently set server product key. /// /// Note: This method returns the HTTP [Response]. Future deleteServerLicenseWithHttpInfo() async { @@ -44,7 +46,9 @@ class ServerApi { ); } - /// This endpoint is an admin-only route, and requires the `serverLicense.delete` permission. + /// Delete server product key + /// + /// Delete the currently set server product key. Future deleteServerLicense() async { final response = await deleteServerLicenseWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -52,7 +56,9 @@ class ServerApi { } } - /// This endpoint requires the `server.about` permission. + /// Get server information + /// + /// Retrieve a list of information about the server. /// /// Note: This method returns the HTTP [Response]. Future getAboutInfoWithHttpInfo() async { @@ -80,7 +86,9 @@ class ServerApi { ); } - /// This endpoint requires the `server.about` permission. + /// Get server information + /// + /// Retrieve a list of information about the server. Future getAboutInfo() async { final response = await getAboutInfoWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -96,7 +104,9 @@ class ServerApi { return null; } - /// This endpoint requires the `server.apkLinks` permission. + /// Get APK links + /// + /// Retrieve links to the APKs for the current server version. /// /// Note: This method returns the HTTP [Response]. Future getApkLinksWithHttpInfo() async { @@ -124,7 +134,9 @@ class ServerApi { ); } - /// This endpoint requires the `server.apkLinks` permission. + /// Get APK links + /// + /// Retrieve links to the APKs for the current server version. Future getApkLinks() async { final response = await getApkLinksWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -140,7 +152,11 @@ class ServerApi { return null; } - /// Performs an HTTP 'GET /server/config' operation and returns the [Response]. + /// Get config + /// + /// Retrieve the current server configuration. + /// + /// Note: This method returns the HTTP [Response]. Future getServerConfigWithHttpInfo() async { // ignore: prefer_const_declarations final apiPath = r'/server/config'; @@ -166,6 +182,9 @@ class ServerApi { ); } + /// Get config + /// + /// Retrieve the current server configuration. Future getServerConfig() async { final response = await getServerConfigWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -181,7 +200,11 @@ class ServerApi { return null; } - /// Performs an HTTP 'GET /server/features' operation and returns the [Response]. + /// Get features + /// + /// Retrieve available features supported by this server. + /// + /// Note: This method returns the HTTP [Response]. Future getServerFeaturesWithHttpInfo() async { // ignore: prefer_const_declarations final apiPath = r'/server/features'; @@ -207,6 +230,9 @@ class ServerApi { ); } + /// Get features + /// + /// Retrieve available features supported by this server. Future getServerFeatures() async { final response = await getServerFeaturesWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -222,7 +248,9 @@ class ServerApi { return null; } - /// This endpoint is an admin-only route, and requires the `serverLicense.read` permission. + /// Get product key + /// + /// Retrieve information about whether the server currently has a product key registered. /// /// Note: This method returns the HTTP [Response]. Future getServerLicenseWithHttpInfo() async { @@ -250,7 +278,9 @@ class ServerApi { ); } - /// This endpoint is an admin-only route, and requires the `serverLicense.read` permission. + /// Get product key + /// + /// Retrieve information about whether the server currently has a product key registered. Future getServerLicense() async { final response = await getServerLicenseWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -266,7 +296,9 @@ class ServerApi { return null; } - /// This endpoint is an admin-only route, and requires the `server.statistics` permission. + /// Get statistics + /// + /// Retrieve statistics about the entire Immich instance such as asset counts. /// /// Note: This method returns the HTTP [Response]. Future getServerStatisticsWithHttpInfo() async { @@ -294,7 +326,9 @@ class ServerApi { ); } - /// This endpoint is an admin-only route, and requires the `server.statistics` permission. + /// Get statistics + /// + /// Retrieve statistics about the entire Immich instance such as asset counts. Future getServerStatistics() async { final response = await getServerStatisticsWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -310,7 +344,11 @@ class ServerApi { return null; } - /// Performs an HTTP 'GET /server/version' operation and returns the [Response]. + /// Get server version + /// + /// Retrieve the current server version in semantic versioning (semver) format. + /// + /// Note: This method returns the HTTP [Response]. Future getServerVersionWithHttpInfo() async { // ignore: prefer_const_declarations final apiPath = r'/server/version'; @@ -336,6 +374,9 @@ class ServerApi { ); } + /// Get server version + /// + /// Retrieve the current server version in semantic versioning (semver) format. Future getServerVersion() async { final response = await getServerVersionWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -351,7 +392,9 @@ class ServerApi { return null; } - /// This endpoint requires the `server.storage` permission. + /// Get storage + /// + /// Retrieve the current storage utilization information of the server. /// /// Note: This method returns the HTTP [Response]. Future getStorageWithHttpInfo() async { @@ -379,7 +422,9 @@ class ServerApi { ); } - /// This endpoint requires the `server.storage` permission. + /// Get storage + /// + /// Retrieve the current storage utilization information of the server. Future getStorage() async { final response = await getStorageWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -395,7 +440,11 @@ class ServerApi { return null; } - /// Performs an HTTP 'GET /server/media-types' operation and returns the [Response]. + /// Get supported media types + /// + /// Retrieve all media types supported by the server. + /// + /// Note: This method returns the HTTP [Response]. Future getSupportedMediaTypesWithHttpInfo() async { // ignore: prefer_const_declarations final apiPath = r'/server/media-types'; @@ -421,6 +470,9 @@ class ServerApi { ); } + /// Get supported media types + /// + /// Retrieve all media types supported by the server. Future getSupportedMediaTypes() async { final response = await getSupportedMediaTypesWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -436,7 +488,11 @@ class ServerApi { return null; } - /// Performs an HTTP 'GET /server/theme' operation and returns the [Response]. + /// Get theme + /// + /// Retrieve the custom CSS, if existent. + /// + /// Note: This method returns the HTTP [Response]. Future getThemeWithHttpInfo() async { // ignore: prefer_const_declarations final apiPath = r'/server/theme'; @@ -462,6 +518,9 @@ class ServerApi { ); } + /// Get theme + /// + /// Retrieve the custom CSS, if existent. Future getTheme() async { final response = await getThemeWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -477,7 +536,9 @@ class ServerApi { return null; } - /// This endpoint requires the `server.versionCheck` permission. + /// Get version check status + /// + /// Retrieve information about the last time the version check ran. /// /// Note: This method returns the HTTP [Response]. Future getVersionCheckWithHttpInfo() async { @@ -505,7 +566,9 @@ class ServerApi { ); } - /// This endpoint requires the `server.versionCheck` permission. + /// Get version check status + /// + /// Retrieve information about the last time the version check ran. Future getVersionCheck() async { final response = await getVersionCheckWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -521,7 +584,11 @@ class ServerApi { return null; } - /// Performs an HTTP 'GET /server/version-history' operation and returns the [Response]. + /// Get version history + /// + /// Retrieve a list of past versions the server has been on. + /// + /// Note: This method returns the HTTP [Response]. Future getVersionHistoryWithHttpInfo() async { // ignore: prefer_const_declarations final apiPath = r'/server/version-history'; @@ -547,6 +614,9 @@ class ServerApi { ); } + /// Get version history + /// + /// Retrieve a list of past versions the server has been on. Future?> getVersionHistory() async { final response = await getVersionHistoryWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -565,7 +635,11 @@ class ServerApi { return null; } - /// Performs an HTTP 'GET /server/ping' operation and returns the [Response]. + /// Ping + /// + /// Pong + /// + /// Note: This method returns the HTTP [Response]. Future pingServerWithHttpInfo() async { // ignore: prefer_const_declarations final apiPath = r'/server/ping'; @@ -591,6 +665,9 @@ class ServerApi { ); } + /// Ping + /// + /// Pong Future pingServer() async { final response = await pingServerWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -606,7 +683,9 @@ class ServerApi { return null; } - /// This endpoint is an admin-only route, and requires the `serverLicense.update` permission. + /// Set server product key + /// + /// Validate and set the server product key if successful. /// /// Note: This method returns the HTTP [Response]. /// @@ -638,7 +717,9 @@ class ServerApi { ); } - /// This endpoint is an admin-only route, and requires the `serverLicense.update` permission. + /// Set server product key + /// + /// Validate and set the server product key if successful. /// /// Parameters: /// diff --git a/mobile/openapi/lib/api/sessions_api.dart b/mobile/openapi/lib/api/sessions_api.dart index 63528d17a7..da508059bc 100644 --- a/mobile/openapi/lib/api/sessions_api.dart +++ b/mobile/openapi/lib/api/sessions_api.dart @@ -16,7 +16,9 @@ class SessionsApi { final ApiClient apiClient; - /// This endpoint requires the `session.create` permission. + /// Create a session + /// + /// Create a session as a child to the current session. This endpoint is used for casting. /// /// Note: This method returns the HTTP [Response]. /// @@ -48,7 +50,9 @@ class SessionsApi { ); } - /// This endpoint requires the `session.create` permission. + /// Create a session + /// + /// Create a session as a child to the current session. This endpoint is used for casting. /// /// Parameters: /// @@ -68,7 +72,9 @@ class SessionsApi { return null; } - /// This endpoint requires the `session.delete` permission. + /// Delete all sessions + /// + /// Delete all sessions for the user. This will not delete the current session. /// /// Note: This method returns the HTTP [Response]. Future deleteAllSessionsWithHttpInfo() async { @@ -96,7 +102,9 @@ class SessionsApi { ); } - /// This endpoint requires the `session.delete` permission. + /// Delete all sessions + /// + /// Delete all sessions for the user. This will not delete the current session. Future deleteAllSessions() async { final response = await deleteAllSessionsWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -104,7 +112,9 @@ class SessionsApi { } } - /// This endpoint requires the `session.delete` permission. + /// Delete a session + /// + /// Delete a specific session by id. /// /// Note: This method returns the HTTP [Response]. /// @@ -137,7 +147,9 @@ class SessionsApi { ); } - /// This endpoint requires the `session.delete` permission. + /// Delete a session + /// + /// Delete a specific session by id. /// /// Parameters: /// @@ -149,7 +161,9 @@ class SessionsApi { } } - /// This endpoint requires the `session.read` permission. + /// Retrieve sessions + /// + /// Retrieve a list of sessions for the user. /// /// Note: This method returns the HTTP [Response]. Future getSessionsWithHttpInfo() async { @@ -177,7 +191,9 @@ class SessionsApi { ); } - /// This endpoint requires the `session.read` permission. + /// Retrieve sessions + /// + /// Retrieve a list of sessions for the user. Future?> getSessions() async { final response = await getSessionsWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -196,7 +212,9 @@ class SessionsApi { return null; } - /// This endpoint requires the `session.lock` permission. + /// Lock a session + /// + /// Lock a specific session by id. /// /// Note: This method returns the HTTP [Response]. /// @@ -229,7 +247,9 @@ class SessionsApi { ); } - /// This endpoint requires the `session.lock` permission. + /// Lock a session + /// + /// Lock a specific session by id. /// /// Parameters: /// @@ -241,7 +261,9 @@ class SessionsApi { } } - /// This endpoint requires the `session.update` permission. + /// Update a session + /// + /// Update a specific session identified by id. /// /// Note: This method returns the HTTP [Response]. /// @@ -276,7 +298,9 @@ class SessionsApi { ); } - /// This endpoint requires the `session.update` permission. + /// Update a session + /// + /// Update a specific session identified by id. /// /// Parameters: /// diff --git a/mobile/openapi/lib/api/shared_links_api.dart b/mobile/openapi/lib/api/shared_links_api.dart index e32c566754..79106e5db6 100644 --- a/mobile/openapi/lib/api/shared_links_api.dart +++ b/mobile/openapi/lib/api/shared_links_api.dart @@ -16,7 +16,12 @@ class SharedLinksApi { final ApiClient apiClient; - /// Performs an HTTP 'PUT /shared-links/{id}/assets' operation and returns the [Response]. + /// Add assets to a shared link + /// + /// Add assets to a specific shared link by its ID. This endpoint is only relevant for shared link of type individual. + /// + /// Note: This method returns the HTTP [Response]. + /// /// Parameters: /// /// * [String] id (required): @@ -59,6 +64,10 @@ class SharedLinksApi { ); } + /// Add assets to a shared link + /// + /// Add assets to a specific shared link by its ID. This endpoint is only relevant for shared link of type individual. + /// /// Parameters: /// /// * [String] id (required): @@ -86,7 +95,9 @@ class SharedLinksApi { return null; } - /// This endpoint requires the `sharedLink.create` permission. + /// Create a shared link + /// + /// Create a new shared link. /// /// Note: This method returns the HTTP [Response]. /// @@ -118,7 +129,9 @@ class SharedLinksApi { ); } - /// This endpoint requires the `sharedLink.create` permission. + /// Create a shared link + /// + /// Create a new shared link. /// /// Parameters: /// @@ -138,7 +151,9 @@ class SharedLinksApi { return null; } - /// This endpoint requires the `sharedLink.read` permission. + /// Retrieve all shared links + /// + /// Retrieve a list of all shared links. /// /// Note: This method returns the HTTP [Response]. /// @@ -174,7 +189,9 @@ class SharedLinksApi { ); } - /// This endpoint requires the `sharedLink.read` permission. + /// Retrieve all shared links + /// + /// Retrieve a list of all shared links. /// /// Parameters: /// @@ -197,17 +214,22 @@ class SharedLinksApi { return null; } - /// Performs an HTTP 'GET /shared-links/me' operation and returns the [Response]. + /// Retrieve current shared link + /// + /// Retrieve the current shared link associated with authentication method. + /// + /// Note: This method returns the HTTP [Response]. + /// /// Parameters: /// - /// * [String] password: - /// - /// * [String] token: - /// /// * [String] key: /// + /// * [String] password: + /// /// * [String] slug: - Future getMySharedLinkWithHttpInfo({ String? password, String? token, String? key, String? slug, }) async { + /// + /// * [String] token: + Future getMySharedLinkWithHttpInfo({ String? key, String? password, String? slug, String? token, }) async { // ignore: prefer_const_declarations final apiPath = r'/shared-links/me'; @@ -218,18 +240,18 @@ class SharedLinksApi { final headerParams = {}; final formParams = {}; - if (password != null) { - queryParams.addAll(_queryParams('', 'password', password)); - } - if (token != null) { - queryParams.addAll(_queryParams('', 'token', token)); - } if (key != null) { queryParams.addAll(_queryParams('', 'key', key)); } + if (password != null) { + queryParams.addAll(_queryParams('', 'password', password)); + } if (slug != null) { queryParams.addAll(_queryParams('', 'slug', slug)); } + if (token != null) { + queryParams.addAll(_queryParams('', 'token', token)); + } const contentTypes = []; @@ -245,17 +267,21 @@ class SharedLinksApi { ); } + /// Retrieve current shared link + /// + /// Retrieve the current shared link associated with authentication method. + /// /// Parameters: /// - /// * [String] password: - /// - /// * [String] token: - /// /// * [String] key: /// + /// * [String] password: + /// /// * [String] slug: - Future getMySharedLink({ String? password, String? token, String? key, String? slug, }) async { - final response = await getMySharedLinkWithHttpInfo( password: password, token: token, key: key, slug: slug, ); + /// + /// * [String] token: + Future getMySharedLink({ String? key, String? password, String? slug, String? token, }) async { + final response = await getMySharedLinkWithHttpInfo( key: key, password: password, slug: slug, token: token, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -269,7 +295,9 @@ class SharedLinksApi { return null; } - /// This endpoint requires the `sharedLink.read` permission. + /// Retrieve a shared link + /// + /// Retrieve a specific shared link by its ID. /// /// Note: This method returns the HTTP [Response]. /// @@ -302,7 +330,9 @@ class SharedLinksApi { ); } - /// This endpoint requires the `sharedLink.read` permission. + /// Retrieve a shared link + /// + /// Retrieve a specific shared link by its ID. /// /// Parameters: /// @@ -322,7 +352,9 @@ class SharedLinksApi { return null; } - /// This endpoint requires the `sharedLink.delete` permission. + /// Delete a shared link + /// + /// Delete a specific shared link by its ID. /// /// Note: This method returns the HTTP [Response]. /// @@ -355,7 +387,9 @@ class SharedLinksApi { ); } - /// This endpoint requires the `sharedLink.delete` permission. + /// Delete a shared link + /// + /// Delete a specific shared link by its ID. /// /// Parameters: /// @@ -367,7 +401,12 @@ class SharedLinksApi { } } - /// Performs an HTTP 'DELETE /shared-links/{id}/assets' operation and returns the [Response]. + /// Remove assets from a shared link + /// + /// Remove assets from a specific shared link by its ID. This endpoint is only relevant for shared link of type individual. + /// + /// Note: This method returns the HTTP [Response]. + /// /// Parameters: /// /// * [String] id (required): @@ -410,6 +449,10 @@ class SharedLinksApi { ); } + /// Remove assets from a shared link + /// + /// Remove assets from a specific shared link by its ID. This endpoint is only relevant for shared link of type individual. + /// /// Parameters: /// /// * [String] id (required): @@ -437,7 +480,9 @@ class SharedLinksApi { return null; } - /// This endpoint requires the `sharedLink.update` permission. + /// Update a shared link + /// + /// Update an existing shared link by its ID. /// /// Note: This method returns the HTTP [Response]. /// @@ -472,7 +517,9 @@ class SharedLinksApi { ); } - /// This endpoint requires the `sharedLink.update` permission. + /// Update a shared link + /// + /// Update an existing shared link by its ID. /// /// Parameters: /// diff --git a/mobile/openapi/lib/api/stacks_api.dart b/mobile/openapi/lib/api/stacks_api.dart index 0f76f3396b..66fa1881ac 100644 --- a/mobile/openapi/lib/api/stacks_api.dart +++ b/mobile/openapi/lib/api/stacks_api.dart @@ -16,7 +16,9 @@ class StacksApi { final ApiClient apiClient; - /// This endpoint requires the `stack.create` permission. + /// Create a stack + /// + /// Create a new stack by providing a name and a list of asset IDs to include in the stack. If any of the provided asset IDs are primary assets of an existing stack, the existing stack will be merged into the newly created stack. /// /// Note: This method returns the HTTP [Response]. /// @@ -48,7 +50,9 @@ class StacksApi { ); } - /// This endpoint requires the `stack.create` permission. + /// Create a stack + /// + /// Create a new stack by providing a name and a list of asset IDs to include in the stack. If any of the provided asset IDs are primary assets of an existing stack, the existing stack will be merged into the newly created stack. /// /// Parameters: /// @@ -68,7 +72,9 @@ class StacksApi { return null; } - /// This endpoint requires the `stack.delete` permission. + /// Delete a stack + /// + /// Delete a specific stack by its ID. /// /// Note: This method returns the HTTP [Response]. /// @@ -101,7 +107,9 @@ class StacksApi { ); } - /// This endpoint requires the `stack.delete` permission. + /// Delete a stack + /// + /// Delete a specific stack by its ID. /// /// Parameters: /// @@ -113,7 +121,9 @@ class StacksApi { } } - /// This endpoint requires the `stack.delete` permission. + /// Delete stacks + /// + /// Delete multiple stacks by providing a list of stack IDs. /// /// Note: This method returns the HTTP [Response]. /// @@ -145,7 +155,9 @@ class StacksApi { ); } - /// This endpoint requires the `stack.delete` permission. + /// Delete stacks + /// + /// Delete multiple stacks by providing a list of stack IDs. /// /// Parameters: /// @@ -157,7 +169,9 @@ class StacksApi { } } - /// This endpoint requires the `stack.read` permission. + /// Retrieve a stack + /// + /// Retrieve a specific stack by its ID. /// /// Note: This method returns the HTTP [Response]. /// @@ -190,7 +204,9 @@ class StacksApi { ); } - /// This endpoint requires the `stack.read` permission. + /// Retrieve a stack + /// + /// Retrieve a specific stack by its ID. /// /// Parameters: /// @@ -210,7 +226,9 @@ class StacksApi { return null; } - /// This endpoint requires the `stack.update` permission. + /// Remove an asset from a stack + /// + /// Remove a specific asset from a stack by providing the stack ID and asset ID. /// /// Note: This method returns the HTTP [Response]. /// @@ -246,7 +264,9 @@ class StacksApi { ); } - /// This endpoint requires the `stack.update` permission. + /// Remove an asset from a stack + /// + /// Remove a specific asset from a stack by providing the stack ID and asset ID. /// /// Parameters: /// @@ -260,7 +280,9 @@ class StacksApi { } } - /// This endpoint requires the `stack.read` permission. + /// Retrieve stacks + /// + /// Retrieve a list of stacks. /// /// Note: This method returns the HTTP [Response]. /// @@ -296,7 +318,9 @@ class StacksApi { ); } - /// This endpoint requires the `stack.read` permission. + /// Retrieve stacks + /// + /// Retrieve a list of stacks. /// /// Parameters: /// @@ -319,7 +343,9 @@ class StacksApi { return null; } - /// This endpoint requires the `stack.update` permission. + /// Update a stack + /// + /// Update an existing stack by its ID. /// /// Note: This method returns the HTTP [Response]. /// @@ -354,7 +380,9 @@ class StacksApi { ); } - /// This endpoint requires the `stack.update` permission. + /// Update a stack + /// + /// Update an existing stack by its ID. /// /// Parameters: /// diff --git a/mobile/openapi/lib/api/sync_api.dart b/mobile/openapi/lib/api/sync_api.dart index 9e594d6ace..6194fd0f89 100644 --- a/mobile/openapi/lib/api/sync_api.dart +++ b/mobile/openapi/lib/api/sync_api.dart @@ -16,7 +16,9 @@ class SyncApi { final ApiClient apiClient; - /// This endpoint requires the `syncCheckpoint.delete` permission. + /// Delete acknowledgements + /// + /// Delete specific synchronization acknowledgments. /// /// Note: This method returns the HTTP [Response]. /// @@ -48,7 +50,9 @@ class SyncApi { ); } - /// This endpoint requires the `syncCheckpoint.delete` permission. + /// Delete acknowledgements + /// + /// Delete specific synchronization acknowledgments. /// /// Parameters: /// @@ -60,7 +64,12 @@ class SyncApi { } } - /// Performs an HTTP 'POST /sync/delta-sync' operation and returns the [Response]. + /// Get delta sync for user + /// + /// Retrieve changed assets since the last sync for the authenticated user. + /// + /// Note: This method returns the HTTP [Response]. + /// /// Parameters: /// /// * [AssetDeltaSyncDto] assetDeltaSyncDto (required): @@ -89,6 +98,10 @@ class SyncApi { ); } + /// Get delta sync for user + /// + /// Retrieve changed assets since the last sync for the authenticated user. + /// /// Parameters: /// /// * [AssetDeltaSyncDto] assetDeltaSyncDto (required): @@ -107,7 +120,12 @@ class SyncApi { return null; } - /// Performs an HTTP 'POST /sync/full-sync' operation and returns the [Response]. + /// Get full sync for user + /// + /// Retrieve all assets for a full synchronization for the authenticated user. + /// + /// Note: This method returns the HTTP [Response]. + /// /// Parameters: /// /// * [AssetFullSyncDto] assetFullSyncDto (required): @@ -136,6 +154,10 @@ class SyncApi { ); } + /// Get full sync for user + /// + /// Retrieve all assets for a full synchronization for the authenticated user. + /// /// Parameters: /// /// * [AssetFullSyncDto] assetFullSyncDto (required): @@ -157,7 +179,9 @@ class SyncApi { return null; } - /// This endpoint requires the `syncCheckpoint.read` permission. + /// Retrieve acknowledgements + /// + /// Retrieve the synchronization acknowledgments for the current session. /// /// Note: This method returns the HTTP [Response]. Future getSyncAckWithHttpInfo() async { @@ -185,7 +209,9 @@ class SyncApi { ); } - /// This endpoint requires the `syncCheckpoint.read` permission. + /// Retrieve acknowledgements + /// + /// Retrieve the synchronization acknowledgments for the current session. Future?> getSyncAck() async { final response = await getSyncAckWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -204,7 +230,9 @@ class SyncApi { return null; } - /// This endpoint requires the `sync.stream` permission. + /// Stream sync changes + /// + /// Retrieve a JSON lines streamed response of changes for synchronization. This endpoint is used by the mobile app to efficiently stay up to date with changes. /// /// Note: This method returns the HTTP [Response]. /// @@ -236,7 +264,9 @@ class SyncApi { ); } - /// This endpoint requires the `sync.stream` permission. + /// Stream sync changes + /// + /// Retrieve a JSON lines streamed response of changes for synchronization. This endpoint is used by the mobile app to efficiently stay up to date with changes. /// /// Parameters: /// @@ -248,7 +278,9 @@ class SyncApi { } } - /// This endpoint requires the `syncCheckpoint.update` permission. + /// Acknowledge changes + /// + /// Send a list of synchronization acknowledgements to confirm that the latest changes have been received. /// /// Note: This method returns the HTTP [Response]. /// @@ -280,7 +312,9 @@ class SyncApi { ); } - /// This endpoint requires the `syncCheckpoint.update` permission. + /// Acknowledge changes + /// + /// Send a list of synchronization acknowledgements to confirm that the latest changes have been received. /// /// Parameters: /// diff --git a/mobile/openapi/lib/api/system_config_api.dart b/mobile/openapi/lib/api/system_config_api.dart index 2ab3879b8a..b04da71273 100644 --- a/mobile/openapi/lib/api/system_config_api.dart +++ b/mobile/openapi/lib/api/system_config_api.dart @@ -16,7 +16,9 @@ class SystemConfigApi { final ApiClient apiClient; - /// This endpoint is an admin-only route, and requires the `systemConfig.read` permission. + /// Get system configuration + /// + /// Retrieve the current system configuration. /// /// Note: This method returns the HTTP [Response]. Future getConfigWithHttpInfo() async { @@ -44,7 +46,9 @@ class SystemConfigApi { ); } - /// This endpoint is an admin-only route, and requires the `systemConfig.read` permission. + /// Get system configuration + /// + /// Retrieve the current system configuration. Future getConfig() async { final response = await getConfigWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -60,7 +64,9 @@ class SystemConfigApi { return null; } - /// This endpoint is an admin-only route, and requires the `systemConfig.read` permission. + /// Get system configuration defaults + /// + /// Retrieve the default values for the system configuration. /// /// Note: This method returns the HTTP [Response]. Future getConfigDefaultsWithHttpInfo() async { @@ -88,7 +94,9 @@ class SystemConfigApi { ); } - /// This endpoint is an admin-only route, and requires the `systemConfig.read` permission. + /// Get system configuration defaults + /// + /// Retrieve the default values for the system configuration. Future getConfigDefaults() async { final response = await getConfigDefaultsWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -104,7 +112,9 @@ class SystemConfigApi { return null; } - /// This endpoint is an admin-only route, and requires the `systemConfig.read` permission. + /// Get storage template options + /// + /// Retrieve exemplary storage template options. /// /// Note: This method returns the HTTP [Response]. Future getStorageTemplateOptionsWithHttpInfo() async { @@ -132,7 +142,9 @@ class SystemConfigApi { ); } - /// This endpoint is an admin-only route, and requires the `systemConfig.read` permission. + /// Get storage template options + /// + /// Retrieve exemplary storage template options. Future getStorageTemplateOptions() async { final response = await getStorageTemplateOptionsWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -148,7 +160,9 @@ class SystemConfigApi { return null; } - /// This endpoint is an admin-only route, and requires the `systemConfig.update` permission. + /// Update system configuration + /// + /// Update the system configuration with a new system configuration. /// /// Note: This method returns the HTTP [Response]. /// @@ -180,7 +194,9 @@ class SystemConfigApi { ); } - /// This endpoint is an admin-only route, and requires the `systemConfig.update` permission. + /// Update system configuration + /// + /// Update the system configuration with a new system configuration. /// /// Parameters: /// diff --git a/mobile/openapi/lib/api/system_metadata_api.dart b/mobile/openapi/lib/api/system_metadata_api.dart index f6b9bad1d6..63fd7628ec 100644 --- a/mobile/openapi/lib/api/system_metadata_api.dart +++ b/mobile/openapi/lib/api/system_metadata_api.dart @@ -16,7 +16,9 @@ class SystemMetadataApi { final ApiClient apiClient; - /// This endpoint is an admin-only route, and requires the `systemMetadata.read` permission. + /// Retrieve admin onboarding + /// + /// Retrieve the current admin onboarding status. /// /// Note: This method returns the HTTP [Response]. Future getAdminOnboardingWithHttpInfo() async { @@ -44,7 +46,9 @@ class SystemMetadataApi { ); } - /// This endpoint is an admin-only route, and requires the `systemMetadata.read` permission. + /// Retrieve admin onboarding + /// + /// Retrieve the current admin onboarding status. Future getAdminOnboarding() async { final response = await getAdminOnboardingWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -60,7 +64,9 @@ class SystemMetadataApi { return null; } - /// This endpoint is an admin-only route, and requires the `systemMetadata.read` permission. + /// Retrieve reverse geocoding state + /// + /// Retrieve the current state of the reverse geocoding import. /// /// Note: This method returns the HTTP [Response]. Future getReverseGeocodingStateWithHttpInfo() async { @@ -88,7 +94,9 @@ class SystemMetadataApi { ); } - /// This endpoint is an admin-only route, and requires the `systemMetadata.read` permission. + /// Retrieve reverse geocoding state + /// + /// Retrieve the current state of the reverse geocoding import. Future getReverseGeocodingState() async { final response = await getReverseGeocodingStateWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -104,7 +112,9 @@ class SystemMetadataApi { return null; } - /// This endpoint is an admin-only route, and requires the `systemMetadata.read` permission. + /// Retrieve version check state + /// + /// Retrieve the current state of the version check process. /// /// Note: This method returns the HTTP [Response]. Future getVersionCheckStateWithHttpInfo() async { @@ -132,7 +142,9 @@ class SystemMetadataApi { ); } - /// This endpoint is an admin-only route, and requires the `systemMetadata.read` permission. + /// Retrieve version check state + /// + /// Retrieve the current state of the version check process. Future getVersionCheckState() async { final response = await getVersionCheckStateWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -148,7 +160,9 @@ class SystemMetadataApi { return null; } - /// This endpoint is an admin-only route, and requires the `systemMetadata.update` permission. + /// Update admin onboarding + /// + /// Update the admin onboarding status. /// /// Note: This method returns the HTTP [Response]. /// @@ -180,7 +194,9 @@ class SystemMetadataApi { ); } - /// This endpoint is an admin-only route, and requires the `systemMetadata.update` permission. + /// Update admin onboarding + /// + /// Update the admin onboarding status. /// /// Parameters: /// diff --git a/mobile/openapi/lib/api/tags_api.dart b/mobile/openapi/lib/api/tags_api.dart index a0cdb91acf..a6840f9483 100644 --- a/mobile/openapi/lib/api/tags_api.dart +++ b/mobile/openapi/lib/api/tags_api.dart @@ -16,7 +16,9 @@ class TagsApi { final ApiClient apiClient; - /// This endpoint requires the `tag.asset` permission. + /// Tag assets + /// + /// Add multiple tags to multiple assets in a single request. /// /// Note: This method returns the HTTP [Response]. /// @@ -48,7 +50,9 @@ class TagsApi { ); } - /// This endpoint requires the `tag.asset` permission. + /// Tag assets + /// + /// Add multiple tags to multiple assets in a single request. /// /// Parameters: /// @@ -68,7 +72,9 @@ class TagsApi { return null; } - /// This endpoint requires the `tag.create` permission. + /// Create a tag + /// + /// Create a new tag by providing a name and optional color. /// /// Note: This method returns the HTTP [Response]. /// @@ -100,7 +106,9 @@ class TagsApi { ); } - /// This endpoint requires the `tag.create` permission. + /// Create a tag + /// + /// Create a new tag by providing a name and optional color. /// /// Parameters: /// @@ -120,7 +128,9 @@ class TagsApi { return null; } - /// This endpoint requires the `tag.delete` permission. + /// Delete a tag + /// + /// Delete a specific tag by its ID. /// /// Note: This method returns the HTTP [Response]. /// @@ -153,7 +163,9 @@ class TagsApi { ); } - /// This endpoint requires the `tag.delete` permission. + /// Delete a tag + /// + /// Delete a specific tag by its ID. /// /// Parameters: /// @@ -165,7 +177,9 @@ class TagsApi { } } - /// This endpoint requires the `tag.read` permission. + /// Retrieve tags + /// + /// Retrieve a list of all tags. /// /// Note: This method returns the HTTP [Response]. Future getAllTagsWithHttpInfo() async { @@ -193,7 +207,9 @@ class TagsApi { ); } - /// This endpoint requires the `tag.read` permission. + /// Retrieve tags + /// + /// Retrieve a list of all tags. Future?> getAllTags() async { final response = await getAllTagsWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -212,7 +228,9 @@ class TagsApi { return null; } - /// This endpoint requires the `tag.read` permission. + /// Retrieve a tag + /// + /// Retrieve a specific tag by its ID. /// /// Note: This method returns the HTTP [Response]. /// @@ -245,7 +263,9 @@ class TagsApi { ); } - /// This endpoint requires the `tag.read` permission. + /// Retrieve a tag + /// + /// Retrieve a specific tag by its ID. /// /// Parameters: /// @@ -265,7 +285,9 @@ class TagsApi { return null; } - /// This endpoint requires the `tag.asset` permission. + /// Tag assets + /// + /// Add a tag to all the specified assets. /// /// Note: This method returns the HTTP [Response]. /// @@ -300,7 +322,9 @@ class TagsApi { ); } - /// This endpoint requires the `tag.asset` permission. + /// Tag assets + /// + /// Add a tag to all the specified assets. /// /// Parameters: /// @@ -325,7 +349,9 @@ class TagsApi { return null; } - /// This endpoint requires the `tag.asset` permission. + /// Untag assets + /// + /// Remove a tag from all the specified assets. /// /// Note: This method returns the HTTP [Response]. /// @@ -360,7 +386,9 @@ class TagsApi { ); } - /// This endpoint requires the `tag.asset` permission. + /// Untag assets + /// + /// Remove a tag from all the specified assets. /// /// Parameters: /// @@ -385,7 +413,9 @@ class TagsApi { return null; } - /// This endpoint requires the `tag.update` permission. + /// Update a tag + /// + /// Update an existing tag identified by its ID. /// /// Note: This method returns the HTTP [Response]. /// @@ -420,7 +450,9 @@ class TagsApi { ); } - /// This endpoint requires the `tag.update` permission. + /// Update a tag + /// + /// Update an existing tag identified by its ID. /// /// Parameters: /// @@ -442,7 +474,9 @@ class TagsApi { return null; } - /// This endpoint requires the `tag.create` permission. + /// Upsert tags + /// + /// Create or update multiple tags in a single request. /// /// Note: This method returns the HTTP [Response]. /// @@ -474,7 +508,9 @@ class TagsApi { ); } - /// This endpoint requires the `tag.create` permission. + /// Upsert tags + /// + /// Create or update multiple tags in a single request. /// /// Parameters: /// diff --git a/mobile/openapi/lib/api/timeline_api.dart b/mobile/openapi/lib/api/timeline_api.dart index 70ac076c9d..2afcea20ff 100644 --- a/mobile/openapi/lib/api/timeline_api.dart +++ b/mobile/openapi/lib/api/timeline_api.dart @@ -16,7 +16,9 @@ class TimelineApi { final ApiClient apiClient; - /// This endpoint requires the `asset.read` permission. + /// Get time bucket + /// + /// Retrieve a string of all asset ids in a given time bucket. /// /// Note: This method returns the HTTP [Response]. /// @@ -127,7 +129,9 @@ class TimelineApi { ); } - /// This endpoint requires the `asset.read` permission. + /// Get time bucket + /// + /// Retrieve a string of all asset ids in a given time bucket. /// /// Parameters: /// @@ -185,7 +189,9 @@ class TimelineApi { return null; } - /// This endpoint requires the `asset.read` permission. + /// Get time buckets + /// + /// Retrieve a list of all minimal time buckets. /// /// Note: This method returns the HTTP [Response]. /// @@ -292,7 +298,9 @@ class TimelineApi { ); } - /// This endpoint requires the `asset.read` permission. + /// Get time buckets + /// + /// Retrieve a list of all minimal time buckets. /// /// Parameters: /// diff --git a/mobile/openapi/lib/api/trash_api.dart b/mobile/openapi/lib/api/trash_api.dart index 480d19960a..f1dcbb8896 100644 --- a/mobile/openapi/lib/api/trash_api.dart +++ b/mobile/openapi/lib/api/trash_api.dart @@ -16,7 +16,9 @@ class TrashApi { final ApiClient apiClient; - /// This endpoint requires the `asset.delete` permission. + /// Empty trash + /// + /// Permanently delete all items in the trash. /// /// Note: This method returns the HTTP [Response]. Future emptyTrashWithHttpInfo() async { @@ -44,7 +46,9 @@ class TrashApi { ); } - /// This endpoint requires the `asset.delete` permission. + /// Empty trash + /// + /// Permanently delete all items in the trash. Future emptyTrash() async { final response = await emptyTrashWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -60,7 +64,9 @@ class TrashApi { return null; } - /// This endpoint requires the `asset.delete` permission. + /// Restore assets + /// + /// Restore specific assets from the trash. /// /// Note: This method returns the HTTP [Response]. /// @@ -92,7 +98,9 @@ class TrashApi { ); } - /// This endpoint requires the `asset.delete` permission. + /// Restore assets + /// + /// Restore specific assets from the trash. /// /// Parameters: /// @@ -112,7 +120,9 @@ class TrashApi { return null; } - /// This endpoint requires the `asset.delete` permission. + /// Restore trash + /// + /// Restore all items in the trash. /// /// Note: This method returns the HTTP [Response]. Future restoreTrashWithHttpInfo() async { @@ -140,7 +150,9 @@ class TrashApi { ); } - /// This endpoint requires the `asset.delete` permission. + /// Restore trash + /// + /// Restore all items in the trash. Future restoreTrash() async { final response = await restoreTrashWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { diff --git a/mobile/openapi/lib/api/users_admin_api.dart b/mobile/openapi/lib/api/users_admin_api.dart index e4fc1673ef..842a3ebc5b 100644 --- a/mobile/openapi/lib/api/users_admin_api.dart +++ b/mobile/openapi/lib/api/users_admin_api.dart @@ -16,7 +16,9 @@ class UsersAdminApi { final ApiClient apiClient; - /// This endpoint is an admin-only route, and requires the `adminUser.create` permission. + /// Create a user + /// + /// Create a new user. /// /// Note: This method returns the HTTP [Response]. /// @@ -48,7 +50,9 @@ class UsersAdminApi { ); } - /// This endpoint is an admin-only route, and requires the `adminUser.create` permission. + /// Create a user + /// + /// Create a new user. /// /// Parameters: /// @@ -68,7 +72,9 @@ class UsersAdminApi { return null; } - /// This endpoint is an admin-only route, and requires the `adminUser.delete` permission. + /// Delete a user + /// + /// Delete a user. /// /// Note: This method returns the HTTP [Response]. /// @@ -103,7 +109,9 @@ class UsersAdminApi { ); } - /// This endpoint is an admin-only route, and requires the `adminUser.delete` permission. + /// Delete a user + /// + /// Delete a user. /// /// Parameters: /// @@ -125,7 +133,9 @@ class UsersAdminApi { return null; } - /// This endpoint is an admin-only route, and requires the `adminUser.read` permission. + /// Retrieve a user + /// + /// Retrieve a specific user by their ID. /// /// Note: This method returns the HTTP [Response]. /// @@ -158,7 +168,9 @@ class UsersAdminApi { ); } - /// This endpoint is an admin-only route, and requires the `adminUser.read` permission. + /// Retrieve a user + /// + /// Retrieve a specific user by their ID. /// /// Parameters: /// @@ -178,7 +190,9 @@ class UsersAdminApi { return null; } - /// This endpoint is an admin-only route, and requires the `adminUser.read` permission. + /// Retrieve user preferences + /// + /// Retrieve the preferences of a specific user. /// /// Note: This method returns the HTTP [Response]. /// @@ -211,7 +225,9 @@ class UsersAdminApi { ); } - /// This endpoint is an admin-only route, and requires the `adminUser.read` permission. + /// Retrieve user preferences + /// + /// Retrieve the preferences of a specific user. /// /// Parameters: /// @@ -231,7 +247,69 @@ class UsersAdminApi { return null; } - /// This endpoint is an admin-only route, and requires the `adminUser.read` permission. + /// Retrieve user sessions + /// + /// Retrieve all sessions for a specific user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getUserSessionsAdminWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/admin/users/{id}/sessions' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve user sessions + /// + /// Retrieve all sessions for a specific user. + /// + /// Parameters: + /// + /// * [String] id (required): + Future?> getUserSessionsAdmin(String id,) async { + final response = await getUserSessionsAdminWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Retrieve user statistics + /// + /// Retrieve asset statistics for a specific user. /// /// Note: This method returns the HTTP [Response]. /// @@ -280,7 +358,9 @@ class UsersAdminApi { ); } - /// This endpoint is an admin-only route, and requires the `adminUser.read` permission. + /// Retrieve user statistics + /// + /// Retrieve asset statistics for a specific user. /// /// Parameters: /// @@ -306,7 +386,9 @@ class UsersAdminApi { return null; } - /// This endpoint is an admin-only route, and requires the `adminUser.delete` permission. + /// Restore a deleted user + /// + /// Restore a previously deleted user. /// /// Note: This method returns the HTTP [Response]. /// @@ -339,7 +421,9 @@ class UsersAdminApi { ); } - /// This endpoint is an admin-only route, and requires the `adminUser.delete` permission. + /// Restore a deleted user + /// + /// Restore a previously deleted user. /// /// Parameters: /// @@ -359,7 +443,9 @@ class UsersAdminApi { return null; } - /// This endpoint is an admin-only route, and requires the `adminUser.read` permission. + /// Search users + /// + /// Search for users. /// /// Note: This method returns the HTTP [Response]. /// @@ -400,7 +486,9 @@ class UsersAdminApi { ); } - /// This endpoint is an admin-only route, and requires the `adminUser.read` permission. + /// Search users + /// + /// Search for users. /// /// Parameters: /// @@ -425,7 +513,9 @@ class UsersAdminApi { return null; } - /// This endpoint is an admin-only route, and requires the `adminUser.update` permission. + /// Update a user + /// + /// Update an existing user. /// /// Note: This method returns the HTTP [Response]. /// @@ -460,7 +550,9 @@ class UsersAdminApi { ); } - /// This endpoint is an admin-only route, and requires the `adminUser.update` permission. + /// Update a user + /// + /// Update an existing user. /// /// Parameters: /// @@ -482,7 +574,9 @@ class UsersAdminApi { return null; } - /// This endpoint is an admin-only route, and requires the `adminUser.update` permission. + /// Update user preferences + /// + /// Update the preferences of a specific user. /// /// Note: This method returns the HTTP [Response]. /// @@ -517,7 +611,9 @@ class UsersAdminApi { ); } - /// This endpoint is an admin-only route, and requires the `adminUser.update` permission. + /// Update user preferences + /// + /// Update the preferences of a specific user. /// /// Parameters: /// diff --git a/mobile/openapi/lib/api/users_api.dart b/mobile/openapi/lib/api/users_api.dart index c8891ba0c2..f398d9c813 100644 --- a/mobile/openapi/lib/api/users_api.dart +++ b/mobile/openapi/lib/api/users_api.dart @@ -16,7 +16,9 @@ class UsersApi { final ApiClient apiClient; - /// This endpoint requires the `userProfileImage.update` permission. + /// Create user profile image + /// + /// Upload and set a new profile image for the current user. /// /// Note: This method returns the HTTP [Response]. /// @@ -58,7 +60,9 @@ class UsersApi { ); } - /// This endpoint requires the `userProfileImage.update` permission. + /// Create user profile image + /// + /// Upload and set a new profile image for the current user. /// /// Parameters: /// @@ -78,7 +82,9 @@ class UsersApi { return null; } - /// This endpoint requires the `userProfileImage.delete` permission. + /// Delete user profile image + /// + /// Delete the profile image of the current user. /// /// Note: This method returns the HTTP [Response]. Future deleteProfileImageWithHttpInfo() async { @@ -106,7 +112,9 @@ class UsersApi { ); } - /// This endpoint requires the `userProfileImage.delete` permission. + /// Delete user profile image + /// + /// Delete the profile image of the current user. Future deleteProfileImage() async { final response = await deleteProfileImageWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -114,7 +122,9 @@ class UsersApi { } } - /// This endpoint requires the `userLicense.delete` permission. + /// Delete user product key + /// + /// Delete the registered product key for the current user. /// /// Note: This method returns the HTTP [Response]. Future deleteUserLicenseWithHttpInfo() async { @@ -142,7 +152,9 @@ class UsersApi { ); } - /// This endpoint requires the `userLicense.delete` permission. + /// Delete user product key + /// + /// Delete the registered product key for the current user. Future deleteUserLicense() async { final response = await deleteUserLicenseWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -150,7 +162,9 @@ class UsersApi { } } - /// This endpoint requires the `userOnboarding.delete` permission. + /// Delete user onboarding + /// + /// Delete the onboarding status of the current user. /// /// Note: This method returns the HTTP [Response]. Future deleteUserOnboardingWithHttpInfo() async { @@ -178,7 +192,9 @@ class UsersApi { ); } - /// This endpoint requires the `userOnboarding.delete` permission. + /// Delete user onboarding + /// + /// Delete the onboarding status of the current user. Future deleteUserOnboarding() async { final response = await deleteUserOnboardingWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -186,7 +202,9 @@ class UsersApi { } } - /// This endpoint requires the `userPreference.read` permission. + /// Get my preferences + /// + /// Retrieve the preferences for the current user. /// /// Note: This method returns the HTTP [Response]. Future getMyPreferencesWithHttpInfo() async { @@ -214,7 +232,9 @@ class UsersApi { ); } - /// This endpoint requires the `userPreference.read` permission. + /// Get my preferences + /// + /// Retrieve the preferences for the current user. Future getMyPreferences() async { final response = await getMyPreferencesWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -230,7 +250,9 @@ class UsersApi { return null; } - /// This endpoint requires the `user.read` permission. + /// Get current user + /// + /// Retrieve information about the user making the API request. /// /// Note: This method returns the HTTP [Response]. Future getMyUserWithHttpInfo() async { @@ -258,7 +280,9 @@ class UsersApi { ); } - /// This endpoint requires the `user.read` permission. + /// Get current user + /// + /// Retrieve information about the user making the API request. Future getMyUser() async { final response = await getMyUserWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -274,7 +298,9 @@ class UsersApi { return null; } - /// This endpoint requires the `userProfileImage.read` permission. + /// Retrieve user profile image + /// + /// Retrieve the profile image file for a user. /// /// Note: This method returns the HTTP [Response]. /// @@ -307,7 +333,9 @@ class UsersApi { ); } - /// This endpoint requires the `userProfileImage.read` permission. + /// Retrieve user profile image + /// + /// Retrieve the profile image file for a user. /// /// Parameters: /// @@ -327,7 +355,9 @@ class UsersApi { return null; } - /// This endpoint requires the `user.read` permission. + /// Retrieve a user + /// + /// Retrieve a specific user by their ID. /// /// Note: This method returns the HTTP [Response]. /// @@ -360,7 +390,9 @@ class UsersApi { ); } - /// This endpoint requires the `user.read` permission. + /// Retrieve a user + /// + /// Retrieve a specific user by their ID. /// /// Parameters: /// @@ -380,7 +412,9 @@ class UsersApi { return null; } - /// This endpoint requires the `userLicense.read` permission. + /// Retrieve user product key + /// + /// Retrieve information about whether the current user has a registered product key. /// /// Note: This method returns the HTTP [Response]. Future getUserLicenseWithHttpInfo() async { @@ -408,7 +442,9 @@ class UsersApi { ); } - /// This endpoint requires the `userLicense.read` permission. + /// Retrieve user product key + /// + /// Retrieve information about whether the current user has a registered product key. Future getUserLicense() async { final response = await getUserLicenseWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -424,7 +460,9 @@ class UsersApi { return null; } - /// This endpoint requires the `userOnboarding.read` permission. + /// Retrieve user onboarding + /// + /// Retrieve the onboarding status of the current user. /// /// Note: This method returns the HTTP [Response]. Future getUserOnboardingWithHttpInfo() async { @@ -452,7 +490,9 @@ class UsersApi { ); } - /// This endpoint requires the `userOnboarding.read` permission. + /// Retrieve user onboarding + /// + /// Retrieve the onboarding status of the current user. Future getUserOnboarding() async { final response = await getUserOnboardingWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -468,7 +508,9 @@ class UsersApi { return null; } - /// This endpoint requires the `user.read` permission. + /// Get all users + /// + /// Retrieve a list of all users on the server. /// /// Note: This method returns the HTTP [Response]. Future searchUsersWithHttpInfo() async { @@ -496,7 +538,9 @@ class UsersApi { ); } - /// This endpoint requires the `user.read` permission. + /// Get all users + /// + /// Retrieve a list of all users on the server. Future?> searchUsers() async { final response = await searchUsersWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { @@ -515,7 +559,9 @@ class UsersApi { return null; } - /// This endpoint requires the `userLicense.update` permission. + /// Set user product key + /// + /// Register a product key for the current user. /// /// Note: This method returns the HTTP [Response]. /// @@ -547,7 +593,9 @@ class UsersApi { ); } - /// This endpoint requires the `userLicense.update` permission. + /// Set user product key + /// + /// Register a product key for the current user. /// /// Parameters: /// @@ -567,7 +615,9 @@ class UsersApi { return null; } - /// This endpoint requires the `userOnboarding.update` permission. + /// Update user onboarding + /// + /// Update the onboarding status of the current user. /// /// Note: This method returns the HTTP [Response]. /// @@ -599,7 +649,9 @@ class UsersApi { ); } - /// This endpoint requires the `userOnboarding.update` permission. + /// Update user onboarding + /// + /// Update the onboarding status of the current user. /// /// Parameters: /// @@ -619,7 +671,9 @@ class UsersApi { return null; } - /// This endpoint requires the `userPreference.update` permission. + /// Update my preferences + /// + /// Update the preferences of the current user. /// /// Note: This method returns the HTTP [Response]. /// @@ -651,7 +705,9 @@ class UsersApi { ); } - /// This endpoint requires the `userPreference.update` permission. + /// Update my preferences + /// + /// Update the preferences of the current user. /// /// Parameters: /// @@ -671,7 +727,9 @@ class UsersApi { return null; } - /// This endpoint requires the `user.update` permission. + /// Update current user + /// + /// Update the current user making teh API request. /// /// Note: This method returns the HTTP [Response]. /// @@ -703,7 +761,9 @@ class UsersApi { ); } - /// This endpoint requires the `user.update` permission. + /// Update current user + /// + /// Update the current user making teh API request. /// /// Parameters: /// diff --git a/mobile/openapi/lib/api/view_api.dart b/mobile/openapi/lib/api/views_api.dart similarity index 83% rename from mobile/openapi/lib/api/view_api.dart rename to mobile/openapi/lib/api/views_api.dart index 1fcaec759c..a45e89d58f 100644 --- a/mobile/openapi/lib/api/view_api.dart +++ b/mobile/openapi/lib/api/views_api.dart @@ -11,12 +11,17 @@ part of openapi.api; -class ViewApi { - ViewApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; +class ViewsApi { + ViewsApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; final ApiClient apiClient; - /// Performs an HTTP 'GET /view/folder' operation and returns the [Response]. + /// Retrieve assets by original path + /// + /// Retrieve assets that are children of a specific folder. + /// + /// Note: This method returns the HTTP [Response]. + /// /// Parameters: /// /// * [String] path (required): @@ -47,6 +52,10 @@ class ViewApi { ); } + /// Retrieve assets by original path + /// + /// Retrieve assets that are children of a specific folder. + /// /// Parameters: /// /// * [String] path (required): @@ -68,7 +77,11 @@ class ViewApi { return null; } - /// Performs an HTTP 'GET /view/folder/unique-paths' operation and returns the [Response]. + /// Retrieve unique paths + /// + /// Retrieve a list of unique folder paths from asset original paths. + /// + /// Note: This method returns the HTTP [Response]. Future getUniqueOriginalPathsWithHttpInfo() async { // ignore: prefer_const_declarations final apiPath = r'/view/folder/unique-paths'; @@ -94,6 +107,9 @@ class ViewApi { ); } + /// Retrieve unique paths + /// + /// Retrieve a list of unique folder paths from asset original paths. Future?> getUniqueOriginalPaths() async { final response = await getUniqueOriginalPathsWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { diff --git a/mobile/openapi/lib/api/o_auth_api.dart b/mobile/openapi/lib/api/workflows_api.dart similarity index 55% rename from mobile/openapi/lib/api/o_auth_api.dart rename to mobile/openapi/lib/api/workflows_api.dart index 9f16e37c70..c589ec9823 100644 --- a/mobile/openapi/lib/api/o_auth_api.dart +++ b/mobile/openapi/lib/api/workflows_api.dart @@ -11,21 +11,26 @@ part of openapi.api; -class OAuthApi { - OAuthApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; +class WorkflowsApi { + WorkflowsApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; final ApiClient apiClient; - /// Performs an HTTP 'POST /oauth/callback' operation and returns the [Response]. + /// Create a workflow + /// + /// Create a new workflow, the workflow can also be created with empty filters and actions. + /// + /// Note: This method returns the HTTP [Response]. + /// /// Parameters: /// - /// * [OAuthCallbackDto] oAuthCallbackDto (required): - Future finishOAuthWithHttpInfo(OAuthCallbackDto oAuthCallbackDto,) async { + /// * [WorkflowCreateDto] workflowCreateDto (required): + Future createWorkflowWithHttpInfo(WorkflowCreateDto workflowCreateDto,) async { // ignore: prefer_const_declarations - final apiPath = r'/oauth/callback'; + final apiPath = r'/workflows'; // ignore: prefer_final_locals - Object? postBody = oAuthCallbackDto; + Object? postBody = workflowCreateDto; final queryParams = []; final headerParams = {}; @@ -45,11 +50,15 @@ class OAuthApi { ); } + /// Create a workflow + /// + /// Create a new workflow, the workflow can also be created with empty filters and actions. + /// /// Parameters: /// - /// * [OAuthCallbackDto] oAuthCallbackDto (required): - Future finishOAuth(OAuthCallbackDto oAuthCallbackDto,) async { - final response = await finishOAuthWithHttpInfo(oAuthCallbackDto,); + /// * [WorkflowCreateDto] workflowCreateDto (required): + Future createWorkflow(WorkflowCreateDto workflowCreateDto,) async { + final response = await createWorkflowWithHttpInfo(workflowCreateDto,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -57,33 +66,39 @@ class OAuthApi { // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" // FormatException when trying to decode an empty string. if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { - return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'LoginResponseDto',) as LoginResponseDto; + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'WorkflowResponseDto',) as WorkflowResponseDto; } return null; } - /// Performs an HTTP 'POST /oauth/link' operation and returns the [Response]. + /// Delete a workflow + /// + /// Delete a workflow by its ID. + /// + /// Note: This method returns the HTTP [Response]. + /// /// Parameters: /// - /// * [OAuthCallbackDto] oAuthCallbackDto (required): - Future linkOAuthAccountWithHttpInfo(OAuthCallbackDto oAuthCallbackDto,) async { + /// * [String] id (required): + Future deleteWorkflowWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final apiPath = r'/oauth/link'; + final apiPath = r'/workflows/{id}' + .replaceAll('{id}', id); // ignore: prefer_final_locals - Object? postBody = oAuthCallbackDto; + Object? postBody; final queryParams = []; final headerParams = {}; final formParams = {}; - const contentTypes = ['application/json']; + const contentTypes = []; return apiClient.invokeAPI( apiPath, - 'POST', + 'DELETE', queryParams, postBody, headerParams, @@ -92,28 +107,33 @@ class OAuthApi { ); } + /// Delete a workflow + /// + /// Delete a workflow by its ID. + /// /// Parameters: /// - /// * [OAuthCallbackDto] oAuthCallbackDto (required): - Future linkOAuthAccount(OAuthCallbackDto oAuthCallbackDto,) async { - final response = await linkOAuthAccountWithHttpInfo(oAuthCallbackDto,); + /// * [String] id (required): + Future deleteWorkflow(String id,) async { + final response = await deleteWorkflowWithHttpInfo(id,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } - // When a remote server returns no body with a status of 204, we shall not decode it. - // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" - // FormatException when trying to decode an empty string. - if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { - return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto; - - } - return null; } - /// Performs an HTTP 'GET /oauth/mobile-redirect' operation and returns the [Response]. - Future redirectOAuthToMobileWithHttpInfo() async { + /// Retrieve a workflow + /// + /// Retrieve information about a specific workflow by its ID. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getWorkflowWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final apiPath = r'/oauth/mobile-redirect'; + final apiPath = r'/workflows/{id}' + .replaceAll('{id}', id); // ignore: prefer_final_locals Object? postBody; @@ -136,47 +156,15 @@ class OAuthApi { ); } - Future redirectOAuthToMobile() async { - final response = await redirectOAuthToMobileWithHttpInfo(); - if (response.statusCode >= HttpStatus.badRequest) { - throw ApiException(response.statusCode, await _decodeBodyBytes(response)); - } - } - - /// Performs an HTTP 'POST /oauth/authorize' operation and returns the [Response]. + /// Retrieve a workflow + /// + /// Retrieve information about a specific workflow by its ID. + /// /// Parameters: /// - /// * [OAuthConfigDto] oAuthConfigDto (required): - Future startOAuthWithHttpInfo(OAuthConfigDto oAuthConfigDto,) async { - // ignore: prefer_const_declarations - final apiPath = r'/oauth/authorize'; - - // ignore: prefer_final_locals - Object? postBody = oAuthConfigDto; - - final queryParams = []; - final headerParams = {}; - final formParams = {}; - - const contentTypes = ['application/json']; - - - return apiClient.invokeAPI( - apiPath, - 'POST', - queryParams, - postBody, - headerParams, - formParams, - contentTypes.isEmpty ? null : contentTypes.first, - ); - } - - /// Parameters: - /// - /// * [OAuthConfigDto] oAuthConfigDto (required): - Future startOAuth(OAuthConfigDto oAuthConfigDto,) async { - final response = await startOAuthWithHttpInfo(oAuthConfigDto,); + /// * [String] id (required): + Future getWorkflow(String id,) async { + final response = await getWorkflowWithHttpInfo(id,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -184,16 +172,20 @@ class OAuthApi { // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" // FormatException when trying to decode an empty string. if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { - return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'OAuthAuthorizeResponseDto',) as OAuthAuthorizeResponseDto; + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'WorkflowResponseDto',) as WorkflowResponseDto; } return null; } - /// Performs an HTTP 'POST /oauth/unlink' operation and returns the [Response]. - Future unlinkOAuthAccountWithHttpInfo() async { + /// List all workflows + /// + /// Retrieve a list of workflows available to the authenticated user. + /// + /// Note: This method returns the HTTP [Response]. + Future getWorkflowsWithHttpInfo() async { // ignore: prefer_const_declarations - final apiPath = r'/oauth/unlink'; + final apiPath = r'/workflows'; // ignore: prefer_final_locals Object? postBody; @@ -207,7 +199,7 @@ class OAuthApi { return apiClient.invokeAPI( apiPath, - 'POST', + 'GET', queryParams, postBody, headerParams, @@ -216,8 +208,11 @@ class OAuthApi { ); } - Future unlinkOAuthAccount() async { - final response = await unlinkOAuthAccountWithHttpInfo(); + /// List all workflows + /// + /// Retrieve a list of workflows available to the authenticated user. + Future?> getWorkflows() async { + final response = await getWorkflowsWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -225,7 +220,71 @@ class OAuthApi { // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" // FormatException when trying to decode an empty string. if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { - return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto; + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Update a workflow + /// + /// Update the information of a specific workflow by its ID. This endpoint can be used to update the workflow name, description, trigger type, filters and actions order, etc. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [WorkflowUpdateDto] workflowUpdateDto (required): + Future updateWorkflowWithHttpInfo(String id, WorkflowUpdateDto workflowUpdateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/workflows/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = workflowUpdateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Update a workflow + /// + /// Update the information of a specific workflow by its ID. This endpoint can be used to update the workflow name, description, trigger type, filters and actions order, etc. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [WorkflowUpdateDto] workflowUpdateDto (required): + Future updateWorkflow(String id, WorkflowUpdateDto workflowUpdateDto,) async { + final response = await updateWorkflowWithHttpInfo(id, workflowUpdateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'WorkflowResponseDto',) as WorkflowResponseDto; } return null; diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index 06d27593c9..91dc670d12 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -220,8 +220,6 @@ class ApiClient { return AlbumsResponse.fromJson(value); case 'AlbumsUpdate': return AlbumsUpdate.fromJson(value); - case 'AllJobStatusResponseDto': - return AllJobStatusResponseDto.fromJson(value); case 'AssetBulkDeleteDto': return AssetBulkDeleteDto.fromJson(value); case 'AssetBulkUpdateDto': @@ -234,6 +232,8 @@ class ApiClient { return AssetBulkUploadCheckResponseDto.fromJson(value); case 'AssetBulkUploadCheckResult': return AssetBulkUploadCheckResult.fromJson(value); + case 'AssetCopyDto': + return AssetCopyDto.fromJson(value); case 'AssetDeltaSyncDto': return AssetDeltaSyncDto.fromJson(value); case 'AssetDeltaSyncResponseDto': @@ -274,6 +274,8 @@ class ApiClient { return AssetMetadataUpsertDto.fromJson(value); case 'AssetMetadataUpsertItemDto': return AssetMetadataUpsertItemDto.fromJson(value); + case 'AssetOcrResponseDto': + return AssetOcrResponseDto.fromJson(value); case 'AssetOrder': return AssetOrderTypeTransformer().decode(value); case 'AssetResponseDto': @@ -314,6 +316,8 @@ class ApiClient { return CheckExistingAssetsResponseDto.fromJson(value); case 'Colorspace': return ColorspaceTypeTransformer().decode(value); + case 'ContributorCountResponseDto': + return ContributorCountResponseDto.fromJson(value); case 'CreateAlbumDto': return CreateAlbumDto.fromJson(value); case 'CreateLibraryDto': @@ -352,20 +356,10 @@ class ApiClient { return FoldersUpdate.fromJson(value); case 'ImageFormat': return ImageFormatTypeTransformer().decode(value); - case 'JobCommand': - return JobCommandTypeTransformer().decode(value); - case 'JobCommandDto': - return JobCommandDto.fromJson(value); - case 'JobCountsDto': - return JobCountsDto.fromJson(value); case 'JobCreateDto': return JobCreateDto.fromJson(value); - case 'JobName': - return JobNameTypeTransformer().decode(value); case 'JobSettingsDto': return JobSettingsDto.fromJson(value); - case 'JobStatusDto': - return JobStatusDto.fromJson(value); case 'LibraryResponseDto': return LibraryResponseDto.fromJson(value); case 'LibraryStatsResponseDto': @@ -398,6 +392,8 @@ class ApiClient { return MemoryCreateDto.fromJson(value); case 'MemoryResponseDto': return MemoryResponseDto.fromJson(value); + case 'MemorySearchOrder': + return MemorySearchOrderTypeTransformer().decode(value); case 'MemoryStatisticsResponseDto': return MemoryStatisticsResponseDto.fromJson(value); case 'MemoryType': @@ -430,6 +426,8 @@ class ApiClient { return OAuthConfigDto.fromJson(value); case 'OAuthTokenEndpointAuthMethod': return OAuthTokenEndpointAuthMethodTypeTransformer().decode(value); + case 'OcrConfig': + return OcrConfig.fromJson(value); case 'OnThisDayDto': return OnThisDayDto.fromJson(value); case 'OnboardingDto': @@ -474,12 +472,34 @@ class ApiClient { return PinCodeSetupDto.fromJson(value); case 'PlacesResponseDto': return PlacesResponseDto.fromJson(value); + case 'PluginActionResponseDto': + return PluginActionResponseDto.fromJson(value); + case 'PluginContext': + return PluginContextTypeTransformer().decode(value); + case 'PluginFilterResponseDto': + return PluginFilterResponseDto.fromJson(value); + case 'PluginResponseDto': + return PluginResponseDto.fromJson(value); + case 'PluginTriggerType': + return PluginTriggerTypeTypeTransformer().decode(value); case 'PurchaseResponse': return PurchaseResponse.fromJson(value); case 'PurchaseUpdate': return PurchaseUpdate.fromJson(value); + case 'QueueCommand': + return QueueCommandTypeTransformer().decode(value); + case 'QueueCommandDto': + return QueueCommandDto.fromJson(value); + case 'QueueName': + return QueueNameTypeTransformer().decode(value); + case 'QueueResponseDto': + return QueueResponseDto.fromJson(value); + case 'QueueStatisticsDto': + return QueueStatisticsDto.fromJson(value); case 'QueueStatusDto': return QueueStatusDto.fromJson(value); + case 'QueuesResponseDto': + return QueuesResponseDto.fromJson(value); case 'RandomSearchDto': return RandomSearchDto.fromJson(value); case 'RatingsResponse': @@ -780,6 +800,20 @@ class ApiClient { return VideoCodecTypeTransformer().decode(value); case 'VideoContainer': return VideoContainerTypeTransformer().decode(value); + case 'WorkflowActionItemDto': + return WorkflowActionItemDto.fromJson(value); + case 'WorkflowActionResponseDto': + return WorkflowActionResponseDto.fromJson(value); + case 'WorkflowCreateDto': + return WorkflowCreateDto.fromJson(value); + case 'WorkflowFilterItemDto': + return WorkflowFilterItemDto.fromJson(value); + case 'WorkflowFilterResponseDto': + return WorkflowFilterResponseDto.fromJson(value); + case 'WorkflowResponseDto': + return WorkflowResponseDto.fromJson(value); + case 'WorkflowUpdateDto': + return WorkflowUpdateDto.fromJson(value); default: dynamic match; if (value is List && (match = _regList.firstMatch(targetType)?.group(1)) != null) { diff --git a/mobile/openapi/lib/api_helper.dart b/mobile/openapi/lib/api_helper.dart index b34e9210c8..4b33a07214 100644 --- a/mobile/openapi/lib/api_helper.dart +++ b/mobile/openapi/lib/api_helper.dart @@ -94,18 +94,15 @@ String parameterToString(dynamic value) { if (value is ImageFormat) { return ImageFormatTypeTransformer().encode(value).toString(); } - if (value is JobCommand) { - return JobCommandTypeTransformer().encode(value).toString(); - } - if (value is JobName) { - return JobNameTypeTransformer().encode(value).toString(); - } if (value is LogLevel) { return LogLevelTypeTransformer().encode(value).toString(); } if (value is ManualJobName) { return ManualJobNameTypeTransformer().encode(value).toString(); } + if (value is MemorySearchOrder) { + return MemorySearchOrderTypeTransformer().encode(value).toString(); + } if (value is MemoryType) { return MemoryTypeTypeTransformer().encode(value).toString(); } @@ -124,6 +121,18 @@ String parameterToString(dynamic value) { if (value is Permission) { return PermissionTypeTransformer().encode(value).toString(); } + if (value is PluginContext) { + return PluginContextTypeTransformer().encode(value).toString(); + } + if (value is PluginTriggerType) { + return PluginTriggerTypeTypeTransformer().encode(value).toString(); + } + if (value is QueueCommand) { + return QueueCommandTypeTransformer().encode(value).toString(); + } + if (value is QueueName) { + return QueueNameTypeTransformer().encode(value).toString(); + } if (value is ReactionLevel) { return ReactionLevelTypeTransformer().encode(value).toString(); } diff --git a/mobile/openapi/lib/model/album_response_dto.dart b/mobile/openapi/lib/model/album_response_dto.dart index 547a6a70fd..2f53706e7a 100644 --- a/mobile/openapi/lib/model/album_response_dto.dart +++ b/mobile/openapi/lib/model/album_response_dto.dart @@ -18,6 +18,7 @@ class AlbumResponseDto { this.albumUsers = const [], required this.assetCount, this.assets = const [], + this.contributorCounts = const [], required this.createdAt, required this.description, this.endDate, @@ -43,6 +44,8 @@ class AlbumResponseDto { List assets; + List contributorCounts; + DateTime createdAt; String description; @@ -100,6 +103,7 @@ class AlbumResponseDto { _deepEquality.equals(other.albumUsers, albumUsers) && other.assetCount == assetCount && _deepEquality.equals(other.assets, assets) && + _deepEquality.equals(other.contributorCounts, contributorCounts) && other.createdAt == createdAt && other.description == description && other.endDate == endDate && @@ -122,6 +126,7 @@ class AlbumResponseDto { (albumUsers.hashCode) + (assetCount.hashCode) + (assets.hashCode) + + (contributorCounts.hashCode) + (createdAt.hashCode) + (description.hashCode) + (endDate == null ? 0 : endDate!.hashCode) + @@ -137,7 +142,7 @@ class AlbumResponseDto { (updatedAt.hashCode); @override - String toString() => 'AlbumResponseDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, albumUsers=$albumUsers, assetCount=$assetCount, assets=$assets, createdAt=$createdAt, description=$description, endDate=$endDate, hasSharedLink=$hasSharedLink, id=$id, isActivityEnabled=$isActivityEnabled, lastModifiedAssetTimestamp=$lastModifiedAssetTimestamp, order=$order, owner=$owner, ownerId=$ownerId, shared=$shared, startDate=$startDate, updatedAt=$updatedAt]'; + String toString() => 'AlbumResponseDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, albumUsers=$albumUsers, assetCount=$assetCount, assets=$assets, contributorCounts=$contributorCounts, createdAt=$createdAt, description=$description, endDate=$endDate, hasSharedLink=$hasSharedLink, id=$id, isActivityEnabled=$isActivityEnabled, lastModifiedAssetTimestamp=$lastModifiedAssetTimestamp, order=$order, owner=$owner, ownerId=$ownerId, shared=$shared, startDate=$startDate, updatedAt=$updatedAt]'; Map toJson() { final json = {}; @@ -150,6 +155,7 @@ class AlbumResponseDto { json[r'albumUsers'] = this.albumUsers; json[r'assetCount'] = this.assetCount; json[r'assets'] = this.assets; + json[r'contributorCounts'] = this.contributorCounts; json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); json[r'description'] = this.description; if (this.endDate != null) { @@ -196,6 +202,7 @@ class AlbumResponseDto { albumUsers: AlbumUserResponseDto.listFromJson(json[r'albumUsers']), assetCount: mapValueOfType(json, r'assetCount')!, assets: AssetResponseDto.listFromJson(json[r'assets']), + contributorCounts: ContributorCountResponseDto.listFromJson(json[r'contributorCounts']), createdAt: mapDateTime(json, r'createdAt', r'')!, description: mapValueOfType(json, r'description')!, endDate: mapDateTime(json, r'endDate', r''), diff --git a/mobile/openapi/lib/model/asset_copy_dto.dart b/mobile/openapi/lib/model/asset_copy_dto.dart new file mode 100644 index 0000000000..ba19cb1dbc --- /dev/null +++ b/mobile/openapi/lib/model/asset_copy_dto.dart @@ -0,0 +1,142 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetCopyDto { + /// Returns a new [AssetCopyDto] instance. + AssetCopyDto({ + this.albums = true, + this.favorite = true, + this.sharedLinks = true, + this.sidecar = true, + required this.sourceId, + this.stack = true, + required this.targetId, + }); + + bool albums; + + bool favorite; + + bool sharedLinks; + + bool sidecar; + + String sourceId; + + bool stack; + + String targetId; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetCopyDto && + other.albums == albums && + other.favorite == favorite && + other.sharedLinks == sharedLinks && + other.sidecar == sidecar && + other.sourceId == sourceId && + other.stack == stack && + other.targetId == targetId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (albums.hashCode) + + (favorite.hashCode) + + (sharedLinks.hashCode) + + (sidecar.hashCode) + + (sourceId.hashCode) + + (stack.hashCode) + + (targetId.hashCode); + + @override + String toString() => 'AssetCopyDto[albums=$albums, favorite=$favorite, sharedLinks=$sharedLinks, sidecar=$sidecar, sourceId=$sourceId, stack=$stack, targetId=$targetId]'; + + Map toJson() { + final json = {}; + json[r'albums'] = this.albums; + json[r'favorite'] = this.favorite; + json[r'sharedLinks'] = this.sharedLinks; + json[r'sidecar'] = this.sidecar; + json[r'sourceId'] = this.sourceId; + json[r'stack'] = this.stack; + json[r'targetId'] = this.targetId; + return json; + } + + /// Returns a new [AssetCopyDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetCopyDto? fromJson(dynamic value) { + upgradeDto(value, "AssetCopyDto"); + if (value is Map) { + final json = value.cast(); + + return AssetCopyDto( + albums: mapValueOfType(json, r'albums') ?? true, + favorite: mapValueOfType(json, r'favorite') ?? true, + sharedLinks: mapValueOfType(json, r'sharedLinks') ?? true, + sidecar: mapValueOfType(json, r'sidecar') ?? true, + sourceId: mapValueOfType(json, r'sourceId')!, + stack: mapValueOfType(json, r'stack') ?? true, + targetId: mapValueOfType(json, r'targetId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetCopyDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetCopyDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetCopyDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetCopyDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'sourceId', + 'targetId', + }; +} + diff --git a/mobile/openapi/lib/model/asset_ocr_response_dto.dart b/mobile/openapi/lib/model/asset_ocr_response_dto.dart new file mode 100644 index 0000000000..c7937c6eb2 --- /dev/null +++ b/mobile/openapi/lib/model/asset_ocr_response_dto.dart @@ -0,0 +1,206 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetOcrResponseDto { + /// Returns a new [AssetOcrResponseDto] instance. + AssetOcrResponseDto({ + required this.assetId, + required this.boxScore, + required this.id, + required this.text, + required this.textScore, + required this.x1, + required this.x2, + required this.x3, + required this.x4, + required this.y1, + required this.y2, + required this.y3, + required this.y4, + }); + + String assetId; + + /// Confidence score for text detection box + double boxScore; + + String id; + + /// Recognized text + String text; + + /// Confidence score for text recognition + double textScore; + + /// Normalized x coordinate of box corner 1 (0-1) + double x1; + + /// Normalized x coordinate of box corner 2 (0-1) + double x2; + + /// Normalized x coordinate of box corner 3 (0-1) + double x3; + + /// Normalized x coordinate of box corner 4 (0-1) + double x4; + + /// Normalized y coordinate of box corner 1 (0-1) + double y1; + + /// Normalized y coordinate of box corner 2 (0-1) + double y2; + + /// Normalized y coordinate of box corner 3 (0-1) + double y3; + + /// Normalized y coordinate of box corner 4 (0-1) + double y4; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetOcrResponseDto && + other.assetId == assetId && + other.boxScore == boxScore && + other.id == id && + other.text == text && + other.textScore == textScore && + other.x1 == x1 && + other.x2 == x2 && + other.x3 == x3 && + other.x4 == x4 && + other.y1 == y1 && + other.y2 == y2 && + other.y3 == y3 && + other.y4 == y4; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetId.hashCode) + + (boxScore.hashCode) + + (id.hashCode) + + (text.hashCode) + + (textScore.hashCode) + + (x1.hashCode) + + (x2.hashCode) + + (x3.hashCode) + + (x4.hashCode) + + (y1.hashCode) + + (y2.hashCode) + + (y3.hashCode) + + (y4.hashCode); + + @override + String toString() => 'AssetOcrResponseDto[assetId=$assetId, boxScore=$boxScore, id=$id, text=$text, textScore=$textScore, x1=$x1, x2=$x2, x3=$x3, x4=$x4, y1=$y1, y2=$y2, y3=$y3, y4=$y4]'; + + Map toJson() { + final json = {}; + json[r'assetId'] = this.assetId; + json[r'boxScore'] = this.boxScore; + json[r'id'] = this.id; + json[r'text'] = this.text; + json[r'textScore'] = this.textScore; + json[r'x1'] = this.x1; + json[r'x2'] = this.x2; + json[r'x3'] = this.x3; + json[r'x4'] = this.x4; + json[r'y1'] = this.y1; + json[r'y2'] = this.y2; + json[r'y3'] = this.y3; + json[r'y4'] = this.y4; + return json; + } + + /// Returns a new [AssetOcrResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetOcrResponseDto? fromJson(dynamic value) { + upgradeDto(value, "AssetOcrResponseDto"); + if (value is Map) { + final json = value.cast(); + + return AssetOcrResponseDto( + assetId: mapValueOfType(json, r'assetId')!, + boxScore: (mapValueOfType(json, r'boxScore')!).toDouble(), + id: mapValueOfType(json, r'id')!, + text: mapValueOfType(json, r'text')!, + textScore: (mapValueOfType(json, r'textScore')!).toDouble(), + x1: (mapValueOfType(json, r'x1')!).toDouble(), + x2: (mapValueOfType(json, r'x2')!).toDouble(), + x3: (mapValueOfType(json, r'x3')!).toDouble(), + x4: (mapValueOfType(json, r'x4')!).toDouble(), + y1: (mapValueOfType(json, r'y1')!).toDouble(), + y2: (mapValueOfType(json, r'y2')!).toDouble(), + y3: (mapValueOfType(json, r'y3')!).toDouble(), + y4: (mapValueOfType(json, r'y4')!).toDouble(), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetOcrResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetOcrResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetOcrResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetOcrResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetId', + 'boxScore', + 'id', + 'text', + 'textScore', + 'x1', + 'x2', + 'x3', + 'x4', + 'y1', + 'y2', + 'y3', + 'y4', + }; +} + diff --git a/mobile/openapi/lib/model/asset_response_dto.dart b/mobile/openapi/lib/model/asset_response_dto.dart index dc957b3bfc..8d49986359 100644 --- a/mobile/openapi/lib/model/asset_response_dto.dart +++ b/mobile/openapi/lib/model/asset_response_dto.dart @@ -87,7 +87,6 @@ class AssetResponseDto { bool isTrashed; - /// This property was deprecated in v1.106.0 String? libraryId; String? livePhotoVideoId; @@ -119,7 +118,6 @@ class AssetResponseDto { List people; - /// This property was deprecated in v1.113.0 /// /// Please note: This property should have been non-nullable! Since the specification file /// does not include a default value (using the "default:" property), however, the generated diff --git a/mobile/openapi/lib/model/change_password_dto.dart b/mobile/openapi/lib/model/change_password_dto.dart index 33b7f4a607..4a897f4079 100644 --- a/mobile/openapi/lib/model/change_password_dto.dart +++ b/mobile/openapi/lib/model/change_password_dto.dart @@ -13,30 +13,36 @@ part of openapi.api; class ChangePasswordDto { /// Returns a new [ChangePasswordDto] instance. ChangePasswordDto({ + this.invalidateSessions = false, required this.newPassword, required this.password, }); + bool invalidateSessions; + String newPassword; String password; @override bool operator ==(Object other) => identical(this, other) || other is ChangePasswordDto && + other.invalidateSessions == invalidateSessions && other.newPassword == newPassword && other.password == password; @override int get hashCode => // ignore: unnecessary_parenthesis + (invalidateSessions.hashCode) + (newPassword.hashCode) + (password.hashCode); @override - String toString() => 'ChangePasswordDto[newPassword=$newPassword, password=$password]'; + String toString() => 'ChangePasswordDto[invalidateSessions=$invalidateSessions, newPassword=$newPassword, password=$password]'; Map toJson() { final json = {}; + json[r'invalidateSessions'] = this.invalidateSessions; json[r'newPassword'] = this.newPassword; json[r'password'] = this.password; return json; @@ -51,6 +57,7 @@ class ChangePasswordDto { final json = value.cast(); return ChangePasswordDto( + invalidateSessions: mapValueOfType(json, r'invalidateSessions') ?? false, newPassword: mapValueOfType(json, r'newPassword')!, password: mapValueOfType(json, r'password')!, ); diff --git a/mobile/openapi/lib/model/contributor_count_response_dto.dart b/mobile/openapi/lib/model/contributor_count_response_dto.dart new file mode 100644 index 0000000000..e0e16ee427 --- /dev/null +++ b/mobile/openapi/lib/model/contributor_count_response_dto.dart @@ -0,0 +1,107 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class ContributorCountResponseDto { + /// Returns a new [ContributorCountResponseDto] instance. + ContributorCountResponseDto({ + required this.assetCount, + required this.userId, + }); + + int assetCount; + + String userId; + + @override + bool operator ==(Object other) => identical(this, other) || other is ContributorCountResponseDto && + other.assetCount == assetCount && + other.userId == userId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetCount.hashCode) + + (userId.hashCode); + + @override + String toString() => 'ContributorCountResponseDto[assetCount=$assetCount, userId=$userId]'; + + Map toJson() { + final json = {}; + json[r'assetCount'] = this.assetCount; + json[r'userId'] = this.userId; + return json; + } + + /// Returns a new [ContributorCountResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static ContributorCountResponseDto? fromJson(dynamic value) { + upgradeDto(value, "ContributorCountResponseDto"); + if (value is Map) { + final json = value.cast(); + + return ContributorCountResponseDto( + assetCount: mapValueOfType(json, r'assetCount')!, + userId: mapValueOfType(json, r'userId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ContributorCountResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = ContributorCountResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of ContributorCountResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = ContributorCountResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetCount', + 'userId', + }; +} + diff --git a/mobile/openapi/lib/model/job_command.dart b/mobile/openapi/lib/model/job_command.dart deleted file mode 100644 index 46ca7db68f..0000000000 --- a/mobile/openapi/lib/model/job_command.dart +++ /dev/null @@ -1,94 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - - -class JobCommand { - /// Instantiate a new enum with the provided [value]. - const JobCommand._(this.value); - - /// The underlying value of this enum member. - final String value; - - @override - String toString() => value; - - String toJson() => value; - - static const start = JobCommand._(r'start'); - static const pause = JobCommand._(r'pause'); - static const resume = JobCommand._(r'resume'); - static const empty = JobCommand._(r'empty'); - static const clearFailed = JobCommand._(r'clear-failed'); - - /// List of all possible values in this [enum][JobCommand]. - static const values = [ - start, - pause, - resume, - empty, - clearFailed, - ]; - - static JobCommand? fromJson(dynamic value) => JobCommandTypeTransformer().decode(value); - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = JobCommand.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } -} - -/// Transformation class that can [encode] an instance of [JobCommand] to String, -/// and [decode] dynamic data back to [JobCommand]. -class JobCommandTypeTransformer { - factory JobCommandTypeTransformer() => _instance ??= const JobCommandTypeTransformer._(); - - const JobCommandTypeTransformer._(); - - String encode(JobCommand data) => data.value; - - /// Decodes a [dynamic value][data] to a JobCommand. - /// - /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, - /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] - /// cannot be decoded successfully, then an [UnimplementedError] is thrown. - /// - /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, - /// and users are still using an old app with the old code. - JobCommand? decode(dynamic data, {bool allowNull = true}) { - if (data != null) { - switch (data) { - case r'start': return JobCommand.start; - case r'pause': return JobCommand.pause; - case r'resume': return JobCommand.resume; - case r'empty': return JobCommand.empty; - case r'clear-failed': return JobCommand.clearFailed; - default: - if (!allowNull) { - throw ArgumentError('Unknown enum value to decode: $data'); - } - } - } - return null; - } - - /// Singleton [JobCommandTypeTransformer] instance. - static JobCommandTypeTransformer? _instance; -} - diff --git a/mobile/openapi/lib/model/job_name.dart b/mobile/openapi/lib/model/job_name.dart deleted file mode 100644 index 6b9a002cbe..0000000000 --- a/mobile/openapi/lib/model/job_name.dart +++ /dev/null @@ -1,124 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// ignore_for_file: unused_element, unused_import -// ignore_for_file: always_put_required_named_parameters_first -// ignore_for_file: constant_identifier_names -// ignore_for_file: lines_longer_than_80_chars - -part of openapi.api; - - -class JobName { - /// Instantiate a new enum with the provided [value]. - const JobName._(this.value); - - /// The underlying value of this enum member. - final String value; - - @override - String toString() => value; - - String toJson() => value; - - static const thumbnailGeneration = JobName._(r'thumbnailGeneration'); - static const metadataExtraction = JobName._(r'metadataExtraction'); - static const videoConversion = JobName._(r'videoConversion'); - static const faceDetection = JobName._(r'faceDetection'); - static const facialRecognition = JobName._(r'facialRecognition'); - static const smartSearch = JobName._(r'smartSearch'); - static const duplicateDetection = JobName._(r'duplicateDetection'); - static const backgroundTask = JobName._(r'backgroundTask'); - static const storageTemplateMigration = JobName._(r'storageTemplateMigration'); - static const migration = JobName._(r'migration'); - static const search = JobName._(r'search'); - static const sidecar = JobName._(r'sidecar'); - static const library_ = JobName._(r'library'); - static const notifications = JobName._(r'notifications'); - static const backupDatabase = JobName._(r'backupDatabase'); - - /// List of all possible values in this [enum][JobName]. - static const values = [ - thumbnailGeneration, - metadataExtraction, - videoConversion, - faceDetection, - facialRecognition, - smartSearch, - duplicateDetection, - backgroundTask, - storageTemplateMigration, - migration, - search, - sidecar, - library_, - notifications, - backupDatabase, - ]; - - static JobName? fromJson(dynamic value) => JobNameTypeTransformer().decode(value); - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = JobName.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } -} - -/// Transformation class that can [encode] an instance of [JobName] to String, -/// and [decode] dynamic data back to [JobName]. -class JobNameTypeTransformer { - factory JobNameTypeTransformer() => _instance ??= const JobNameTypeTransformer._(); - - const JobNameTypeTransformer._(); - - String encode(JobName data) => data.value; - - /// Decodes a [dynamic value][data] to a JobName. - /// - /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, - /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] - /// cannot be decoded successfully, then an [UnimplementedError] is thrown. - /// - /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, - /// and users are still using an old app with the old code. - JobName? decode(dynamic data, {bool allowNull = true}) { - if (data != null) { - switch (data) { - case r'thumbnailGeneration': return JobName.thumbnailGeneration; - case r'metadataExtraction': return JobName.metadataExtraction; - case r'videoConversion': return JobName.videoConversion; - case r'faceDetection': return JobName.faceDetection; - case r'facialRecognition': return JobName.facialRecognition; - case r'smartSearch': return JobName.smartSearch; - case r'duplicateDetection': return JobName.duplicateDetection; - case r'backgroundTask': return JobName.backgroundTask; - case r'storageTemplateMigration': return JobName.storageTemplateMigration; - case r'migration': return JobName.migration; - case r'search': return JobName.search; - case r'sidecar': return JobName.sidecar; - case r'library': return JobName.library_; - case r'notifications': return JobName.notifications; - case r'backupDatabase': return JobName.backupDatabase; - default: - if (!allowNull) { - throw ArgumentError('Unknown enum value to decode: $data'); - } - } - } - return null; - } - - /// Singleton [JobNameTypeTransformer] instance. - static JobNameTypeTransformer? _instance; -} - diff --git a/mobile/openapi/lib/model/memories_response.dart b/mobile/openapi/lib/model/memories_response.dart index b9f8b5d8b1..cb42f596a6 100644 --- a/mobile/openapi/lib/model/memories_response.dart +++ b/mobile/openapi/lib/model/memories_response.dart @@ -13,25 +13,31 @@ part of openapi.api; class MemoriesResponse { /// Returns a new [MemoriesResponse] instance. MemoriesResponse({ + this.duration = 5, this.enabled = true, }); + int duration; + bool enabled; @override bool operator ==(Object other) => identical(this, other) || other is MemoriesResponse && + other.duration == duration && other.enabled == enabled; @override int get hashCode => // ignore: unnecessary_parenthesis + (duration.hashCode) + (enabled.hashCode); @override - String toString() => 'MemoriesResponse[enabled=$enabled]'; + String toString() => 'MemoriesResponse[duration=$duration, enabled=$enabled]'; Map toJson() { final json = {}; + json[r'duration'] = this.duration; json[r'enabled'] = this.enabled; return json; } @@ -45,6 +51,7 @@ class MemoriesResponse { final json = value.cast(); return MemoriesResponse( + duration: mapValueOfType(json, r'duration')!, enabled: mapValueOfType(json, r'enabled')!, ); } @@ -93,6 +100,7 @@ class MemoriesResponse { /// The list of required keys that must be present in a JSON. static const requiredKeys = { + 'duration', 'enabled', }; } diff --git a/mobile/openapi/lib/model/memories_update.dart b/mobile/openapi/lib/model/memories_update.dart index 71efd71ae7..39c46ffd2f 100644 --- a/mobile/openapi/lib/model/memories_update.dart +++ b/mobile/openapi/lib/model/memories_update.dart @@ -13,9 +13,19 @@ part of openapi.api; class MemoriesUpdate { /// Returns a new [MemoriesUpdate] instance. MemoriesUpdate({ + this.duration, this.enabled, }); + /// Minimum value: 1 + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + int? duration; + /// /// Please note: This property should have been non-nullable! Since the specification file /// does not include a default value (using the "default:" property), however, the generated @@ -26,18 +36,25 @@ class MemoriesUpdate { @override bool operator ==(Object other) => identical(this, other) || other is MemoriesUpdate && + other.duration == duration && other.enabled == enabled; @override int get hashCode => // ignore: unnecessary_parenthesis + (duration == null ? 0 : duration!.hashCode) + (enabled == null ? 0 : enabled!.hashCode); @override - String toString() => 'MemoriesUpdate[enabled=$enabled]'; + String toString() => 'MemoriesUpdate[duration=$duration, enabled=$enabled]'; Map toJson() { final json = {}; + if (this.duration != null) { + json[r'duration'] = this.duration; + } else { + // json[r'duration'] = null; + } if (this.enabled != null) { json[r'enabled'] = this.enabled; } else { @@ -55,6 +72,7 @@ class MemoriesUpdate { final json = value.cast(); return MemoriesUpdate( + duration: mapValueOfType(json, r'duration'), enabled: mapValueOfType(json, r'enabled'), ); } diff --git a/mobile/openapi/lib/model/memory_search_order.dart b/mobile/openapi/lib/model/memory_search_order.dart new file mode 100644 index 0000000000..bdf5b59894 --- /dev/null +++ b/mobile/openapi/lib/model/memory_search_order.dart @@ -0,0 +1,88 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class MemorySearchOrder { + /// Instantiate a new enum with the provided [value]. + const MemorySearchOrder._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const asc = MemorySearchOrder._(r'asc'); + static const desc = MemorySearchOrder._(r'desc'); + static const random = MemorySearchOrder._(r'random'); + + /// List of all possible values in this [enum][MemorySearchOrder]. + static const values = [ + asc, + desc, + random, + ]; + + static MemorySearchOrder? fromJson(dynamic value) => MemorySearchOrderTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = MemorySearchOrder.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [MemorySearchOrder] to String, +/// and [decode] dynamic data back to [MemorySearchOrder]. +class MemorySearchOrderTypeTransformer { + factory MemorySearchOrderTypeTransformer() => _instance ??= const MemorySearchOrderTypeTransformer._(); + + const MemorySearchOrderTypeTransformer._(); + + String encode(MemorySearchOrder data) => data.value; + + /// Decodes a [dynamic value][data] to a MemorySearchOrder. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + MemorySearchOrder? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'asc': return MemorySearchOrder.asc; + case r'desc': return MemorySearchOrder.desc; + case r'random': return MemorySearchOrder.random; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [MemorySearchOrderTypeTransformer] instance. + static MemorySearchOrderTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/metadata_search_dto.dart b/mobile/openapi/lib/model/metadata_search_dto.dart index b7e637d4b4..7d8d2b1314 100644 --- a/mobile/openapi/lib/model/metadata_search_dto.dart +++ b/mobile/openapi/lib/model/metadata_search_dto.dart @@ -33,6 +33,7 @@ class MetadataSearchDto { this.libraryId, this.make, this.model, + this.ocr, this.order = AssetOrder.desc, this.originalFileName, this.originalPath, @@ -182,6 +183,14 @@ class MetadataSearchDto { String? model; + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? ocr; + AssetOrder order; /// @@ -369,6 +378,7 @@ class MetadataSearchDto { other.libraryId == libraryId && other.make == make && other.model == model && + other.ocr == ocr && other.order == order && other.originalFileName == originalFileName && other.originalPath == originalPath && @@ -416,6 +426,7 @@ class MetadataSearchDto { (libraryId == null ? 0 : libraryId!.hashCode) + (make == null ? 0 : make!.hashCode) + (model == null ? 0 : model!.hashCode) + + (ocr == null ? 0 : ocr!.hashCode) + (order.hashCode) + (originalFileName == null ? 0 : originalFileName!.hashCode) + (originalPath == null ? 0 : originalPath!.hashCode) + @@ -441,7 +452,7 @@ class MetadataSearchDto { (withStacked == null ? 0 : withStacked!.hashCode); @override - String toString() => 'MetadataSearchDto[albumIds=$albumIds, checksum=$checksum, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, description=$description, deviceAssetId=$deviceAssetId, deviceId=$deviceId, encodedVideoPath=$encodedVideoPath, id=$id, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, order=$order, originalFileName=$originalFileName, originalPath=$originalPath, page=$page, personIds=$personIds, previewPath=$previewPath, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, thumbnailPath=$thumbnailPath, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]'; + String toString() => 'MetadataSearchDto[albumIds=$albumIds, checksum=$checksum, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, description=$description, deviceAssetId=$deviceAssetId, deviceId=$deviceId, encodedVideoPath=$encodedVideoPath, id=$id, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, ocr=$ocr, order=$order, originalFileName=$originalFileName, originalPath=$originalPath, page=$page, personIds=$personIds, previewPath=$previewPath, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, thumbnailPath=$thumbnailPath, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]'; Map toJson() { final json = {}; @@ -540,6 +551,11 @@ class MetadataSearchDto { json[r'model'] = this.model; } else { // json[r'model'] = null; + } + if (this.ocr != null) { + json[r'ocr'] = this.ocr; + } else { + // json[r'ocr'] = null; } json[r'order'] = this.order; if (this.originalFileName != null) { @@ -682,6 +698,7 @@ class MetadataSearchDto { libraryId: mapValueOfType(json, r'libraryId'), make: mapValueOfType(json, r'make'), model: mapValueOfType(json, r'model'), + ocr: mapValueOfType(json, r'ocr'), order: AssetOrder.fromJson(json[r'order']) ?? AssetOrder.desc, originalFileName: mapValueOfType(json, r'originalFileName'), originalPath: mapValueOfType(json, r'originalPath'), diff --git a/mobile/openapi/lib/model/notification_type.dart b/mobile/openapi/lib/model/notification_type.dart index 436d2d190f..b5885aa441 100644 --- a/mobile/openapi/lib/model/notification_type.dart +++ b/mobile/openapi/lib/model/notification_type.dart @@ -26,6 +26,8 @@ class NotificationType { static const jobFailed = NotificationType._(r'JobFailed'); static const backupFailed = NotificationType._(r'BackupFailed'); static const systemMessage = NotificationType._(r'SystemMessage'); + static const albumInvite = NotificationType._(r'AlbumInvite'); + static const albumUpdate = NotificationType._(r'AlbumUpdate'); static const custom = NotificationType._(r'Custom'); /// List of all possible values in this [enum][NotificationType]. @@ -33,6 +35,8 @@ class NotificationType { jobFailed, backupFailed, systemMessage, + albumInvite, + albumUpdate, custom, ]; @@ -75,6 +79,8 @@ class NotificationTypeTypeTransformer { case r'JobFailed': return NotificationType.jobFailed; case r'BackupFailed': return NotificationType.backupFailed; case r'SystemMessage': return NotificationType.systemMessage; + case r'AlbumInvite': return NotificationType.albumInvite; + case r'AlbumUpdate': return NotificationType.albumUpdate; case r'Custom': return NotificationType.custom; default: if (!allowNull) { diff --git a/mobile/openapi/lib/model/ocr_config.dart b/mobile/openapi/lib/model/ocr_config.dart new file mode 100644 index 0000000000..51746c4924 --- /dev/null +++ b/mobile/openapi/lib/model/ocr_config.dart @@ -0,0 +1,136 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class OcrConfig { + /// Returns a new [OcrConfig] instance. + OcrConfig({ + required this.enabled, + required this.maxResolution, + required this.minDetectionScore, + required this.minRecognitionScore, + required this.modelName, + }); + + bool enabled; + + /// Minimum value: 1 + int maxResolution; + + /// Minimum value: 0.1 + /// Maximum value: 1 + double minDetectionScore; + + /// Minimum value: 0.1 + /// Maximum value: 1 + double minRecognitionScore; + + String modelName; + + @override + bool operator ==(Object other) => identical(this, other) || other is OcrConfig && + other.enabled == enabled && + other.maxResolution == maxResolution && + other.minDetectionScore == minDetectionScore && + other.minRecognitionScore == minRecognitionScore && + other.modelName == modelName; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (enabled.hashCode) + + (maxResolution.hashCode) + + (minDetectionScore.hashCode) + + (minRecognitionScore.hashCode) + + (modelName.hashCode); + + @override + String toString() => 'OcrConfig[enabled=$enabled, maxResolution=$maxResolution, minDetectionScore=$minDetectionScore, minRecognitionScore=$minRecognitionScore, modelName=$modelName]'; + + Map toJson() { + final json = {}; + json[r'enabled'] = this.enabled; + json[r'maxResolution'] = this.maxResolution; + json[r'minDetectionScore'] = this.minDetectionScore; + json[r'minRecognitionScore'] = this.minRecognitionScore; + json[r'modelName'] = this.modelName; + return json; + } + + /// Returns a new [OcrConfig] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static OcrConfig? fromJson(dynamic value) { + upgradeDto(value, "OcrConfig"); + if (value is Map) { + final json = value.cast(); + + return OcrConfig( + enabled: mapValueOfType(json, r'enabled')!, + maxResolution: mapValueOfType(json, r'maxResolution')!, + minDetectionScore: (mapValueOfType(json, r'minDetectionScore')!).toDouble(), + minRecognitionScore: (mapValueOfType(json, r'minRecognitionScore')!).toDouble(), + modelName: mapValueOfType(json, r'modelName')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = OcrConfig.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = OcrConfig.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of OcrConfig-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = OcrConfig.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'enabled', + 'maxResolution', + 'minDetectionScore', + 'minRecognitionScore', + 'modelName', + }; +} + diff --git a/mobile/openapi/lib/model/people_response_dto.dart b/mobile/openapi/lib/model/people_response_dto.dart index 49f0e85aad..901c38ade9 100644 --- a/mobile/openapi/lib/model/people_response_dto.dart +++ b/mobile/openapi/lib/model/people_response_dto.dart @@ -19,7 +19,6 @@ class PeopleResponseDto { required this.total, }); - /// This property was added in v1.110.0 /// /// Please note: This property should have been non-nullable! Since the specification file /// does not include a default value (using the "default:" property), however, the generated diff --git a/mobile/openapi/lib/model/permission.dart b/mobile/openapi/lib/model/permission.dart index 95b9a55fba..8b05de523b 100644 --- a/mobile/openapi/lib/model/permission.dart +++ b/mobile/openapi/lib/model/permission.dart @@ -42,6 +42,7 @@ class Permission { static const assetPeriodDownload = Permission._(r'asset.download'); static const assetPeriodUpload = Permission._(r'asset.upload'); static const assetPeriodReplace = Permission._(r'asset.replace'); + static const assetPeriodCopy = Permission._(r'asset.copy'); static const albumPeriodCreate = Permission._(r'album.create'); static const albumPeriodRead = Permission._(r'album.read'); static const albumPeriodUpdate = Permission._(r'album.update'); @@ -97,6 +98,10 @@ class Permission { static const pinCodePeriodCreate = Permission._(r'pinCode.create'); static const pinCodePeriodUpdate = Permission._(r'pinCode.update'); static const pinCodePeriodDelete = Permission._(r'pinCode.delete'); + static const pluginPeriodCreate = Permission._(r'plugin.create'); + static const pluginPeriodRead = Permission._(r'plugin.read'); + static const pluginPeriodUpdate = Permission._(r'plugin.update'); + static const pluginPeriodDelete = Permission._(r'plugin.delete'); static const serverPeriodAbout = Permission._(r'server.about'); static const serverPeriodApkLinks = Permission._(r'server.apkLinks'); static const serverPeriodStorage = Permission._(r'server.storage'); @@ -146,10 +151,15 @@ class Permission { static const userProfileImagePeriodRead = Permission._(r'userProfileImage.read'); static const userProfileImagePeriodUpdate = Permission._(r'userProfileImage.update'); static const userProfileImagePeriodDelete = Permission._(r'userProfileImage.delete'); + static const workflowPeriodCreate = Permission._(r'workflow.create'); + static const workflowPeriodRead = Permission._(r'workflow.read'); + static const workflowPeriodUpdate = Permission._(r'workflow.update'); + static const workflowPeriodDelete = Permission._(r'workflow.delete'); static const adminUserPeriodCreate = Permission._(r'adminUser.create'); static const adminUserPeriodRead = Permission._(r'adminUser.read'); static const adminUserPeriodUpdate = Permission._(r'adminUser.update'); static const adminUserPeriodDelete = Permission._(r'adminUser.delete'); + static const adminSessionPeriodRead = Permission._(r'adminSession.read'); static const adminAuthPeriodUnlinkAll = Permission._(r'adminAuth.unlinkAll'); /// List of all possible values in this [enum][Permission]. @@ -173,6 +183,7 @@ class Permission { assetPeriodDownload, assetPeriodUpload, assetPeriodReplace, + assetPeriodCopy, albumPeriodCreate, albumPeriodRead, albumPeriodUpdate, @@ -228,6 +239,10 @@ class Permission { pinCodePeriodCreate, pinCodePeriodUpdate, pinCodePeriodDelete, + pluginPeriodCreate, + pluginPeriodRead, + pluginPeriodUpdate, + pluginPeriodDelete, serverPeriodAbout, serverPeriodApkLinks, serverPeriodStorage, @@ -277,10 +292,15 @@ class Permission { userProfileImagePeriodRead, userProfileImagePeriodUpdate, userProfileImagePeriodDelete, + workflowPeriodCreate, + workflowPeriodRead, + workflowPeriodUpdate, + workflowPeriodDelete, adminUserPeriodCreate, adminUserPeriodRead, adminUserPeriodUpdate, adminUserPeriodDelete, + adminSessionPeriodRead, adminAuthPeriodUnlinkAll, ]; @@ -339,6 +359,7 @@ class PermissionTypeTransformer { case r'asset.download': return Permission.assetPeriodDownload; case r'asset.upload': return Permission.assetPeriodUpload; case r'asset.replace': return Permission.assetPeriodReplace; + case r'asset.copy': return Permission.assetPeriodCopy; case r'album.create': return Permission.albumPeriodCreate; case r'album.read': return Permission.albumPeriodRead; case r'album.update': return Permission.albumPeriodUpdate; @@ -394,6 +415,10 @@ class PermissionTypeTransformer { case r'pinCode.create': return Permission.pinCodePeriodCreate; case r'pinCode.update': return Permission.pinCodePeriodUpdate; case r'pinCode.delete': return Permission.pinCodePeriodDelete; + case r'plugin.create': return Permission.pluginPeriodCreate; + case r'plugin.read': return Permission.pluginPeriodRead; + case r'plugin.update': return Permission.pluginPeriodUpdate; + case r'plugin.delete': return Permission.pluginPeriodDelete; case r'server.about': return Permission.serverPeriodAbout; case r'server.apkLinks': return Permission.serverPeriodApkLinks; case r'server.storage': return Permission.serverPeriodStorage; @@ -443,10 +468,15 @@ class PermissionTypeTransformer { case r'userProfileImage.read': return Permission.userProfileImagePeriodRead; case r'userProfileImage.update': return Permission.userProfileImagePeriodUpdate; case r'userProfileImage.delete': return Permission.userProfileImagePeriodDelete; + case r'workflow.create': return Permission.workflowPeriodCreate; + case r'workflow.read': return Permission.workflowPeriodRead; + case r'workflow.update': return Permission.workflowPeriodUpdate; + case r'workflow.delete': return Permission.workflowPeriodDelete; case r'adminUser.create': return Permission.adminUserPeriodCreate; case r'adminUser.read': return Permission.adminUserPeriodRead; case r'adminUser.update': return Permission.adminUserPeriodUpdate; case r'adminUser.delete': return Permission.adminUserPeriodDelete; + case r'adminSession.read': return Permission.adminSessionPeriodRead; case r'adminAuth.unlinkAll': return Permission.adminAuthPeriodUnlinkAll; default: if (!allowNull) { diff --git a/mobile/openapi/lib/model/person_response_dto.dart b/mobile/openapi/lib/model/person_response_dto.dart index c9ebb14c72..a6ad5e0c24 100644 --- a/mobile/openapi/lib/model/person_response_dto.dart +++ b/mobile/openapi/lib/model/person_response_dto.dart @@ -25,7 +25,6 @@ class PersonResponseDto { DateTime? birthDate; - /// This property was added in v1.126.0 /// /// Please note: This property should have been non-nullable! Since the specification file /// does not include a default value (using the "default:" property), however, the generated @@ -36,7 +35,6 @@ class PersonResponseDto { String id; - /// This property was added in v1.126.0 /// /// Please note: This property should have been non-nullable! Since the specification file /// does not include a default value (using the "default:" property), however, the generated @@ -51,7 +49,6 @@ class PersonResponseDto { String thumbnailPath; - /// This property was added in v1.107.0 /// /// Please note: This property should have been non-nullable! Since the specification file /// does not include a default value (using the "default:" property), however, the generated diff --git a/mobile/openapi/lib/model/person_with_faces_response_dto.dart b/mobile/openapi/lib/model/person_with_faces_response_dto.dart index 0bd38b0870..9b2e40cf56 100644 --- a/mobile/openapi/lib/model/person_with_faces_response_dto.dart +++ b/mobile/openapi/lib/model/person_with_faces_response_dto.dart @@ -26,7 +26,6 @@ class PersonWithFacesResponseDto { DateTime? birthDate; - /// This property was added in v1.126.0 /// /// Please note: This property should have been non-nullable! Since the specification file /// does not include a default value (using the "default:" property), however, the generated @@ -39,7 +38,6 @@ class PersonWithFacesResponseDto { String id; - /// This property was added in v1.126.0 /// /// Please note: This property should have been non-nullable! Since the specification file /// does not include a default value (using the "default:" property), however, the generated @@ -54,7 +52,6 @@ class PersonWithFacesResponseDto { String thumbnailPath; - /// This property was added in v1.107.0 /// /// Please note: This property should have been non-nullable! Since the specification file /// does not include a default value (using the "default:" property), however, the generated diff --git a/mobile/openapi/lib/model/plugin_action_response_dto.dart b/mobile/openapi/lib/model/plugin_action_response_dto.dart new file mode 100644 index 0000000000..75b23fc8a4 --- /dev/null +++ b/mobile/openapi/lib/model/plugin_action_response_dto.dart @@ -0,0 +1,151 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PluginActionResponseDto { + /// Returns a new [PluginActionResponseDto] instance. + PluginActionResponseDto({ + required this.description, + required this.id, + required this.methodName, + required this.pluginId, + required this.schema, + this.supportedContexts = const [], + required this.title, + }); + + String description; + + String id; + + String methodName; + + String pluginId; + + Object? schema; + + List supportedContexts; + + String title; + + @override + bool operator ==(Object other) => identical(this, other) || other is PluginActionResponseDto && + other.description == description && + other.id == id && + other.methodName == methodName && + other.pluginId == pluginId && + other.schema == schema && + _deepEquality.equals(other.supportedContexts, supportedContexts) && + other.title == title; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (description.hashCode) + + (id.hashCode) + + (methodName.hashCode) + + (pluginId.hashCode) + + (schema == null ? 0 : schema!.hashCode) + + (supportedContexts.hashCode) + + (title.hashCode); + + @override + String toString() => 'PluginActionResponseDto[description=$description, id=$id, methodName=$methodName, pluginId=$pluginId, schema=$schema, supportedContexts=$supportedContexts, title=$title]'; + + Map toJson() { + final json = {}; + json[r'description'] = this.description; + json[r'id'] = this.id; + json[r'methodName'] = this.methodName; + json[r'pluginId'] = this.pluginId; + if (this.schema != null) { + json[r'schema'] = this.schema; + } else { + // json[r'schema'] = null; + } + json[r'supportedContexts'] = this.supportedContexts; + json[r'title'] = this.title; + return json; + } + + /// Returns a new [PluginActionResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PluginActionResponseDto? fromJson(dynamic value) { + upgradeDto(value, "PluginActionResponseDto"); + if (value is Map) { + final json = value.cast(); + + return PluginActionResponseDto( + description: mapValueOfType(json, r'description')!, + id: mapValueOfType(json, r'id')!, + methodName: mapValueOfType(json, r'methodName')!, + pluginId: mapValueOfType(json, r'pluginId')!, + schema: mapValueOfType(json, r'schema'), + supportedContexts: PluginContext.listFromJson(json[r'supportedContexts']), + title: mapValueOfType(json, r'title')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PluginActionResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PluginActionResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PluginActionResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PluginActionResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'description', + 'id', + 'methodName', + 'pluginId', + 'schema', + 'supportedContexts', + 'title', + }; +} + diff --git a/mobile/openapi/lib/model/plugin_context.dart b/mobile/openapi/lib/model/plugin_context.dart new file mode 100644 index 0000000000..efb701c7d0 --- /dev/null +++ b/mobile/openapi/lib/model/plugin_context.dart @@ -0,0 +1,88 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class PluginContext { + /// Instantiate a new enum with the provided [value]. + const PluginContext._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const asset = PluginContext._(r'asset'); + static const album = PluginContext._(r'album'); + static const person = PluginContext._(r'person'); + + /// List of all possible values in this [enum][PluginContext]. + static const values = [ + asset, + album, + person, + ]; + + static PluginContext? fromJson(dynamic value) => PluginContextTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PluginContext.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [PluginContext] to String, +/// and [decode] dynamic data back to [PluginContext]. +class PluginContextTypeTransformer { + factory PluginContextTypeTransformer() => _instance ??= const PluginContextTypeTransformer._(); + + const PluginContextTypeTransformer._(); + + String encode(PluginContext data) => data.value; + + /// Decodes a [dynamic value][data] to a PluginContext. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + PluginContext? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'asset': return PluginContext.asset; + case r'album': return PluginContext.album; + case r'person': return PluginContext.person; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [PluginContextTypeTransformer] instance. + static PluginContextTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/plugin_filter_response_dto.dart b/mobile/openapi/lib/model/plugin_filter_response_dto.dart new file mode 100644 index 0000000000..8ed6acec78 --- /dev/null +++ b/mobile/openapi/lib/model/plugin_filter_response_dto.dart @@ -0,0 +1,151 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PluginFilterResponseDto { + /// Returns a new [PluginFilterResponseDto] instance. + PluginFilterResponseDto({ + required this.description, + required this.id, + required this.methodName, + required this.pluginId, + required this.schema, + this.supportedContexts = const [], + required this.title, + }); + + String description; + + String id; + + String methodName; + + String pluginId; + + Object? schema; + + List supportedContexts; + + String title; + + @override + bool operator ==(Object other) => identical(this, other) || other is PluginFilterResponseDto && + other.description == description && + other.id == id && + other.methodName == methodName && + other.pluginId == pluginId && + other.schema == schema && + _deepEquality.equals(other.supportedContexts, supportedContexts) && + other.title == title; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (description.hashCode) + + (id.hashCode) + + (methodName.hashCode) + + (pluginId.hashCode) + + (schema == null ? 0 : schema!.hashCode) + + (supportedContexts.hashCode) + + (title.hashCode); + + @override + String toString() => 'PluginFilterResponseDto[description=$description, id=$id, methodName=$methodName, pluginId=$pluginId, schema=$schema, supportedContexts=$supportedContexts, title=$title]'; + + Map toJson() { + final json = {}; + json[r'description'] = this.description; + json[r'id'] = this.id; + json[r'methodName'] = this.methodName; + json[r'pluginId'] = this.pluginId; + if (this.schema != null) { + json[r'schema'] = this.schema; + } else { + // json[r'schema'] = null; + } + json[r'supportedContexts'] = this.supportedContexts; + json[r'title'] = this.title; + return json; + } + + /// Returns a new [PluginFilterResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PluginFilterResponseDto? fromJson(dynamic value) { + upgradeDto(value, "PluginFilterResponseDto"); + if (value is Map) { + final json = value.cast(); + + return PluginFilterResponseDto( + description: mapValueOfType(json, r'description')!, + id: mapValueOfType(json, r'id')!, + methodName: mapValueOfType(json, r'methodName')!, + pluginId: mapValueOfType(json, r'pluginId')!, + schema: mapValueOfType(json, r'schema'), + supportedContexts: PluginContext.listFromJson(json[r'supportedContexts']), + title: mapValueOfType(json, r'title')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PluginFilterResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PluginFilterResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PluginFilterResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PluginFilterResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'description', + 'id', + 'methodName', + 'pluginId', + 'schema', + 'supportedContexts', + 'title', + }; +} + diff --git a/mobile/openapi/lib/model/plugin_response_dto.dart b/mobile/openapi/lib/model/plugin_response_dto.dart new file mode 100644 index 0000000000..afa6f3e1ab --- /dev/null +++ b/mobile/openapi/lib/model/plugin_response_dto.dart @@ -0,0 +1,171 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PluginResponseDto { + /// Returns a new [PluginResponseDto] instance. + PluginResponseDto({ + this.actions = const [], + required this.author, + required this.createdAt, + required this.description, + this.filters = const [], + required this.id, + required this.name, + required this.title, + required this.updatedAt, + required this.version, + }); + + List actions; + + String author; + + String createdAt; + + String description; + + List filters; + + String id; + + String name; + + String title; + + String updatedAt; + + String version; + + @override + bool operator ==(Object other) => identical(this, other) || other is PluginResponseDto && + _deepEquality.equals(other.actions, actions) && + other.author == author && + other.createdAt == createdAt && + other.description == description && + _deepEquality.equals(other.filters, filters) && + other.id == id && + other.name == name && + other.title == title && + other.updatedAt == updatedAt && + other.version == version; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (actions.hashCode) + + (author.hashCode) + + (createdAt.hashCode) + + (description.hashCode) + + (filters.hashCode) + + (id.hashCode) + + (name.hashCode) + + (title.hashCode) + + (updatedAt.hashCode) + + (version.hashCode); + + @override + String toString() => 'PluginResponseDto[actions=$actions, author=$author, createdAt=$createdAt, description=$description, filters=$filters, id=$id, name=$name, title=$title, updatedAt=$updatedAt, version=$version]'; + + Map toJson() { + final json = {}; + json[r'actions'] = this.actions; + json[r'author'] = this.author; + json[r'createdAt'] = this.createdAt; + json[r'description'] = this.description; + json[r'filters'] = this.filters; + json[r'id'] = this.id; + json[r'name'] = this.name; + json[r'title'] = this.title; + json[r'updatedAt'] = this.updatedAt; + json[r'version'] = this.version; + return json; + } + + /// Returns a new [PluginResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PluginResponseDto? fromJson(dynamic value) { + upgradeDto(value, "PluginResponseDto"); + if (value is Map) { + final json = value.cast(); + + return PluginResponseDto( + actions: PluginActionResponseDto.listFromJson(json[r'actions']), + author: mapValueOfType(json, r'author')!, + createdAt: mapValueOfType(json, r'createdAt')!, + description: mapValueOfType(json, r'description')!, + filters: PluginFilterResponseDto.listFromJson(json[r'filters']), + id: mapValueOfType(json, r'id')!, + name: mapValueOfType(json, r'name')!, + title: mapValueOfType(json, r'title')!, + updatedAt: mapValueOfType(json, r'updatedAt')!, + version: mapValueOfType(json, r'version')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PluginResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PluginResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PluginResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PluginResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'actions', + 'author', + 'createdAt', + 'description', + 'filters', + 'id', + 'name', + 'title', + 'updatedAt', + 'version', + }; +} + diff --git a/mobile/openapi/lib/model/plugin_trigger_type.dart b/mobile/openapi/lib/model/plugin_trigger_type.dart new file mode 100644 index 0000000000..b200f1b9e6 --- /dev/null +++ b/mobile/openapi/lib/model/plugin_trigger_type.dart @@ -0,0 +1,85 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class PluginTriggerType { + /// Instantiate a new enum with the provided [value]. + const PluginTriggerType._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const assetCreate = PluginTriggerType._(r'AssetCreate'); + static const personRecognized = PluginTriggerType._(r'PersonRecognized'); + + /// List of all possible values in this [enum][PluginTriggerType]. + static const values = [ + assetCreate, + personRecognized, + ]; + + static PluginTriggerType? fromJson(dynamic value) => PluginTriggerTypeTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PluginTriggerType.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [PluginTriggerType] to String, +/// and [decode] dynamic data back to [PluginTriggerType]. +class PluginTriggerTypeTypeTransformer { + factory PluginTriggerTypeTypeTransformer() => _instance ??= const PluginTriggerTypeTypeTransformer._(); + + const PluginTriggerTypeTypeTransformer._(); + + String encode(PluginTriggerType data) => data.value; + + /// Decodes a [dynamic value][data] to a PluginTriggerType. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + PluginTriggerType? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'AssetCreate': return PluginTriggerType.assetCreate; + case r'PersonRecognized': return PluginTriggerType.personRecognized; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [PluginTriggerTypeTypeTransformer] instance. + static PluginTriggerTypeTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/queue_command.dart b/mobile/openapi/lib/model/queue_command.dart new file mode 100644 index 0000000000..f03ec6eccd --- /dev/null +++ b/mobile/openapi/lib/model/queue_command.dart @@ -0,0 +1,94 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class QueueCommand { + /// Instantiate a new enum with the provided [value]. + const QueueCommand._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const start = QueueCommand._(r'start'); + static const pause = QueueCommand._(r'pause'); + static const resume = QueueCommand._(r'resume'); + static const empty = QueueCommand._(r'empty'); + static const clearFailed = QueueCommand._(r'clear-failed'); + + /// List of all possible values in this [enum][QueueCommand]. + static const values = [ + start, + pause, + resume, + empty, + clearFailed, + ]; + + static QueueCommand? fromJson(dynamic value) => QueueCommandTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = QueueCommand.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [QueueCommand] to String, +/// and [decode] dynamic data back to [QueueCommand]. +class QueueCommandTypeTransformer { + factory QueueCommandTypeTransformer() => _instance ??= const QueueCommandTypeTransformer._(); + + const QueueCommandTypeTransformer._(); + + String encode(QueueCommand data) => data.value; + + /// Decodes a [dynamic value][data] to a QueueCommand. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + QueueCommand? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'start': return QueueCommand.start; + case r'pause': return QueueCommand.pause; + case r'resume': return QueueCommand.resume; + case r'empty': return QueueCommand.empty; + case r'clear-failed': return QueueCommand.clearFailed; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [QueueCommandTypeTransformer] instance. + static QueueCommandTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/job_command_dto.dart b/mobile/openapi/lib/model/queue_command_dto.dart similarity index 66% rename from mobile/openapi/lib/model/job_command_dto.dart rename to mobile/openapi/lib/model/queue_command_dto.dart index 32274037f6..ded848c12f 100644 --- a/mobile/openapi/lib/model/job_command_dto.dart +++ b/mobile/openapi/lib/model/queue_command_dto.dart @@ -10,14 +10,14 @@ part of openapi.api; -class JobCommandDto { - /// Returns a new [JobCommandDto] instance. - JobCommandDto({ +class QueueCommandDto { + /// Returns a new [QueueCommandDto] instance. + QueueCommandDto({ required this.command, this.force, }); - JobCommand command; + QueueCommand command; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -28,7 +28,7 @@ class JobCommandDto { bool? force; @override - bool operator ==(Object other) => identical(this, other) || other is JobCommandDto && + bool operator ==(Object other) => identical(this, other) || other is QueueCommandDto && other.command == command && other.force == force; @@ -39,7 +39,7 @@ class JobCommandDto { (force == null ? 0 : force!.hashCode); @override - String toString() => 'JobCommandDto[command=$command, force=$force]'; + String toString() => 'QueueCommandDto[command=$command, force=$force]'; Map toJson() { final json = {}; @@ -52,27 +52,27 @@ class JobCommandDto { return json; } - /// Returns a new [JobCommandDto] instance and imports its values from + /// Returns a new [QueueCommandDto] instance and imports its values from /// [value] if it's a [Map], null otherwise. // ignore: prefer_constructors_over_static_methods - static JobCommandDto? fromJson(dynamic value) { - upgradeDto(value, "JobCommandDto"); + static QueueCommandDto? fromJson(dynamic value) { + upgradeDto(value, "QueueCommandDto"); if (value is Map) { final json = value.cast(); - return JobCommandDto( - command: JobCommand.fromJson(json[r'command'])!, + return QueueCommandDto( + command: QueueCommand.fromJson(json[r'command'])!, force: mapValueOfType(json, r'force'), ); } return null; } - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; if (json is List && json.isNotEmpty) { for (final row in json) { - final value = JobCommandDto.fromJson(row); + final value = QueueCommandDto.fromJson(row); if (value != null) { result.add(value); } @@ -81,12 +81,12 @@ class JobCommandDto { return result.toList(growable: growable); } - static Map mapFromJson(dynamic json) { - final map = {}; + static Map mapFromJson(dynamic json) { + final map = {}; if (json is Map && json.isNotEmpty) { json = json.cast(); // ignore: parameter_assignments for (final entry in json.entries) { - final value = JobCommandDto.fromJson(entry.value); + final value = QueueCommandDto.fromJson(entry.value); if (value != null) { map[entry.key] = value; } @@ -95,14 +95,14 @@ class JobCommandDto { return map; } - // maps a json object with a list of JobCommandDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; + // maps a json object with a list of QueueCommandDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; if (json is Map && json.isNotEmpty) { // ignore: parameter_assignments json = json.cast(); for (final entry in json.entries) { - map[entry.key] = JobCommandDto.listFromJson(entry.value, growable: growable,); + map[entry.key] = QueueCommandDto.listFromJson(entry.value, growable: growable,); } } return map; diff --git a/mobile/openapi/lib/model/queue_name.dart b/mobile/openapi/lib/model/queue_name.dart new file mode 100644 index 0000000000..bcc4159fce --- /dev/null +++ b/mobile/openapi/lib/model/queue_name.dart @@ -0,0 +1,130 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class QueueName { + /// Instantiate a new enum with the provided [value]. + const QueueName._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const thumbnailGeneration = QueueName._(r'thumbnailGeneration'); + static const metadataExtraction = QueueName._(r'metadataExtraction'); + static const videoConversion = QueueName._(r'videoConversion'); + static const faceDetection = QueueName._(r'faceDetection'); + static const facialRecognition = QueueName._(r'facialRecognition'); + static const smartSearch = QueueName._(r'smartSearch'); + static const duplicateDetection = QueueName._(r'duplicateDetection'); + static const backgroundTask = QueueName._(r'backgroundTask'); + static const storageTemplateMigration = QueueName._(r'storageTemplateMigration'); + static const migration = QueueName._(r'migration'); + static const search = QueueName._(r'search'); + static const sidecar = QueueName._(r'sidecar'); + static const library_ = QueueName._(r'library'); + static const notifications = QueueName._(r'notifications'); + static const backupDatabase = QueueName._(r'backupDatabase'); + static const ocr = QueueName._(r'ocr'); + static const workflow = QueueName._(r'workflow'); + + /// List of all possible values in this [enum][QueueName]. + static const values = [ + thumbnailGeneration, + metadataExtraction, + videoConversion, + faceDetection, + facialRecognition, + smartSearch, + duplicateDetection, + backgroundTask, + storageTemplateMigration, + migration, + search, + sidecar, + library_, + notifications, + backupDatabase, + ocr, + workflow, + ]; + + static QueueName? fromJson(dynamic value) => QueueNameTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = QueueName.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [QueueName] to String, +/// and [decode] dynamic data back to [QueueName]. +class QueueNameTypeTransformer { + factory QueueNameTypeTransformer() => _instance ??= const QueueNameTypeTransformer._(); + + const QueueNameTypeTransformer._(); + + String encode(QueueName data) => data.value; + + /// Decodes a [dynamic value][data] to a QueueName. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + QueueName? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'thumbnailGeneration': return QueueName.thumbnailGeneration; + case r'metadataExtraction': return QueueName.metadataExtraction; + case r'videoConversion': return QueueName.videoConversion; + case r'faceDetection': return QueueName.faceDetection; + case r'facialRecognition': return QueueName.facialRecognition; + case r'smartSearch': return QueueName.smartSearch; + case r'duplicateDetection': return QueueName.duplicateDetection; + case r'backgroundTask': return QueueName.backgroundTask; + case r'storageTemplateMigration': return QueueName.storageTemplateMigration; + case r'migration': return QueueName.migration; + case r'search': return QueueName.search; + case r'sidecar': return QueueName.sidecar; + case r'library': return QueueName.library_; + case r'notifications': return QueueName.notifications; + case r'backupDatabase': return QueueName.backupDatabase; + case r'ocr': return QueueName.ocr; + case r'workflow': return QueueName.workflow; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [QueueNameTypeTransformer] instance. + static QueueNameTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/job_status_dto.dart b/mobile/openapi/lib/model/queue_response_dto.dart similarity index 62% rename from mobile/openapi/lib/model/job_status_dto.dart rename to mobile/openapi/lib/model/queue_response_dto.dart index 18fab8dfb3..b20449f721 100644 --- a/mobile/openapi/lib/model/job_status_dto.dart +++ b/mobile/openapi/lib/model/queue_response_dto.dart @@ -10,19 +10,19 @@ part of openapi.api; -class JobStatusDto { - /// Returns a new [JobStatusDto] instance. - JobStatusDto({ +class QueueResponseDto { + /// Returns a new [QueueResponseDto] instance. + QueueResponseDto({ required this.jobCounts, required this.queueStatus, }); - JobCountsDto jobCounts; + QueueStatisticsDto jobCounts; QueueStatusDto queueStatus; @override - bool operator ==(Object other) => identical(this, other) || other is JobStatusDto && + bool operator ==(Object other) => identical(this, other) || other is QueueResponseDto && other.jobCounts == jobCounts && other.queueStatus == queueStatus; @@ -33,7 +33,7 @@ class JobStatusDto { (queueStatus.hashCode); @override - String toString() => 'JobStatusDto[jobCounts=$jobCounts, queueStatus=$queueStatus]'; + String toString() => 'QueueResponseDto[jobCounts=$jobCounts, queueStatus=$queueStatus]'; Map toJson() { final json = {}; @@ -42,27 +42,27 @@ class JobStatusDto { return json; } - /// Returns a new [JobStatusDto] instance and imports its values from + /// Returns a new [QueueResponseDto] instance and imports its values from /// [value] if it's a [Map], null otherwise. // ignore: prefer_constructors_over_static_methods - static JobStatusDto? fromJson(dynamic value) { - upgradeDto(value, "JobStatusDto"); + static QueueResponseDto? fromJson(dynamic value) { + upgradeDto(value, "QueueResponseDto"); if (value is Map) { final json = value.cast(); - return JobStatusDto( - jobCounts: JobCountsDto.fromJson(json[r'jobCounts'])!, + return QueueResponseDto( + jobCounts: QueueStatisticsDto.fromJson(json[r'jobCounts'])!, queueStatus: QueueStatusDto.fromJson(json[r'queueStatus'])!, ); } return null; } - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; if (json is List && json.isNotEmpty) { for (final row in json) { - final value = JobStatusDto.fromJson(row); + final value = QueueResponseDto.fromJson(row); if (value != null) { result.add(value); } @@ -71,12 +71,12 @@ class JobStatusDto { return result.toList(growable: growable); } - static Map mapFromJson(dynamic json) { - final map = {}; + static Map mapFromJson(dynamic json) { + final map = {}; if (json is Map && json.isNotEmpty) { json = json.cast(); // ignore: parameter_assignments for (final entry in json.entries) { - final value = JobStatusDto.fromJson(entry.value); + final value = QueueResponseDto.fromJson(entry.value); if (value != null) { map[entry.key] = value; } @@ -85,14 +85,14 @@ class JobStatusDto { return map; } - // maps a json object with a list of JobStatusDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; + // maps a json object with a list of QueueResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; if (json is Map && json.isNotEmpty) { // ignore: parameter_assignments json = json.cast(); for (final entry in json.entries) { - map[entry.key] = JobStatusDto.listFromJson(entry.value, growable: growable,); + map[entry.key] = QueueResponseDto.listFromJson(entry.value, growable: growable,); } } return map; diff --git a/mobile/openapi/lib/model/job_counts_dto.dart b/mobile/openapi/lib/model/queue_statistics_dto.dart similarity index 70% rename from mobile/openapi/lib/model/job_counts_dto.dart rename to mobile/openapi/lib/model/queue_statistics_dto.dart index afc90d1084..c27c4a5892 100644 --- a/mobile/openapi/lib/model/job_counts_dto.dart +++ b/mobile/openapi/lib/model/queue_statistics_dto.dart @@ -10,9 +10,9 @@ part of openapi.api; -class JobCountsDto { - /// Returns a new [JobCountsDto] instance. - JobCountsDto({ +class QueueStatisticsDto { + /// Returns a new [QueueStatisticsDto] instance. + QueueStatisticsDto({ required this.active, required this.completed, required this.delayed, @@ -34,7 +34,7 @@ class JobCountsDto { int waiting; @override - bool operator ==(Object other) => identical(this, other) || other is JobCountsDto && + bool operator ==(Object other) => identical(this, other) || other is QueueStatisticsDto && other.active == active && other.completed == completed && other.delayed == delayed && @@ -53,7 +53,7 @@ class JobCountsDto { (waiting.hashCode); @override - String toString() => 'JobCountsDto[active=$active, completed=$completed, delayed=$delayed, failed=$failed, paused=$paused, waiting=$waiting]'; + String toString() => 'QueueStatisticsDto[active=$active, completed=$completed, delayed=$delayed, failed=$failed, paused=$paused, waiting=$waiting]'; Map toJson() { final json = {}; @@ -66,15 +66,15 @@ class JobCountsDto { return json; } - /// Returns a new [JobCountsDto] instance and imports its values from + /// Returns a new [QueueStatisticsDto] instance and imports its values from /// [value] if it's a [Map], null otherwise. // ignore: prefer_constructors_over_static_methods - static JobCountsDto? fromJson(dynamic value) { - upgradeDto(value, "JobCountsDto"); + static QueueStatisticsDto? fromJson(dynamic value) { + upgradeDto(value, "QueueStatisticsDto"); if (value is Map) { final json = value.cast(); - return JobCountsDto( + return QueueStatisticsDto( active: mapValueOfType(json, r'active')!, completed: mapValueOfType(json, r'completed')!, delayed: mapValueOfType(json, r'delayed')!, @@ -86,11 +86,11 @@ class JobCountsDto { return null; } - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; if (json is List && json.isNotEmpty) { for (final row in json) { - final value = JobCountsDto.fromJson(row); + final value = QueueStatisticsDto.fromJson(row); if (value != null) { result.add(value); } @@ -99,12 +99,12 @@ class JobCountsDto { return result.toList(growable: growable); } - static Map mapFromJson(dynamic json) { - final map = {}; + static Map mapFromJson(dynamic json) { + final map = {}; if (json is Map && json.isNotEmpty) { json = json.cast(); // ignore: parameter_assignments for (final entry in json.entries) { - final value = JobCountsDto.fromJson(entry.value); + final value = QueueStatisticsDto.fromJson(entry.value); if (value != null) { map[entry.key] = value; } @@ -113,14 +113,14 @@ class JobCountsDto { return map; } - // maps a json object with a list of JobCountsDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; + // maps a json object with a list of QueueStatisticsDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; if (json is Map && json.isNotEmpty) { // ignore: parameter_assignments json = json.cast(); for (final entry in json.entries) { - map[entry.key] = JobCountsDto.listFromJson(entry.value, growable: growable,); + map[entry.key] = QueueStatisticsDto.listFromJson(entry.value, growable: growable,); } } return map; diff --git a/mobile/openapi/lib/model/all_job_status_response_dto.dart b/mobile/openapi/lib/model/queues_response_dto.dart similarity index 53% rename from mobile/openapi/lib/model/all_job_status_response_dto.dart rename to mobile/openapi/lib/model/queues_response_dto.dart index 787d02dd0e..be40a56fb1 100644 --- a/mobile/openapi/lib/model/all_job_status_response_dto.dart +++ b/mobile/openapi/lib/model/queues_response_dto.dart @@ -10,9 +10,9 @@ part of openapi.api; -class AllJobStatusResponseDto { - /// Returns a new [AllJobStatusResponseDto] instance. - AllJobStatusResponseDto({ +class QueuesResponseDto { + /// Returns a new [QueuesResponseDto] instance. + QueuesResponseDto({ required this.backgroundTask, required this.backupDatabase, required this.duplicateDetection, @@ -22,46 +22,52 @@ class AllJobStatusResponseDto { required this.metadataExtraction, required this.migration, required this.notifications, + required this.ocr, required this.search, required this.sidecar, required this.smartSearch, required this.storageTemplateMigration, required this.thumbnailGeneration, required this.videoConversion, + required this.workflow, }); - JobStatusDto backgroundTask; + QueueResponseDto backgroundTask; - JobStatusDto backupDatabase; + QueueResponseDto backupDatabase; - JobStatusDto duplicateDetection; + QueueResponseDto duplicateDetection; - JobStatusDto faceDetection; + QueueResponseDto faceDetection; - JobStatusDto facialRecognition; + QueueResponseDto facialRecognition; - JobStatusDto library_; + QueueResponseDto library_; - JobStatusDto metadataExtraction; + QueueResponseDto metadataExtraction; - JobStatusDto migration; + QueueResponseDto migration; - JobStatusDto notifications; + QueueResponseDto notifications; - JobStatusDto search; + QueueResponseDto ocr; - JobStatusDto sidecar; + QueueResponseDto search; - JobStatusDto smartSearch; + QueueResponseDto sidecar; - JobStatusDto storageTemplateMigration; + QueueResponseDto smartSearch; - JobStatusDto thumbnailGeneration; + QueueResponseDto storageTemplateMigration; - JobStatusDto videoConversion; + QueueResponseDto thumbnailGeneration; + + QueueResponseDto videoConversion; + + QueueResponseDto workflow; @override - bool operator ==(Object other) => identical(this, other) || other is AllJobStatusResponseDto && + bool operator ==(Object other) => identical(this, other) || other is QueuesResponseDto && other.backgroundTask == backgroundTask && other.backupDatabase == backupDatabase && other.duplicateDetection == duplicateDetection && @@ -71,12 +77,14 @@ class AllJobStatusResponseDto { other.metadataExtraction == metadataExtraction && other.migration == migration && other.notifications == notifications && + other.ocr == ocr && other.search == search && other.sidecar == sidecar && other.smartSearch == smartSearch && other.storageTemplateMigration == storageTemplateMigration && other.thumbnailGeneration == thumbnailGeneration && - other.videoConversion == videoConversion; + other.videoConversion == videoConversion && + other.workflow == workflow; @override int get hashCode => @@ -90,15 +98,17 @@ class AllJobStatusResponseDto { (metadataExtraction.hashCode) + (migration.hashCode) + (notifications.hashCode) + + (ocr.hashCode) + (search.hashCode) + (sidecar.hashCode) + (smartSearch.hashCode) + (storageTemplateMigration.hashCode) + (thumbnailGeneration.hashCode) + - (videoConversion.hashCode); + (videoConversion.hashCode) + + (workflow.hashCode); @override - String toString() => 'AllJobStatusResponseDto[backgroundTask=$backgroundTask, backupDatabase=$backupDatabase, duplicateDetection=$duplicateDetection, faceDetection=$faceDetection, facialRecognition=$facialRecognition, library_=$library_, metadataExtraction=$metadataExtraction, migration=$migration, notifications=$notifications, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, storageTemplateMigration=$storageTemplateMigration, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion]'; + String toString() => 'QueuesResponseDto[backgroundTask=$backgroundTask, backupDatabase=$backupDatabase, duplicateDetection=$duplicateDetection, faceDetection=$faceDetection, facialRecognition=$facialRecognition, library_=$library_, metadataExtraction=$metadataExtraction, migration=$migration, notifications=$notifications, ocr=$ocr, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, storageTemplateMigration=$storageTemplateMigration, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion, workflow=$workflow]'; Map toJson() { final json = {}; @@ -111,49 +121,53 @@ class AllJobStatusResponseDto { json[r'metadataExtraction'] = this.metadataExtraction; json[r'migration'] = this.migration; json[r'notifications'] = this.notifications; + json[r'ocr'] = this.ocr; json[r'search'] = this.search; json[r'sidecar'] = this.sidecar; json[r'smartSearch'] = this.smartSearch; json[r'storageTemplateMigration'] = this.storageTemplateMigration; json[r'thumbnailGeneration'] = this.thumbnailGeneration; json[r'videoConversion'] = this.videoConversion; + json[r'workflow'] = this.workflow; return json; } - /// Returns a new [AllJobStatusResponseDto] instance and imports its values from + /// Returns a new [QueuesResponseDto] instance and imports its values from /// [value] if it's a [Map], null otherwise. // ignore: prefer_constructors_over_static_methods - static AllJobStatusResponseDto? fromJson(dynamic value) { - upgradeDto(value, "AllJobStatusResponseDto"); + static QueuesResponseDto? fromJson(dynamic value) { + upgradeDto(value, "QueuesResponseDto"); if (value is Map) { final json = value.cast(); - return AllJobStatusResponseDto( - backgroundTask: JobStatusDto.fromJson(json[r'backgroundTask'])!, - backupDatabase: JobStatusDto.fromJson(json[r'backupDatabase'])!, - duplicateDetection: JobStatusDto.fromJson(json[r'duplicateDetection'])!, - faceDetection: JobStatusDto.fromJson(json[r'faceDetection'])!, - facialRecognition: JobStatusDto.fromJson(json[r'facialRecognition'])!, - library_: JobStatusDto.fromJson(json[r'library'])!, - metadataExtraction: JobStatusDto.fromJson(json[r'metadataExtraction'])!, - migration: JobStatusDto.fromJson(json[r'migration'])!, - notifications: JobStatusDto.fromJson(json[r'notifications'])!, - search: JobStatusDto.fromJson(json[r'search'])!, - sidecar: JobStatusDto.fromJson(json[r'sidecar'])!, - smartSearch: JobStatusDto.fromJson(json[r'smartSearch'])!, - storageTemplateMigration: JobStatusDto.fromJson(json[r'storageTemplateMigration'])!, - thumbnailGeneration: JobStatusDto.fromJson(json[r'thumbnailGeneration'])!, - videoConversion: JobStatusDto.fromJson(json[r'videoConversion'])!, + return QueuesResponseDto( + backgroundTask: QueueResponseDto.fromJson(json[r'backgroundTask'])!, + backupDatabase: QueueResponseDto.fromJson(json[r'backupDatabase'])!, + duplicateDetection: QueueResponseDto.fromJson(json[r'duplicateDetection'])!, + faceDetection: QueueResponseDto.fromJson(json[r'faceDetection'])!, + facialRecognition: QueueResponseDto.fromJson(json[r'facialRecognition'])!, + library_: QueueResponseDto.fromJson(json[r'library'])!, + metadataExtraction: QueueResponseDto.fromJson(json[r'metadataExtraction'])!, + migration: QueueResponseDto.fromJson(json[r'migration'])!, + notifications: QueueResponseDto.fromJson(json[r'notifications'])!, + ocr: QueueResponseDto.fromJson(json[r'ocr'])!, + search: QueueResponseDto.fromJson(json[r'search'])!, + sidecar: QueueResponseDto.fromJson(json[r'sidecar'])!, + smartSearch: QueueResponseDto.fromJson(json[r'smartSearch'])!, + storageTemplateMigration: QueueResponseDto.fromJson(json[r'storageTemplateMigration'])!, + thumbnailGeneration: QueueResponseDto.fromJson(json[r'thumbnailGeneration'])!, + videoConversion: QueueResponseDto.fromJson(json[r'videoConversion'])!, + workflow: QueueResponseDto.fromJson(json[r'workflow'])!, ); } return null; } - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; if (json is List && json.isNotEmpty) { for (final row in json) { - final value = AllJobStatusResponseDto.fromJson(row); + final value = QueuesResponseDto.fromJson(row); if (value != null) { result.add(value); } @@ -162,12 +176,12 @@ class AllJobStatusResponseDto { return result.toList(growable: growable); } - static Map mapFromJson(dynamic json) { - final map = {}; + static Map mapFromJson(dynamic json) { + final map = {}; if (json is Map && json.isNotEmpty) { json = json.cast(); // ignore: parameter_assignments for (final entry in json.entries) { - final value = AllJobStatusResponseDto.fromJson(entry.value); + final value = QueuesResponseDto.fromJson(entry.value); if (value != null) { map[entry.key] = value; } @@ -176,14 +190,14 @@ class AllJobStatusResponseDto { return map; } - // maps a json object with a list of AllJobStatusResponseDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; + // maps a json object with a list of QueuesResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; if (json is Map && json.isNotEmpty) { // ignore: parameter_assignments json = json.cast(); for (final entry in json.entries) { - map[entry.key] = AllJobStatusResponseDto.listFromJson(entry.value, growable: growable,); + map[entry.key] = QueuesResponseDto.listFromJson(entry.value, growable: growable,); } } return map; @@ -200,12 +214,14 @@ class AllJobStatusResponseDto { 'metadataExtraction', 'migration', 'notifications', + 'ocr', 'search', 'sidecar', 'smartSearch', 'storageTemplateMigration', 'thumbnailGeneration', 'videoConversion', + 'workflow', }; } diff --git a/mobile/openapi/lib/model/random_search_dto.dart b/mobile/openapi/lib/model/random_search_dto.dart index 98cc715af4..96d670fd96 100644 --- a/mobile/openapi/lib/model/random_search_dto.dart +++ b/mobile/openapi/lib/model/random_search_dto.dart @@ -28,6 +28,7 @@ class RandomSearchDto { this.libraryId, this.make, this.model, + this.ocr, this.personIds = const [], this.rating, this.size, @@ -131,6 +132,14 @@ class RandomSearchDto { String? model; + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? ocr; + List personIds; /// Minimum value: -1 @@ -270,6 +279,7 @@ class RandomSearchDto { other.libraryId == libraryId && other.make == make && other.model == model && + other.ocr == ocr && _deepEquality.equals(other.personIds, personIds) && other.rating == rating && other.size == size && @@ -306,6 +316,7 @@ class RandomSearchDto { (libraryId == null ? 0 : libraryId!.hashCode) + (make == null ? 0 : make!.hashCode) + (model == null ? 0 : model!.hashCode) + + (ocr == null ? 0 : ocr!.hashCode) + (personIds.hashCode) + (rating == null ? 0 : rating!.hashCode) + (size == null ? 0 : size!.hashCode) + @@ -325,7 +336,7 @@ class RandomSearchDto { (withStacked == null ? 0 : withStacked!.hashCode); @override - String toString() => 'RandomSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceId=$deviceId, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, personIds=$personIds, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]'; + String toString() => 'RandomSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceId=$deviceId, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, ocr=$ocr, personIds=$personIds, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]'; Map toJson() { final json = {}; @@ -399,6 +410,11 @@ class RandomSearchDto { json[r'model'] = this.model; } else { // json[r'model'] = null; + } + if (this.ocr != null) { + json[r'ocr'] = this.ocr; + } else { + // json[r'ocr'] = null; } json[r'personIds'] = this.personIds; if (this.rating != null) { @@ -510,6 +526,7 @@ class RandomSearchDto { libraryId: mapValueOfType(json, r'libraryId'), make: mapValueOfType(json, r'make'), model: mapValueOfType(json, r'model'), + ocr: mapValueOfType(json, r'ocr'), personIds: json[r'personIds'] is Iterable ? (json[r'personIds'] as Iterable).cast().toList(growable: false) : const [], diff --git a/mobile/openapi/lib/model/search_suggestion_type.dart b/mobile/openapi/lib/model/search_suggestion_type.dart index 3f905e029d..b18fe687c4 100644 --- a/mobile/openapi/lib/model/search_suggestion_type.dart +++ b/mobile/openapi/lib/model/search_suggestion_type.dart @@ -28,6 +28,7 @@ class SearchSuggestionType { static const city = SearchSuggestionType._(r'city'); static const cameraMake = SearchSuggestionType._(r'camera-make'); static const cameraModel = SearchSuggestionType._(r'camera-model'); + static const cameraLensModel = SearchSuggestionType._(r'camera-lens-model'); /// List of all possible values in this [enum][SearchSuggestionType]. static const values = [ @@ -36,6 +37,7 @@ class SearchSuggestionType { city, cameraMake, cameraModel, + cameraLensModel, ]; static SearchSuggestionType? fromJson(dynamic value) => SearchSuggestionTypeTypeTransformer().decode(value); @@ -79,6 +81,7 @@ class SearchSuggestionTypeTypeTransformer { case r'city': return SearchSuggestionType.city; case r'camera-make': return SearchSuggestionType.cameraMake; case r'camera-model': return SearchSuggestionType.cameraModel; + case r'camera-lens-model': return SearchSuggestionType.cameraLensModel; default: if (!allowNull) { throw ArgumentError('Unknown enum value to decode: $data'); diff --git a/mobile/openapi/lib/model/server_features_dto.dart b/mobile/openapi/lib/model/server_features_dto.dart index 5149c3796a..7b5980ca13 100644 --- a/mobile/openapi/lib/model/server_features_dto.dart +++ b/mobile/openapi/lib/model/server_features_dto.dart @@ -21,6 +21,7 @@ class ServerFeaturesDto { required this.map, required this.oauth, required this.oauthAutoLaunch, + required this.ocr, required this.passwordLogin, required this.reverseGeocoding, required this.search, @@ -45,6 +46,8 @@ class ServerFeaturesDto { bool oauthAutoLaunch; + bool ocr; + bool passwordLogin; bool reverseGeocoding; @@ -67,6 +70,7 @@ class ServerFeaturesDto { other.map == map && other.oauth == oauth && other.oauthAutoLaunch == oauthAutoLaunch && + other.ocr == ocr && other.passwordLogin == passwordLogin && other.reverseGeocoding == reverseGeocoding && other.search == search && @@ -85,6 +89,7 @@ class ServerFeaturesDto { (map.hashCode) + (oauth.hashCode) + (oauthAutoLaunch.hashCode) + + (ocr.hashCode) + (passwordLogin.hashCode) + (reverseGeocoding.hashCode) + (search.hashCode) + @@ -93,7 +98,7 @@ class ServerFeaturesDto { (trash.hashCode); @override - String toString() => 'ServerFeaturesDto[configFile=$configFile, duplicateDetection=$duplicateDetection, email=$email, facialRecognition=$facialRecognition, importFaces=$importFaces, map=$map, oauth=$oauth, oauthAutoLaunch=$oauthAutoLaunch, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, trash=$trash]'; + String toString() => 'ServerFeaturesDto[configFile=$configFile, duplicateDetection=$duplicateDetection, email=$email, facialRecognition=$facialRecognition, importFaces=$importFaces, map=$map, oauth=$oauth, oauthAutoLaunch=$oauthAutoLaunch, ocr=$ocr, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, trash=$trash]'; Map toJson() { final json = {}; @@ -105,6 +110,7 @@ class ServerFeaturesDto { json[r'map'] = this.map; json[r'oauth'] = this.oauth; json[r'oauthAutoLaunch'] = this.oauthAutoLaunch; + json[r'ocr'] = this.ocr; json[r'passwordLogin'] = this.passwordLogin; json[r'reverseGeocoding'] = this.reverseGeocoding; json[r'search'] = this.search; @@ -131,6 +137,7 @@ class ServerFeaturesDto { map: mapValueOfType(json, r'map')!, oauth: mapValueOfType(json, r'oauth')!, oauthAutoLaunch: mapValueOfType(json, r'oauthAutoLaunch')!, + ocr: mapValueOfType(json, r'ocr')!, passwordLogin: mapValueOfType(json, r'passwordLogin')!, reverseGeocoding: mapValueOfType(json, r'reverseGeocoding')!, search: mapValueOfType(json, r'search')!, @@ -192,6 +199,7 @@ class ServerFeaturesDto { 'map', 'oauth', 'oauthAutoLaunch', + 'ocr', 'passwordLogin', 'reverseGeocoding', 'search', diff --git a/mobile/openapi/lib/model/session_create_response_dto.dart b/mobile/openapi/lib/model/session_create_response_dto.dart index a4f93e8d9c..e16597f3b5 100644 --- a/mobile/openapi/lib/model/session_create_response_dto.dart +++ b/mobile/openapi/lib/model/session_create_response_dto.dart @@ -13,6 +13,7 @@ part of openapi.api; class SessionCreateResponseDto { /// Returns a new [SessionCreateResponseDto] instance. SessionCreateResponseDto({ + required this.appVersion, required this.createdAt, required this.current, required this.deviceOS, @@ -24,6 +25,8 @@ class SessionCreateResponseDto { required this.updatedAt, }); + String? appVersion; + String createdAt; bool current; @@ -50,6 +53,7 @@ class SessionCreateResponseDto { @override bool operator ==(Object other) => identical(this, other) || other is SessionCreateResponseDto && + other.appVersion == appVersion && other.createdAt == createdAt && other.current == current && other.deviceOS == deviceOS && @@ -63,6 +67,7 @@ class SessionCreateResponseDto { @override int get hashCode => // ignore: unnecessary_parenthesis + (appVersion == null ? 0 : appVersion!.hashCode) + (createdAt.hashCode) + (current.hashCode) + (deviceOS.hashCode) + @@ -74,10 +79,15 @@ class SessionCreateResponseDto { (updatedAt.hashCode); @override - String toString() => 'SessionCreateResponseDto[createdAt=$createdAt, current=$current, deviceOS=$deviceOS, deviceType=$deviceType, expiresAt=$expiresAt, id=$id, isPendingSyncReset=$isPendingSyncReset, token=$token, updatedAt=$updatedAt]'; + String toString() => 'SessionCreateResponseDto[appVersion=$appVersion, createdAt=$createdAt, current=$current, deviceOS=$deviceOS, deviceType=$deviceType, expiresAt=$expiresAt, id=$id, isPendingSyncReset=$isPendingSyncReset, token=$token, updatedAt=$updatedAt]'; Map toJson() { final json = {}; + if (this.appVersion != null) { + json[r'appVersion'] = this.appVersion; + } else { + // json[r'appVersion'] = null; + } json[r'createdAt'] = this.createdAt; json[r'current'] = this.current; json[r'deviceOS'] = this.deviceOS; @@ -103,6 +113,7 @@ class SessionCreateResponseDto { final json = value.cast(); return SessionCreateResponseDto( + appVersion: mapValueOfType(json, r'appVersion'), createdAt: mapValueOfType(json, r'createdAt')!, current: mapValueOfType(json, r'current')!, deviceOS: mapValueOfType(json, r'deviceOS')!, @@ -159,6 +170,7 @@ class SessionCreateResponseDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { + 'appVersion', 'createdAt', 'current', 'deviceOS', diff --git a/mobile/openapi/lib/model/session_response_dto.dart b/mobile/openapi/lib/model/session_response_dto.dart index e76e4d48b4..85acb8a358 100644 --- a/mobile/openapi/lib/model/session_response_dto.dart +++ b/mobile/openapi/lib/model/session_response_dto.dart @@ -13,6 +13,7 @@ part of openapi.api; class SessionResponseDto { /// Returns a new [SessionResponseDto] instance. SessionResponseDto({ + required this.appVersion, required this.createdAt, required this.current, required this.deviceOS, @@ -23,6 +24,8 @@ class SessionResponseDto { required this.updatedAt, }); + String? appVersion; + String createdAt; bool current; @@ -47,6 +50,7 @@ class SessionResponseDto { @override bool operator ==(Object other) => identical(this, other) || other is SessionResponseDto && + other.appVersion == appVersion && other.createdAt == createdAt && other.current == current && other.deviceOS == deviceOS && @@ -59,6 +63,7 @@ class SessionResponseDto { @override int get hashCode => // ignore: unnecessary_parenthesis + (appVersion == null ? 0 : appVersion!.hashCode) + (createdAt.hashCode) + (current.hashCode) + (deviceOS.hashCode) + @@ -69,10 +74,15 @@ class SessionResponseDto { (updatedAt.hashCode); @override - String toString() => 'SessionResponseDto[createdAt=$createdAt, current=$current, deviceOS=$deviceOS, deviceType=$deviceType, expiresAt=$expiresAt, id=$id, isPendingSyncReset=$isPendingSyncReset, updatedAt=$updatedAt]'; + String toString() => 'SessionResponseDto[appVersion=$appVersion, createdAt=$createdAt, current=$current, deviceOS=$deviceOS, deviceType=$deviceType, expiresAt=$expiresAt, id=$id, isPendingSyncReset=$isPendingSyncReset, updatedAt=$updatedAt]'; Map toJson() { final json = {}; + if (this.appVersion != null) { + json[r'appVersion'] = this.appVersion; + } else { + // json[r'appVersion'] = null; + } json[r'createdAt'] = this.createdAt; json[r'current'] = this.current; json[r'deviceOS'] = this.deviceOS; @@ -97,6 +107,7 @@ class SessionResponseDto { final json = value.cast(); return SessionResponseDto( + appVersion: mapValueOfType(json, r'appVersion'), createdAt: mapValueOfType(json, r'createdAt')!, current: mapValueOfType(json, r'current')!, deviceOS: mapValueOfType(json, r'deviceOS')!, @@ -152,6 +163,7 @@ class SessionResponseDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { + 'appVersion', 'createdAt', 'current', 'deviceOS', diff --git a/mobile/openapi/lib/model/smart_search_dto.dart b/mobile/openapi/lib/model/smart_search_dto.dart index 90902b9791..24f040a92b 100644 --- a/mobile/openapi/lib/model/smart_search_dto.dart +++ b/mobile/openapi/lib/model/smart_search_dto.dart @@ -29,6 +29,7 @@ class SmartSearchDto { this.libraryId, this.make, this.model, + this.ocr, this.page, this.personIds = const [], this.query, @@ -141,6 +142,14 @@ class SmartSearchDto { String? model; + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? ocr; + /// Minimum value: 1 /// /// Please note: This property should have been non-nullable! Since the specification file @@ -290,6 +299,7 @@ class SmartSearchDto { other.libraryId == libraryId && other.make == make && other.model == model && + other.ocr == ocr && other.page == page && _deepEquality.equals(other.personIds, personIds) && other.query == query && @@ -328,6 +338,7 @@ class SmartSearchDto { (libraryId == null ? 0 : libraryId!.hashCode) + (make == null ? 0 : make!.hashCode) + (model == null ? 0 : model!.hashCode) + + (ocr == null ? 0 : ocr!.hashCode) + (page == null ? 0 : page!.hashCode) + (personIds.hashCode) + (query == null ? 0 : query!.hashCode) + @@ -348,7 +359,7 @@ class SmartSearchDto { (withExif == null ? 0 : withExif!.hashCode); @override - String toString() => 'SmartSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceId=$deviceId, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, language=$language, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, page=$page, personIds=$personIds, query=$query, queryAssetId=$queryAssetId, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif]'; + String toString() => 'SmartSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceId=$deviceId, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, language=$language, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, ocr=$ocr, page=$page, personIds=$personIds, query=$query, queryAssetId=$queryAssetId, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif]'; Map toJson() { final json = {}; @@ -428,6 +439,11 @@ class SmartSearchDto { } else { // json[r'model'] = null; } + if (this.ocr != null) { + json[r'ocr'] = this.ocr; + } else { + // json[r'ocr'] = null; + } if (this.page != null) { json[r'page'] = this.page; } else { @@ -544,6 +560,7 @@ class SmartSearchDto { libraryId: mapValueOfType(json, r'libraryId'), make: mapValueOfType(json, r'make'), model: mapValueOfType(json, r'model'), + ocr: mapValueOfType(json, r'ocr'), page: num.parse('${json[r'page']}'), personIds: json[r'personIds'] is Iterable ? (json[r'personIds'] as Iterable).cast().toList(growable: false) diff --git a/mobile/openapi/lib/model/statistics_search_dto.dart b/mobile/openapi/lib/model/statistics_search_dto.dart index 73d80c9e36..e0965352e0 100644 --- a/mobile/openapi/lib/model/statistics_search_dto.dart +++ b/mobile/openapi/lib/model/statistics_search_dto.dart @@ -29,6 +29,7 @@ class StatisticsSearchDto { this.libraryId, this.make, this.model, + this.ocr, this.personIds = const [], this.rating, this.state, @@ -135,6 +136,14 @@ class StatisticsSearchDto { String? model; + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? ocr; + List personIds; /// Minimum value: -1 @@ -233,6 +242,7 @@ class StatisticsSearchDto { other.libraryId == libraryId && other.make == make && other.model == model && + other.ocr == ocr && _deepEquality.equals(other.personIds, personIds) && other.rating == rating && other.state == state && @@ -265,6 +275,7 @@ class StatisticsSearchDto { (libraryId == null ? 0 : libraryId!.hashCode) + (make == null ? 0 : make!.hashCode) + (model == null ? 0 : model!.hashCode) + + (ocr == null ? 0 : ocr!.hashCode) + (personIds.hashCode) + (rating == null ? 0 : rating!.hashCode) + (state == null ? 0 : state!.hashCode) + @@ -279,7 +290,7 @@ class StatisticsSearchDto { (visibility == null ? 0 : visibility!.hashCode); @override - String toString() => 'StatisticsSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, description=$description, deviceId=$deviceId, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, personIds=$personIds, rating=$rating, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility]'; + String toString() => 'StatisticsSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, description=$description, deviceId=$deviceId, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, ocr=$ocr, personIds=$personIds, rating=$rating, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility]'; Map toJson() { final json = {}; @@ -358,6 +369,11 @@ class StatisticsSearchDto { json[r'model'] = this.model; } else { // json[r'model'] = null; + } + if (this.ocr != null) { + json[r'ocr'] = this.ocr; + } else { + // json[r'ocr'] = null; } json[r'personIds'] = this.personIds; if (this.rating != null) { @@ -445,6 +461,7 @@ class StatisticsSearchDto { libraryId: mapValueOfType(json, r'libraryId'), make: mapValueOfType(json, r'make'), model: mapValueOfType(json, r'model'), + ocr: mapValueOfType(json, r'ocr'), personIds: json[r'personIds'] is Iterable ? (json[r'personIds'] as Iterable).cast().toList(growable: false) : const [], diff --git a/mobile/openapi/lib/model/system_config_job_dto.dart b/mobile/openapi/lib/model/system_config_job_dto.dart index c0fed5cccc..461420b3e3 100644 --- a/mobile/openapi/lib/model/system_config_job_dto.dart +++ b/mobile/openapi/lib/model/system_config_job_dto.dart @@ -19,11 +19,13 @@ class SystemConfigJobDto { required this.metadataExtraction, required this.migration, required this.notifications, + required this.ocr, required this.search, required this.sidecar, required this.smartSearch, required this.thumbnailGeneration, required this.videoConversion, + required this.workflow, }); JobSettingsDto backgroundTask; @@ -38,6 +40,8 @@ class SystemConfigJobDto { JobSettingsDto notifications; + JobSettingsDto ocr; + JobSettingsDto search; JobSettingsDto sidecar; @@ -48,6 +52,8 @@ class SystemConfigJobDto { JobSettingsDto videoConversion; + JobSettingsDto workflow; + @override bool operator ==(Object other) => identical(this, other) || other is SystemConfigJobDto && other.backgroundTask == backgroundTask && @@ -56,11 +62,13 @@ class SystemConfigJobDto { other.metadataExtraction == metadataExtraction && other.migration == migration && other.notifications == notifications && + other.ocr == ocr && other.search == search && other.sidecar == sidecar && other.smartSearch == smartSearch && other.thumbnailGeneration == thumbnailGeneration && - other.videoConversion == videoConversion; + other.videoConversion == videoConversion && + other.workflow == workflow; @override int get hashCode => @@ -71,14 +79,16 @@ class SystemConfigJobDto { (metadataExtraction.hashCode) + (migration.hashCode) + (notifications.hashCode) + + (ocr.hashCode) + (search.hashCode) + (sidecar.hashCode) + (smartSearch.hashCode) + (thumbnailGeneration.hashCode) + - (videoConversion.hashCode); + (videoConversion.hashCode) + + (workflow.hashCode); @override - String toString() => 'SystemConfigJobDto[backgroundTask=$backgroundTask, faceDetection=$faceDetection, library_=$library_, metadataExtraction=$metadataExtraction, migration=$migration, notifications=$notifications, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion]'; + String toString() => 'SystemConfigJobDto[backgroundTask=$backgroundTask, faceDetection=$faceDetection, library_=$library_, metadataExtraction=$metadataExtraction, migration=$migration, notifications=$notifications, ocr=$ocr, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion, workflow=$workflow]'; Map toJson() { final json = {}; @@ -88,11 +98,13 @@ class SystemConfigJobDto { json[r'metadataExtraction'] = this.metadataExtraction; json[r'migration'] = this.migration; json[r'notifications'] = this.notifications; + json[r'ocr'] = this.ocr; json[r'search'] = this.search; json[r'sidecar'] = this.sidecar; json[r'smartSearch'] = this.smartSearch; json[r'thumbnailGeneration'] = this.thumbnailGeneration; json[r'videoConversion'] = this.videoConversion; + json[r'workflow'] = this.workflow; return json; } @@ -111,11 +123,13 @@ class SystemConfigJobDto { metadataExtraction: JobSettingsDto.fromJson(json[r'metadataExtraction'])!, migration: JobSettingsDto.fromJson(json[r'migration'])!, notifications: JobSettingsDto.fromJson(json[r'notifications'])!, + ocr: JobSettingsDto.fromJson(json[r'ocr'])!, search: JobSettingsDto.fromJson(json[r'search'])!, sidecar: JobSettingsDto.fromJson(json[r'sidecar'])!, smartSearch: JobSettingsDto.fromJson(json[r'smartSearch'])!, thumbnailGeneration: JobSettingsDto.fromJson(json[r'thumbnailGeneration'])!, videoConversion: JobSettingsDto.fromJson(json[r'videoConversion'])!, + workflow: JobSettingsDto.fromJson(json[r'workflow'])!, ); } return null; @@ -169,11 +183,13 @@ class SystemConfigJobDto { 'metadataExtraction', 'migration', 'notifications', + 'ocr', 'search', 'sidecar', 'smartSearch', 'thumbnailGeneration', 'videoConversion', + 'workflow', }; } diff --git a/mobile/openapi/lib/model/system_config_machine_learning_dto.dart b/mobile/openapi/lib/model/system_config_machine_learning_dto.dart index d7b2566d59..da689936f8 100644 --- a/mobile/openapi/lib/model/system_config_machine_learning_dto.dart +++ b/mobile/openapi/lib/model/system_config_machine_learning_dto.dart @@ -18,6 +18,7 @@ class SystemConfigMachineLearningDto { required this.duplicateDetection, required this.enabled, required this.facialRecognition, + required this.ocr, this.urls = const [], }); @@ -31,6 +32,8 @@ class SystemConfigMachineLearningDto { FacialRecognitionConfig facialRecognition; + OcrConfig ocr; + List urls; @override @@ -40,6 +43,7 @@ class SystemConfigMachineLearningDto { other.duplicateDetection == duplicateDetection && other.enabled == enabled && other.facialRecognition == facialRecognition && + other.ocr == ocr && _deepEquality.equals(other.urls, urls); @override @@ -50,10 +54,11 @@ class SystemConfigMachineLearningDto { (duplicateDetection.hashCode) + (enabled.hashCode) + (facialRecognition.hashCode) + + (ocr.hashCode) + (urls.hashCode); @override - String toString() => 'SystemConfigMachineLearningDto[availabilityChecks=$availabilityChecks, clip=$clip, duplicateDetection=$duplicateDetection, enabled=$enabled, facialRecognition=$facialRecognition, urls=$urls]'; + String toString() => 'SystemConfigMachineLearningDto[availabilityChecks=$availabilityChecks, clip=$clip, duplicateDetection=$duplicateDetection, enabled=$enabled, facialRecognition=$facialRecognition, ocr=$ocr, urls=$urls]'; Map toJson() { final json = {}; @@ -62,6 +67,7 @@ class SystemConfigMachineLearningDto { json[r'duplicateDetection'] = this.duplicateDetection; json[r'enabled'] = this.enabled; json[r'facialRecognition'] = this.facialRecognition; + json[r'ocr'] = this.ocr; json[r'urls'] = this.urls; return json; } @@ -80,6 +86,7 @@ class SystemConfigMachineLearningDto { duplicateDetection: DuplicateDetectionConfig.fromJson(json[r'duplicateDetection'])!, enabled: mapValueOfType(json, r'enabled')!, facialRecognition: FacialRecognitionConfig.fromJson(json[r'facialRecognition'])!, + ocr: OcrConfig.fromJson(json[r'ocr'])!, urls: json[r'urls'] is Iterable ? (json[r'urls'] as Iterable).cast().toList(growable: false) : const [], @@ -135,6 +142,7 @@ class SystemConfigMachineLearningDto { 'duplicateDetection', 'enabled', 'facialRecognition', + 'ocr', 'urls', }; } diff --git a/mobile/openapi/lib/model/system_config_smtp_transport_dto.dart b/mobile/openapi/lib/model/system_config_smtp_transport_dto.dart index bdaaa426c5..46307046b4 100644 --- a/mobile/openapi/lib/model/system_config_smtp_transport_dto.dart +++ b/mobile/openapi/lib/model/system_config_smtp_transport_dto.dart @@ -17,6 +17,7 @@ class SystemConfigSmtpTransportDto { required this.ignoreCert, required this.password, required this.port, + required this.secure, required this.username, }); @@ -30,6 +31,8 @@ class SystemConfigSmtpTransportDto { /// Maximum value: 65535 num port; + bool secure; + String username; @override @@ -38,6 +41,7 @@ class SystemConfigSmtpTransportDto { other.ignoreCert == ignoreCert && other.password == password && other.port == port && + other.secure == secure && other.username == username; @override @@ -47,10 +51,11 @@ class SystemConfigSmtpTransportDto { (ignoreCert.hashCode) + (password.hashCode) + (port.hashCode) + + (secure.hashCode) + (username.hashCode); @override - String toString() => 'SystemConfigSmtpTransportDto[host=$host, ignoreCert=$ignoreCert, password=$password, port=$port, username=$username]'; + String toString() => 'SystemConfigSmtpTransportDto[host=$host, ignoreCert=$ignoreCert, password=$password, port=$port, secure=$secure, username=$username]'; Map toJson() { final json = {}; @@ -58,6 +63,7 @@ class SystemConfigSmtpTransportDto { json[r'ignoreCert'] = this.ignoreCert; json[r'password'] = this.password; json[r'port'] = this.port; + json[r'secure'] = this.secure; json[r'username'] = this.username; return json; } @@ -75,6 +81,7 @@ class SystemConfigSmtpTransportDto { ignoreCert: mapValueOfType(json, r'ignoreCert')!, password: mapValueOfType(json, r'password')!, port: num.parse('${json[r'port']}'), + secure: mapValueOfType(json, r'secure')!, username: mapValueOfType(json, r'username')!, ); } @@ -127,6 +134,7 @@ class SystemConfigSmtpTransportDto { 'ignoreCert', 'password', 'port', + 'secure', 'username', }; } diff --git a/mobile/openapi/lib/model/workflow_action_item_dto.dart b/mobile/openapi/lib/model/workflow_action_item_dto.dart new file mode 100644 index 0000000000..ee0b30216d --- /dev/null +++ b/mobile/openapi/lib/model/workflow_action_item_dto.dart @@ -0,0 +1,116 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class WorkflowActionItemDto { + /// Returns a new [WorkflowActionItemDto] instance. + WorkflowActionItemDto({ + this.actionConfig, + required this.actionId, + }); + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + Object? actionConfig; + + String actionId; + + @override + bool operator ==(Object other) => identical(this, other) || other is WorkflowActionItemDto && + other.actionConfig == actionConfig && + other.actionId == actionId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (actionConfig == null ? 0 : actionConfig!.hashCode) + + (actionId.hashCode); + + @override + String toString() => 'WorkflowActionItemDto[actionConfig=$actionConfig, actionId=$actionId]'; + + Map toJson() { + final json = {}; + if (this.actionConfig != null) { + json[r'actionConfig'] = this.actionConfig; + } else { + // json[r'actionConfig'] = null; + } + json[r'actionId'] = this.actionId; + return json; + } + + /// Returns a new [WorkflowActionItemDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static WorkflowActionItemDto? fromJson(dynamic value) { + upgradeDto(value, "WorkflowActionItemDto"); + if (value is Map) { + final json = value.cast(); + + return WorkflowActionItemDto( + actionConfig: mapValueOfType(json, r'actionConfig'), + actionId: mapValueOfType(json, r'actionId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = WorkflowActionItemDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = WorkflowActionItemDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of WorkflowActionItemDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = WorkflowActionItemDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'actionId', + }; +} + diff --git a/mobile/openapi/lib/model/workflow_action_response_dto.dart b/mobile/openapi/lib/model/workflow_action_response_dto.dart new file mode 100644 index 0000000000..6528f018c9 --- /dev/null +++ b/mobile/openapi/lib/model/workflow_action_response_dto.dart @@ -0,0 +1,135 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class WorkflowActionResponseDto { + /// Returns a new [WorkflowActionResponseDto] instance. + WorkflowActionResponseDto({ + required this.actionConfig, + required this.actionId, + required this.id, + required this.order, + required this.workflowId, + }); + + Object? actionConfig; + + String actionId; + + String id; + + num order; + + String workflowId; + + @override + bool operator ==(Object other) => identical(this, other) || other is WorkflowActionResponseDto && + other.actionConfig == actionConfig && + other.actionId == actionId && + other.id == id && + other.order == order && + other.workflowId == workflowId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (actionConfig == null ? 0 : actionConfig!.hashCode) + + (actionId.hashCode) + + (id.hashCode) + + (order.hashCode) + + (workflowId.hashCode); + + @override + String toString() => 'WorkflowActionResponseDto[actionConfig=$actionConfig, actionId=$actionId, id=$id, order=$order, workflowId=$workflowId]'; + + Map toJson() { + final json = {}; + if (this.actionConfig != null) { + json[r'actionConfig'] = this.actionConfig; + } else { + // json[r'actionConfig'] = null; + } + json[r'actionId'] = this.actionId; + json[r'id'] = this.id; + json[r'order'] = this.order; + json[r'workflowId'] = this.workflowId; + return json; + } + + /// Returns a new [WorkflowActionResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static WorkflowActionResponseDto? fromJson(dynamic value) { + upgradeDto(value, "WorkflowActionResponseDto"); + if (value is Map) { + final json = value.cast(); + + return WorkflowActionResponseDto( + actionConfig: mapValueOfType(json, r'actionConfig'), + actionId: mapValueOfType(json, r'actionId')!, + id: mapValueOfType(json, r'id')!, + order: num.parse('${json[r'order']}'), + workflowId: mapValueOfType(json, r'workflowId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = WorkflowActionResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = WorkflowActionResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of WorkflowActionResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = WorkflowActionResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'actionConfig', + 'actionId', + 'id', + 'order', + 'workflowId', + }; +} + diff --git a/mobile/openapi/lib/model/workflow_create_dto.dart b/mobile/openapi/lib/model/workflow_create_dto.dart new file mode 100644 index 0000000000..c6e44743ac --- /dev/null +++ b/mobile/openapi/lib/model/workflow_create_dto.dart @@ -0,0 +1,157 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class WorkflowCreateDto { + /// Returns a new [WorkflowCreateDto] instance. + WorkflowCreateDto({ + this.actions = const [], + this.description, + this.enabled, + this.filters = const [], + required this.name, + required this.triggerType, + }); + + List actions; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? description; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? enabled; + + List filters; + + String name; + + PluginTriggerType triggerType; + + @override + bool operator ==(Object other) => identical(this, other) || other is WorkflowCreateDto && + _deepEquality.equals(other.actions, actions) && + other.description == description && + other.enabled == enabled && + _deepEquality.equals(other.filters, filters) && + other.name == name && + other.triggerType == triggerType; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (actions.hashCode) + + (description == null ? 0 : description!.hashCode) + + (enabled == null ? 0 : enabled!.hashCode) + + (filters.hashCode) + + (name.hashCode) + + (triggerType.hashCode); + + @override + String toString() => 'WorkflowCreateDto[actions=$actions, description=$description, enabled=$enabled, filters=$filters, name=$name, triggerType=$triggerType]'; + + Map toJson() { + final json = {}; + json[r'actions'] = this.actions; + if (this.description != null) { + json[r'description'] = this.description; + } else { + // json[r'description'] = null; + } + if (this.enabled != null) { + json[r'enabled'] = this.enabled; + } else { + // json[r'enabled'] = null; + } + json[r'filters'] = this.filters; + json[r'name'] = this.name; + json[r'triggerType'] = this.triggerType; + return json; + } + + /// Returns a new [WorkflowCreateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static WorkflowCreateDto? fromJson(dynamic value) { + upgradeDto(value, "WorkflowCreateDto"); + if (value is Map) { + final json = value.cast(); + + return WorkflowCreateDto( + actions: WorkflowActionItemDto.listFromJson(json[r'actions']), + description: mapValueOfType(json, r'description'), + enabled: mapValueOfType(json, r'enabled'), + filters: WorkflowFilterItemDto.listFromJson(json[r'filters']), + name: mapValueOfType(json, r'name')!, + triggerType: PluginTriggerType.fromJson(json[r'triggerType'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = WorkflowCreateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = WorkflowCreateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of WorkflowCreateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = WorkflowCreateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'actions', + 'filters', + 'name', + 'triggerType', + }; +} + diff --git a/mobile/openapi/lib/model/workflow_filter_item_dto.dart b/mobile/openapi/lib/model/workflow_filter_item_dto.dart new file mode 100644 index 0000000000..5b78585c3d --- /dev/null +++ b/mobile/openapi/lib/model/workflow_filter_item_dto.dart @@ -0,0 +1,116 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class WorkflowFilterItemDto { + /// Returns a new [WorkflowFilterItemDto] instance. + WorkflowFilterItemDto({ + this.filterConfig, + required this.filterId, + }); + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + Object? filterConfig; + + String filterId; + + @override + bool operator ==(Object other) => identical(this, other) || other is WorkflowFilterItemDto && + other.filterConfig == filterConfig && + other.filterId == filterId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (filterConfig == null ? 0 : filterConfig!.hashCode) + + (filterId.hashCode); + + @override + String toString() => 'WorkflowFilterItemDto[filterConfig=$filterConfig, filterId=$filterId]'; + + Map toJson() { + final json = {}; + if (this.filterConfig != null) { + json[r'filterConfig'] = this.filterConfig; + } else { + // json[r'filterConfig'] = null; + } + json[r'filterId'] = this.filterId; + return json; + } + + /// Returns a new [WorkflowFilterItemDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static WorkflowFilterItemDto? fromJson(dynamic value) { + upgradeDto(value, "WorkflowFilterItemDto"); + if (value is Map) { + final json = value.cast(); + + return WorkflowFilterItemDto( + filterConfig: mapValueOfType(json, r'filterConfig'), + filterId: mapValueOfType(json, r'filterId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = WorkflowFilterItemDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = WorkflowFilterItemDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of WorkflowFilterItemDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = WorkflowFilterItemDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'filterId', + }; +} + diff --git a/mobile/openapi/lib/model/workflow_filter_response_dto.dart b/mobile/openapi/lib/model/workflow_filter_response_dto.dart new file mode 100644 index 0000000000..5257c92b80 --- /dev/null +++ b/mobile/openapi/lib/model/workflow_filter_response_dto.dart @@ -0,0 +1,135 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class WorkflowFilterResponseDto { + /// Returns a new [WorkflowFilterResponseDto] instance. + WorkflowFilterResponseDto({ + required this.filterConfig, + required this.filterId, + required this.id, + required this.order, + required this.workflowId, + }); + + Object? filterConfig; + + String filterId; + + String id; + + num order; + + String workflowId; + + @override + bool operator ==(Object other) => identical(this, other) || other is WorkflowFilterResponseDto && + other.filterConfig == filterConfig && + other.filterId == filterId && + other.id == id && + other.order == order && + other.workflowId == workflowId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (filterConfig == null ? 0 : filterConfig!.hashCode) + + (filterId.hashCode) + + (id.hashCode) + + (order.hashCode) + + (workflowId.hashCode); + + @override + String toString() => 'WorkflowFilterResponseDto[filterConfig=$filterConfig, filterId=$filterId, id=$id, order=$order, workflowId=$workflowId]'; + + Map toJson() { + final json = {}; + if (this.filterConfig != null) { + json[r'filterConfig'] = this.filterConfig; + } else { + // json[r'filterConfig'] = null; + } + json[r'filterId'] = this.filterId; + json[r'id'] = this.id; + json[r'order'] = this.order; + json[r'workflowId'] = this.workflowId; + return json; + } + + /// Returns a new [WorkflowFilterResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static WorkflowFilterResponseDto? fromJson(dynamic value) { + upgradeDto(value, "WorkflowFilterResponseDto"); + if (value is Map) { + final json = value.cast(); + + return WorkflowFilterResponseDto( + filterConfig: mapValueOfType(json, r'filterConfig'), + filterId: mapValueOfType(json, r'filterId')!, + id: mapValueOfType(json, r'id')!, + order: num.parse('${json[r'order']}'), + workflowId: mapValueOfType(json, r'workflowId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = WorkflowFilterResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = WorkflowFilterResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of WorkflowFilterResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = WorkflowFilterResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'filterConfig', + 'filterId', + 'id', + 'order', + 'workflowId', + }; +} + diff --git a/mobile/openapi/lib/model/workflow_response_dto.dart b/mobile/openapi/lib/model/workflow_response_dto.dart new file mode 100644 index 0000000000..5132e7cb73 --- /dev/null +++ b/mobile/openapi/lib/model/workflow_response_dto.dart @@ -0,0 +1,241 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class WorkflowResponseDto { + /// Returns a new [WorkflowResponseDto] instance. + WorkflowResponseDto({ + this.actions = const [], + required this.createdAt, + required this.description, + required this.enabled, + this.filters = const [], + required this.id, + required this.name, + required this.ownerId, + required this.triggerType, + }); + + List actions; + + String createdAt; + + String description; + + bool enabled; + + List filters; + + String id; + + String? name; + + String ownerId; + + WorkflowResponseDtoTriggerTypeEnum triggerType; + + @override + bool operator ==(Object other) => identical(this, other) || other is WorkflowResponseDto && + _deepEquality.equals(other.actions, actions) && + other.createdAt == createdAt && + other.description == description && + other.enabled == enabled && + _deepEquality.equals(other.filters, filters) && + other.id == id && + other.name == name && + other.ownerId == ownerId && + other.triggerType == triggerType; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (actions.hashCode) + + (createdAt.hashCode) + + (description.hashCode) + + (enabled.hashCode) + + (filters.hashCode) + + (id.hashCode) + + (name == null ? 0 : name!.hashCode) + + (ownerId.hashCode) + + (triggerType.hashCode); + + @override + String toString() => 'WorkflowResponseDto[actions=$actions, createdAt=$createdAt, description=$description, enabled=$enabled, filters=$filters, id=$id, name=$name, ownerId=$ownerId, triggerType=$triggerType]'; + + Map toJson() { + final json = {}; + json[r'actions'] = this.actions; + json[r'createdAt'] = this.createdAt; + json[r'description'] = this.description; + json[r'enabled'] = this.enabled; + json[r'filters'] = this.filters; + json[r'id'] = this.id; + if (this.name != null) { + json[r'name'] = this.name; + } else { + // json[r'name'] = null; + } + json[r'ownerId'] = this.ownerId; + json[r'triggerType'] = this.triggerType; + return json; + } + + /// Returns a new [WorkflowResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static WorkflowResponseDto? fromJson(dynamic value) { + upgradeDto(value, "WorkflowResponseDto"); + if (value is Map) { + final json = value.cast(); + + return WorkflowResponseDto( + actions: WorkflowActionResponseDto.listFromJson(json[r'actions']), + createdAt: mapValueOfType(json, r'createdAt')!, + description: mapValueOfType(json, r'description')!, + enabled: mapValueOfType(json, r'enabled')!, + filters: WorkflowFilterResponseDto.listFromJson(json[r'filters']), + id: mapValueOfType(json, r'id')!, + name: mapValueOfType(json, r'name'), + ownerId: mapValueOfType(json, r'ownerId')!, + triggerType: WorkflowResponseDtoTriggerTypeEnum.fromJson(json[r'triggerType'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = WorkflowResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = WorkflowResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of WorkflowResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = WorkflowResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'actions', + 'createdAt', + 'description', + 'enabled', + 'filters', + 'id', + 'name', + 'ownerId', + 'triggerType', + }; +} + + +class WorkflowResponseDtoTriggerTypeEnum { + /// Instantiate a new enum with the provided [value]. + const WorkflowResponseDtoTriggerTypeEnum._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const assetCreate = WorkflowResponseDtoTriggerTypeEnum._(r'AssetCreate'); + static const personRecognized = WorkflowResponseDtoTriggerTypeEnum._(r'PersonRecognized'); + + /// List of all possible values in this [enum][WorkflowResponseDtoTriggerTypeEnum]. + static const values = [ + assetCreate, + personRecognized, + ]; + + static WorkflowResponseDtoTriggerTypeEnum? fromJson(dynamic value) => WorkflowResponseDtoTriggerTypeEnumTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = WorkflowResponseDtoTriggerTypeEnum.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [WorkflowResponseDtoTriggerTypeEnum] to String, +/// and [decode] dynamic data back to [WorkflowResponseDtoTriggerTypeEnum]. +class WorkflowResponseDtoTriggerTypeEnumTypeTransformer { + factory WorkflowResponseDtoTriggerTypeEnumTypeTransformer() => _instance ??= const WorkflowResponseDtoTriggerTypeEnumTypeTransformer._(); + + const WorkflowResponseDtoTriggerTypeEnumTypeTransformer._(); + + String encode(WorkflowResponseDtoTriggerTypeEnum data) => data.value; + + /// Decodes a [dynamic value][data] to a WorkflowResponseDtoTriggerTypeEnum. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + WorkflowResponseDtoTriggerTypeEnum? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'AssetCreate': return WorkflowResponseDtoTriggerTypeEnum.assetCreate; + case r'PersonRecognized': return WorkflowResponseDtoTriggerTypeEnum.personRecognized; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [WorkflowResponseDtoTriggerTypeEnumTypeTransformer] instance. + static WorkflowResponseDtoTriggerTypeEnumTypeTransformer? _instance; +} + + diff --git a/mobile/openapi/lib/model/workflow_update_dto.dart b/mobile/openapi/lib/model/workflow_update_dto.dart new file mode 100644 index 0000000000..b36a396dc6 --- /dev/null +++ b/mobile/openapi/lib/model/workflow_update_dto.dart @@ -0,0 +1,156 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class WorkflowUpdateDto { + /// Returns a new [WorkflowUpdateDto] instance. + WorkflowUpdateDto({ + this.actions = const [], + this.description, + this.enabled, + this.filters = const [], + this.name, + }); + + List actions; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? description; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? enabled; + + List filters; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? name; + + @override + bool operator ==(Object other) => identical(this, other) || other is WorkflowUpdateDto && + _deepEquality.equals(other.actions, actions) && + other.description == description && + other.enabled == enabled && + _deepEquality.equals(other.filters, filters) && + other.name == name; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (actions.hashCode) + + (description == null ? 0 : description!.hashCode) + + (enabled == null ? 0 : enabled!.hashCode) + + (filters.hashCode) + + (name == null ? 0 : name!.hashCode); + + @override + String toString() => 'WorkflowUpdateDto[actions=$actions, description=$description, enabled=$enabled, filters=$filters, name=$name]'; + + Map toJson() { + final json = {}; + json[r'actions'] = this.actions; + if (this.description != null) { + json[r'description'] = this.description; + } else { + // json[r'description'] = null; + } + if (this.enabled != null) { + json[r'enabled'] = this.enabled; + } else { + // json[r'enabled'] = null; + } + json[r'filters'] = this.filters; + if (this.name != null) { + json[r'name'] = this.name; + } else { + // json[r'name'] = null; + } + return json; + } + + /// Returns a new [WorkflowUpdateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static WorkflowUpdateDto? fromJson(dynamic value) { + upgradeDto(value, "WorkflowUpdateDto"); + if (value is Map) { + final json = value.cast(); + + return WorkflowUpdateDto( + actions: WorkflowActionItemDto.listFromJson(json[r'actions']), + description: mapValueOfType(json, r'description'), + enabled: mapValueOfType(json, r'enabled'), + filters: WorkflowFilterItemDto.listFromJson(json[r'filters']), + name: mapValueOfType(json, r'name'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = WorkflowUpdateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = WorkflowUpdateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of WorkflowUpdateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = WorkflowUpdateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/pigeon/background_worker_api.dart b/mobile/pigeon/background_worker_api.dart index 6f6c781de2..a40d290199 100644 --- a/mobile/pigeon/background_worker_api.dart +++ b/mobile/pigeon/background_worker_api.dart @@ -22,6 +22,8 @@ class BackgroundWorkerSettings { abstract class BackgroundWorkerFgHostApi { void enable(); + void saveNotificationMessage(String title, String body); + void configure(BackgroundWorkerSettings settings); void disable(); @@ -33,8 +35,6 @@ abstract class BackgroundWorkerBgHostApi { // required platform channels to notify the native side to start the background upload void onInitialized(); - void showNotification(String title, String content); - // Called from the background flutter engine to request the native side to cleanup void close(); } diff --git a/mobile/pigeon/native_sync_api.dart b/mobile/pigeon/native_sync_api.dart index ac08a68ca3..822e2eddb3 100644 --- a/mobile/pigeon/native_sync_api.dart +++ b/mobile/pigeon/native_sync_api.dart @@ -14,8 +14,10 @@ import 'package:pigeon/pigeon.dart'; class PlatformAsset { final String id; final String name; + // Follows AssetType enum from base_asset.model.dart final int type; + // Seconds since epoch final int? createdAt; final int? updatedAt; @@ -42,6 +44,7 @@ class PlatformAsset { class PlatformAlbum { final String id; final String name; + // Seconds since epoch final int? updatedAt; final bool isCloud; @@ -60,6 +63,7 @@ class SyncDelta { final bool hasChanges; final List updates; final List deletes; + // Asset -> Album mapping final Map> assetAlbums; @@ -107,4 +111,7 @@ abstract class NativeSyncApi { List hashAssets(List assetIds, {bool allowNetworkAccess = false}); void cancelHashing(); + + @TaskQueue(type: TaskQueueType.serialBackgroundThread) + Map> getTrashedAssets(); } diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index d0dc8e64d3..59b23f23ca 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: args - sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.7.0" async: dependency: "direct main" description: @@ -77,10 +77,10 @@ packages: dependency: "direct main" description: name: background_downloader - sha256: "9ed74c55750932178f6989ba8a659687c2a102e05b70f561a1b3f047a5dda790" + sha256: a913b37cc47a656a225e9562b69576000d516f705482f392e2663500e6ff6032 url: "https://pub.dev" source: hosted - version: "9.2.5" + version: "9.3.0" bonsoir: dependency: transitive description: @@ -317,10 +317,10 @@ packages: dependency: "direct main" description: name: connectivity_plus - sha256: "04bf81bb0b77de31557b58d052b24b3eee33f09a6e7a8c68a3e247c7df19ec27" + sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec url: "https://pub.dev" source: hosted - version: "6.1.3" + version: "6.1.5" connectivity_plus_platform_interface: dependency: transitive description: @@ -437,18 +437,18 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: "306b78788d1bb569edb7c55d622953c2414ca12445b41c9117963e03afc5c513" + sha256: dd0e8e02186b2196c7848c9d394a5fd6e5b57a43a546082c5820b1ec72317e33 url: "https://pub.dev" source: hosted - version: "11.3.3" + version: "12.2.0" device_info_plus_platform_interface: dependency: transitive description: name: device_info_plus_platform_interface - sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2" + sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f url: "https://pub.dev" source: hosted - version: "7.0.2" + version: "7.0.3" drift: dependency: "direct main" description: @@ -469,34 +469,26 @@ packages: dependency: "direct main" description: name: drift_flutter - sha256: "0cadbf3b8733409a6cf61d18ba2e94e149df81df7de26f48ae0695b48fd71922" + sha256: b52bd710f809db11e25259d429d799d034ba1c5224ce6a73fe8419feb980d44c url: "https://pub.dev" source: hosted - version: "0.2.4" + version: "0.2.6" dynamic_color: dependency: "direct main" description: name: dynamic_color - sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d + sha256: "43a5a6679649a7731ab860334a5812f2067c2d9ce6452cf069c5e0c25336c17c" url: "https://pub.dev" source: hosted - version: "1.7.0" - easy_image_viewer: - dependency: "direct main" - description: - name: easy_image_viewer - sha256: fb6cb123c3605552cc91150dcdb50ca977001dcddfb71d20caa0c5edc9a80947 - url: "https://pub.dev" - source: hosted - version: "1.5.1" + version: "1.8.1" easy_localization: dependency: "direct main" description: name: easy_localization - sha256: "0f5239c7b8ab06c66440cfb0e9aa4b4640429c6668d5a42fe389c5de42220b12" + sha256: "2ccdf9db8fe4d9c5a75c122e6275674508fd0f0d49c827354967b8afcc56bbed" url: "https://pub.dev" source: hosted - version: "3.0.7+1" + version: "3.0.8" easy_logger: dependency: transitive description: @@ -594,10 +586,10 @@ packages: dependency: "direct main" description: name: flutter_displaymode - sha256: "42c5e9abd13d28ed74f701b60529d7f8416947e58256e6659c5550db719c57ef" + sha256: ecd44b1e902b0073b42ff5b55bf283f38e088270724cdbb7f7065ccf54aa60a8 url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.7.0" flutter_driver: dependency: transitive description: flutter @@ -607,18 +599,18 @@ packages: dependency: "direct main" description: name: flutter_hooks - sha256: b772e710d16d7a20c0740c4f855095026b31c7eb5ba3ab67d2bd52021cd9461d + sha256: "8ae1f090e5f4ef5cfa6670ce1ab5dddadd33f3533a7f9ba19d9f958aa2a89f42" url: "https://pub.dev" source: hosted - version: "0.21.2" + version: "0.21.3+1" flutter_launcher_icons: dependency: "direct dev" description: name: flutter_launcher_icons - sha256: bfa04787c85d80ecb3f8777bde5fc10c3de809240c48fa061a2c2bf15ea5211c + sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" url: "https://pub.dev" source: hosted - version: "0.14.3" + version: "0.14.4" flutter_lints: dependency: "direct dev" description: @@ -660,10 +652,10 @@ packages: dependency: "direct dev" description: name: flutter_native_splash - sha256: edb09c35ee9230c4b03f13dd45bb3a276d0801865f0a4650b7e2a3bba61a803a + sha256: "4fb9f4113350d3a80841ce05ebf1976a36de622af7d19aca0ca9a9911c7ff002" url: "https://pub.dev" source: hosted - version: "2.4.5" + version: "2.4.7" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -732,10 +724,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b + sha256: b9c2ad5872518a27507ab432d1fb97e8813b05f0fc693f9d40fad06d073e0678 url: "https://pub.dev" source: hosted - version: "2.0.17" + version: "2.2.1" flutter_test: dependency: "direct dev" description: flutter @@ -745,10 +737,10 @@ packages: dependency: "direct main" description: name: flutter_udid - sha256: be464dc5b1fb7ee894f6a32d65c086ca5e177fdcf9375ac08d77495b98150f84 + sha256: "166bee5989a58c66b8b62000ea65edccc7c8167bbafdbb08022638db330dd030" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "4.0.0" flutter_web_auth_2: dependency: "direct main" description: @@ -799,14 +791,22 @@ packages: description: flutter source: sdk version: "0.0.0" + geoclue: + dependency: transitive + description: + name: geoclue + sha256: c2a998c77474fc57aa00c6baa2928e58f4b267649057a1c76738656e9dbd2a7f + url: "https://pub.dev" + source: hosted + version: "0.1.1" geolocator: dependency: "direct main" description: name: geolocator - sha256: e7ebfa04ce451daf39b5499108c973189a71a919aa53c1204effda1c5b93b822 + sha256: "79939537046c9025be47ec645f35c8090ecadb6fe98eba146a0d25e8c1357516" url: "https://pub.dev" source: hosted - version: "14.0.0" + version: "14.0.2" geolocator_android: dependency: transitive description: @@ -823,6 +823,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.9" + geolocator_linux: + dependency: transitive + description: + name: geolocator_linux + sha256: c4e966f0a7a87e70049eac7a2617f9e16fd4c585a26e4330bdfc3a71e6a721f3 + url: "https://pub.dev" + source: hosted + version: "0.2.3" geolocator_platform_interface: dependency: transitive description: @@ -863,14 +871,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" + gsettings: + dependency: transitive + description: + name: gsettings + sha256: "1b0ce661f5436d2db1e51f3c4295a49849f03d304003a7ba177d01e3a858249c" + url: "https://pub.dev" + source: hosted + version: "0.2.8" home_widget: dependency: "direct main" description: name: home_widget - sha256: ad9634ef5894f3bac73f04d59e2e5151a39798f49985399fd928dadc828d974a + sha256: "908d033514a981f829fd98213909e11a428104327be3b422718aa643ac9d084a" url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.8.1" hooks_riverpod: dependency: "direct main" description: @@ -891,18 +907,18 @@ packages: dependency: transitive description: name: html - sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec" + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" url: "https://pub.dev" source: hosted - version: "0.15.5" + version: "0.15.6" http: dependency: "direct main" description: name: http - sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f + sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.5.0" http_multi_server: dependency: transitive description: @@ -923,74 +939,74 @@ packages: dependency: transitive description: name: image - sha256: "13d3349ace88f12f4a0d175eb5c12dcdd39d35c4c109a8a13dfeb6d0bd9e31c3" + sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" url: "https://pub.dev" source: hosted - version: "4.5.3" + version: "4.5.4" image_picker: dependency: "direct main" description: name: image_picker - sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" + sha256: "736eb56a911cf24d1859315ad09ddec0b66104bc41a7f8c5b96b4e2620cf5041" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.2.0" image_picker_android: dependency: transitive description: name: image_picker_android - sha256: "8bd392ba8b0c8957a157ae0dc9fcf48c58e6c20908d5880aea1d79734df090e9" + sha256: "58a85e6f09fe9c4484d53d18a0bd6271b72c53fce1d05e6f745ae36d8c18efca" url: "https://pub.dev" source: hosted - version: "0.8.12+22" + version: "0.8.13+5" image_picker_for_web: dependency: transitive description: name: image_picker_for_web - sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83" + sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6" url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.1.0" image_picker_ios: dependency: transitive description: name: image_picker_ios - sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100" + sha256: e675c22790bcc24e9abd455deead2b7a88de4b79f7327a281812f14de1a56f58 url: "https://pub.dev" source: hosted - version: "0.8.12+2" + version: "0.8.13+1" image_picker_linux: dependency: transitive description: name: image_picker_linux - sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" + sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" url: "https://pub.dev" source: hosted - version: "0.2.1+1" + version: "0.2.2" image_picker_macos: dependency: transitive description: name: image_picker_macos - sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1" + sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91" url: "https://pub.dev" source: hosted - version: "0.2.1+2" + version: "0.2.2+1" image_picker_platform_interface: dependency: transitive description: name: image_picker_platform_interface - sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0" + sha256: "9f143b0dba3e459553209e20cc425c9801af48e6dfa4f01a0fcf927be3f41665" url: "https://pub.dev" source: hosted - version: "2.10.1" + version: "2.11.0" image_picker_windows: dependency: transitive description: name: image_picker_windows - sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae url: "https://pub.dev" source: hosted - version: "0.2.1+1" + version: "0.2.2" immich_mobile_immich_lint: dependency: "direct dev" description: @@ -1022,25 +1038,34 @@ packages: isar: dependency: "direct main" description: - name: isar - sha256: e17a9555bc7f22ff26568b8c64d019b4ffa2dc6bd4cb1c8d9b269aefd32e53ad - url: "https://pub.isar-community.dev" - source: hosted + path: "packages/isar" + ref: bb1dca40fe87a001122e5d43bc6254718cb49f3a + resolved-ref: bb1dca40fe87a001122e5d43bc6254718cb49f3a + url: "https://github.com/immich-app/isar" + source: git version: "3.1.8" - isar_flutter_libs: + isar_community: + dependency: transitive + description: + name: isar_community + sha256: "28f59e54636c45ba0bb1b3b7f2656f1c50325f740cea6efcd101900be3fba546" + url: "https://pub.dev" + source: hosted + version: "3.3.0-dev.3" + isar_community_flutter_libs: dependency: "direct main" description: - name: isar_flutter_libs - sha256: "78710781e658ce4bff59b3f38c5b2735e899e627f4e926e1221934e77b95231a" - url: "https://pub.isar-community.dev" + name: isar_community_flutter_libs + sha256: c2934fe755bb3181cb67602fd5df0d080b3d3eb52799f98623aa4fc5acbea010 + url: "https://pub.dev" source: hosted - version: "3.1.8" + version: "3.3.0-dev.3" isar_generator: dependency: "direct dev" description: path: "packages/isar_generator" - ref: v3 - resolved-ref: ad574f60ed6f39d2995cd16fc7dc3de9a646ef30 + ref: bb1dca40fe87a001122e5d43bc6254718cb49f3a + resolved-ref: bb1dca40fe87a001122e5d43bc6254718cb49f3a url: "https://github.com/immich-app/isar" source: git version: "3.1.8" @@ -1208,8 +1233,8 @@ packages: dependency: "direct main" description: path: "." - ref: "5459d54" - resolved-ref: "5459d54cdc1cf4d99e2193b310052f1ebb5dcf43" + ref: e132bc3 + resolved-ref: e132bc3ecc6a6d8fc2089d96f849c8a13129500e url: "https://github.com/immich-app/native_video_player" source: git version: "1.3.1" @@ -1312,10 +1337,10 @@ packages: dependency: "direct main" description: name: path_provider_foundation - sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + sha256: efaec349ddfc181528345c56f8eda9d6cccd71c177511b132c6a0ddaefaa2738 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.3" path_provider_linux: dependency: transitive description: @@ -1392,42 +1417,34 @@ packages: dependency: transitive description: name: petitparser - sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" + sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "7.0.1" photo_manager: dependency: "direct main" description: name: photo_manager - sha256: "0bc7548fd3111eb93a3b0abf1c57364e40aeda32512c100085a48dade60e574f" + sha256: a0d9a7a9bc35eda02d33766412bde6d883a8b0acb86bbe37dac5f691a0894e8a url: "https://pub.dev" source: hosted - version: "3.6.4" - photo_manager_image_provider: - dependency: "direct main" - description: - name: photo_manager_image_provider - sha256: b6015b67b32f345f57cf32c126f871bced2501236c405aafaefa885f7c821e4f - url: "https://pub.dev" - source: hosted - version: "2.2.0" + version: "3.7.1" pigeon: dependency: "direct dev" description: name: pigeon - sha256: b65acb352dc5a5f8615d074a83419388cbcc249f07c6d8c78b5bc16680a55dda + sha256: "0045b172d1da43c40cb3f58e80e04b50a65cba20b8b70dc880af04181f7758da" url: "https://pub.dev" source: hosted - version: "26.0.0" + version: "26.0.2" pinput: dependency: "direct main" description: name: pinput - sha256: "8a73be426a91fefec90a7f130763ca39772d547e92f19a827cf4aa02e323d35a" + sha256: c41f42ee301505ae2375ec32871c985d3717bf8aee845620465b286e0140aad2 url: "https://pub.dev" source: hosted - version: "5.0.1" + version: "5.0.2" platform: dependency: transitive description: @@ -1576,18 +1593,18 @@ packages: dependency: "direct main" description: name: share_handler - sha256: "76575533be04df3fecbebd3c5b5325a8271b5973131f8b8b0ab8490c395a5d37" + sha256: "0a6d007f0e44fbee27164adcd159ecbc88238864313f4e5c58161cae2180328d" url: "https://pub.dev" source: hosted - version: "0.0.22" + version: "0.0.25" share_handler_android: dependency: transitive description: name: share_handler_android - sha256: "124dcc914fb7ecd89076d3dc28435b98fe2129a988bf7742f7a01dcb66a95667" + sha256: caf555b933dc72783aa37fef75688c7b86bd6f7bc17d80fbf585bc42f123cc8d url: "https://pub.dev" source: hosted - version: "0.0.9" + version: "0.0.11" share_handler_ios: dependency: transitive description: @@ -1941,10 +1958,10 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.3.2" url_launcher_android: dependency: transitive description: @@ -2029,10 +2046,10 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" + sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc url: "https://pub.dev" source: hosted - version: "1.1.16" + version: "1.1.19" vector_math: dependency: transitive description: @@ -2053,18 +2070,18 @@ packages: dependency: "direct main" description: name: wakelock_plus - sha256: "36c88af0b930121941345306d259ec4cc4ecca3b151c02e3a9e71aede83c615e" + sha256: "61713aa82b7f85c21c9f4cd0a148abd75f38a74ec645fcb1e446f882c82fd09b" url: "https://pub.dev" source: hosted - version: "1.2.10" + version: "1.3.3" wakelock_plus_platform_interface: dependency: transitive description: name: wakelock_plus_platform_interface - sha256: "70e780bc99796e1db82fe764b1e7dcb89a86f1e5b3afb1db354de50f2e41eb7a" + sha256: "036deb14cd62f558ca3b73006d52ce049fabcdcb2eddfe0bf0fe4e8a943b5cf2" url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.3.0" watcher: dependency: transitive description: @@ -2133,10 +2150,10 @@ packages: dependency: "direct main" description: name: worker_manager - sha256: "086ed63e9b36266e851404ca90fd44e37c0f4c9bbf819e5f8d7c87f9741c0591" + sha256: "1bce9f894a0c187856f5fc0e150e7fe1facce326f048ca6172947754dac3d4f3" url: "https://pub.dev" source: hosted - version: "7.2.3" + version: "7.2.7" xdg_directories: dependency: transitive description: @@ -2149,10 +2166,10 @@ packages: dependency: transitive description: name: xml - sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" url: "https://pub.dev" source: hosted - version: "6.5.0" + version: "6.6.1" xxh3: dependency: transitive description: @@ -2170,5 +2187,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.8.0 <4.0.0" - flutter: ">=3.35.4" + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.35.7" diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 9f5ca01fc1..3dce49e4e1 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -2,121 +2,116 @@ name: immich_mobile description: Immich - selfhosted backup media file on mobile phone publish_to: 'none' -version: 1.143.0+3016 +version: 2.2.3+3026 environment: sdk: '>=3.8.0 <4.0.0' - flutter: 3.35.4 - -isar_version: &isar_version 3.1.8 + flutter: 3.35.7 dependencies: - flutter: - sdk: flutter - - async: ^2.11.0 + async: ^2.13.0 auto_route: ^9.2.0 - background_downloader: ^9.2.5 + background_downloader: ^9.3.0 cached_network_image: ^3.4.1 cancellation_token_http: ^2.1.0 cast: ^2.1.0 - collection: ^1.18.0 + collection: ^1.19.1 connectivity_plus: ^6.1.3 crop_image: ^1.0.16 crypto: ^3.0.6 - device_info_plus: ^11.3.3 - dynamic_color: ^1.7.0 - easy_image_viewer: ^1.5.1 - easy_localization: ^3.0.7+1 + device_info_plus: ^12.2.0 + # DB + drift: ^2.26.0 + drift_flutter: ^0.2.6 + dynamic_color: ^1.8.1 + easy_localization: ^3.0.8 + ffi: ^2.1.4 file_picker: ^8.0.0+1 + flutter: + sdk: flutter flutter_cache_manager: ^3.4.1 - flutter_displaymode: ^0.6.0 - flutter_hooks: ^0.21.2 + flutter_displaymode: ^0.7.0 + flutter_hooks: ^0.21.3+1 flutter_local_notifications: ^17.2.1+2 flutter_secure_storage: ^9.2.4 - flutter_svg: ^2.0.17 - flutter_udid: ^3.0.0 + flutter_svg: ^2.2.1 + flutter_udid: ^4.0.0 flutter_web_auth_2: ^5.0.0-alpha.0 fluttertoast: ^8.2.12 - geolocator: ^14.0.0 + geolocator: ^14.0.2 + home_widget: ^0.8.1 hooks_riverpod: ^2.6.1 - home_widget: ^0.8.0 - http: ^1.3.0 - image_picker: ^1.1.2 - intl: ^0.20.0 + http: ^1.5.0 + image_picker: ^1.2.0 + intl: ^0.20.2 + isar: + git: + url: https://github.com/immich-app/isar + ref: 'bb1dca40fe87a001122e5d43bc6254718cb49f3a' + path: packages/isar/ + isar_community_flutter_libs: 3.3.0-dev.3 local_auth: ^2.3.0 logging: ^1.3.0 maplibre_gl: ^0.22.0 + + native_video_player: + git: + url: https://github.com/immich-app/native_video_player + ref: 'e132bc3' network_info_plus: ^6.1.3 octo_image: ^2.1.0 + openapi: + path: openapi package_info_plus: ^8.3.0 path: ^1.9.1 path_provider: ^2.1.5 - path_provider_foundation: ^2.4.1 + path_provider_foundation: ^2.4.3 permission_handler: ^11.4.0 - photo_manager: ^3.6.4 - photo_manager_image_provider: ^2.2.0 - pinput: ^5.0.1 + photo_manager: ^3.7.1 + pinput: ^5.0.2 punycode: ^1.0.0 riverpod_annotation: ^2.6.1 + scroll_date_picker: ^3.8.0 scrollable_positioned_list: ^0.3.8 - share_handler: ^0.0.22 + share_handler: ^0.0.25 share_plus: ^10.1.4 sliver_tools: ^0.2.12 socket_io_client: ^2.0.3+1 stream_transform: ^2.1.1 thumbhash: 0.1.0+1 timezone: ^0.9.4 - url_launcher: ^6.3.1 + url_launcher: ^6.3.2 uuid: ^4.5.1 - wakelock_plus: ^1.2.10 - worker_manager: ^7.2.3 - scroll_date_picker: ^3.8.0 - ffi: ^2.1.4 - - native_video_player: - git: - url: https://github.com/immich-app/native_video_player - ref: '5459d54' - openapi: - path: openapi - isar: - version: *isar_version - hosted: https://pub.isar-community.dev/ - isar_flutter_libs: # contains Isar Core - version: *isar_version - hosted: https://pub.isar-community.dev/ - # DB - drift: ^2.23.1 - drift_flutter: ^0.2.4 + wakelock_plus: ^1.3.0 + worker_manager: ^7.2.7 dev_dependencies: + auto_route_generator: ^9.0.0 + build_runner: ^2.4.8 + custom_lint: ^0.7.5 + # Drift generator + drift_dev: ^2.26.0 + fake_async: ^1.3.3 + file: ^7.0.1 # for MemoryFileSystem + flutter_launcher_icons: ^0.14.4 + flutter_lints: ^5.0.0 + flutter_native_splash: ^2.4.7 flutter_test: sdk: flutter - flutter_lints: ^5.0.0 - build_runner: ^2.4.8 - auto_route_generator: ^9.0.0 - flutter_launcher_icons: ^0.14.3 - flutter_native_splash: ^2.4.5 + immich_mobile_immich_lint: + path: './immich_lint' + integration_test: + sdk: flutter isar_generator: git: url: https://github.com/immich-app/isar - ref: v3 + ref: 'bb1dca40fe87a001122e5d43bc6254718cb49f3a' path: packages/isar_generator/ - integration_test: - sdk: flutter - custom_lint: ^0.7.5 - riverpod_lint: ^2.6.1 - riverpod_generator: ^2.6.1 mocktail: ^1.0.4 - immich_mobile_immich_lint: - path: './immich_lint' - fake_async: ^1.3.1 - file: ^7.0.1 # for MemoryFileSystem - # Drift generator - drift_dev: ^2.23.1 # Type safe platform code - pigeon: ^26.0.0 + pigeon: ^26.0.2 + riverpod_generator: ^2.6.1 + riverpod_lint: ^2.6.1 flutter: uses-material-design: true diff --git a/mobile/scripts/fdroid_build_isar.sh b/mobile/scripts/fdroid_build_isar.sh index f42bc51d9a..a145268356 100755 --- a/mobile/scripts/fdroid_build_isar.sh +++ b/mobile/scripts/fdroid_build_isar.sh @@ -8,11 +8,11 @@ bash tool/build_android.sh x64 bash tool/build_android.sh armv7 bash tool/build_android.sh arm64 mv libisar_android_arm64.so libisar.so -mv libisar.so ../.pub-cache/hosted/pub.isar-community.dev/isar_flutter_libs-*/android/src/main/jniLibs/arm64-v8a/ +mv libisar.so ../.pub-cache/hosted/pub.dev/isar_community_flutter_libs-*/android/src/main/jniLibs/arm64-v8a/ mv libisar_android_armv7.so libisar.so -mv libisar.so ../.pub-cache/hosted/pub.isar-community.dev/isar_flutter_libs-*/android/src/main/jniLibs/armeabi-v7a/ +mv libisar.so ../.pub-cache/hosted/pub.dev/isar_community_flutter_libs-*/android/src/main/jniLibs/armeabi-v7a/ mv libisar_android_x64.so libisar.so -mv libisar.so ../.pub-cache/hosted/pub.isar-community.dev/isar_flutter_libs-*/android/src/main/jniLibs/x86_64/ +mv libisar.so ../.pub-cache/hosted/pub.dev/isar_community_flutter_libs-*/android/src/main/jniLibs/x86_64/ mv libisar_android_x86.so libisar.so -mv libisar.so ../.pub-cache/hosted/pub.isar-community.dev/isar_flutter_libs-*/android/src/main/jniLibs/x86/ -) \ No newline at end of file +mv libisar.so ../.pub-cache/hosted/pub.dev/isar_community_flutter_libs-*/android/src/main/jniLibs/x86/ +) diff --git a/mobile/test/domain/service.mock.dart b/mobile/test/domain/service.mock.dart index 8293faf125..0bab675889 100644 --- a/mobile/test/domain/service.mock.dart +++ b/mobile/test/domain/service.mock.dart @@ -17,3 +17,4 @@ class MockNativeSyncApi extends Mock implements NativeSyncApi {} class MockAppSettingsService extends Mock implements AppSettingsService {} class MockUploadService extends Mock implements UploadService {} + diff --git a/mobile/test/domain/services/hash_service_test.dart b/mobile/test/domain/services/hash_service_test.dart index 20d60b6866..3529ecca38 100644 --- a/mobile/test/domain/services/hash_service_test.dart +++ b/mobile/test/domain/services/hash_service_test.dart @@ -14,16 +14,19 @@ void main() { late MockLocalAlbumRepository mockAlbumRepo; late MockLocalAssetRepository mockAssetRepo; late MockNativeSyncApi mockNativeApi; + late MockTrashedLocalAssetRepository mockTrashedAssetRepo; setUp(() { mockAlbumRepo = MockLocalAlbumRepository(); mockAssetRepo = MockLocalAssetRepository(); mockNativeApi = MockNativeSyncApi(); + mockTrashedAssetRepo = MockTrashedLocalAssetRepository(); sut = HashService( localAlbumRepository: mockAlbumRepo, localAssetRepository: mockAssetRepo, nativeSyncApi: mockNativeApi, + trashedLocalAssetRepository: mockTrashedAssetRepo, ); registerFallbackValue(LocalAlbumStub.recent); @@ -114,6 +117,7 @@ void main() { localAssetRepository: mockAssetRepo, nativeSyncApi: mockNativeApi, batchSize: batchSize, + trashedLocalAssetRepository: mockTrashedAssetRepo, ); final album = LocalAlbumStub.recent; @@ -186,4 +190,5 @@ void main() { verify(() => mockNativeApi.hashAssets([asset2.id], allowNetworkAccess: false)).called(1); }); }); + } diff --git a/mobile/test/domain/services/local_sync_service_test.dart b/mobile/test/domain/services/local_sync_service_test.dart new file mode 100644 index 0000000000..2f236971e0 --- /dev/null +++ b/mobile/test/domain/services/local_sync_service_test.dart @@ -0,0 +1,190 @@ +import 'package:drift/drift.dart' as drift; +import 'package:drift/native.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/services/local_sync.service.dart'; +import 'package:immich_mobile/domain/services/store.service.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart'; +import 'package:immich_mobile/platform/native_sync_api.g.dart'; +import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../domain/service.mock.dart'; +import '../../fixtures/asset.stub.dart'; +import '../../infrastructure/repository.mock.dart'; +import '../../mocks/asset_entity.mock.dart'; +import '../../repository.mocks.dart'; + +void main() { + late LocalSyncService sut; + late DriftLocalAlbumRepository mockLocalAlbumRepository; + late DriftTrashedLocalAssetRepository mockTrashedLocalAssetRepository; + late LocalFilesManagerRepository mockLocalFilesManager; + late StorageRepository mockStorageRepository; + late MockNativeSyncApi mockNativeSyncApi; + late Drift db; + + setUpAll(() async { + TestWidgetsFlutterBinding.ensureInitialized(); + debugDefaultTargetPlatformOverride = TargetPlatform.android; + + db = Drift(drift.DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); + await StoreService.init(storeRepository: DriftStoreRepository(db)); + }); + + tearDownAll(() async { + debugDefaultTargetPlatformOverride = null; + await Store.clear(); + await db.close(); + }); + + setUp(() async { + mockLocalAlbumRepository = MockLocalAlbumRepository(); + mockTrashedLocalAssetRepository = MockTrashedLocalAssetRepository(); + mockLocalFilesManager = MockLocalFilesManagerRepository(); + mockStorageRepository = MockStorageRepository(); + mockNativeSyncApi = MockNativeSyncApi(); + + when(() => mockNativeSyncApi.shouldFullSync()).thenAnswer((_) async => false); + when(() => mockNativeSyncApi.getMediaChanges()).thenAnswer( + (_) async => SyncDelta( + hasChanges: false, + updates: const [], + deletes: const [], + assetAlbums: const {}, + ), + ); + when(() => mockNativeSyncApi.getTrashedAssets()).thenAnswer((_) async => {}); + when(() => mockTrashedLocalAssetRepository.processTrashSnapshot(any())).thenAnswer((_) async {}); + when(() => mockTrashedLocalAssetRepository.getToRestore()).thenAnswer((_) async => []); + when(() => mockTrashedLocalAssetRepository.getToTrash()).thenAnswer((_) async => {}); + when(() => mockTrashedLocalAssetRepository.applyRestoredAssets(any())).thenAnswer((_) async {}); + when(() => mockTrashedLocalAssetRepository.trashLocalAsset(any())).thenAnswer((_) async {}); + when(() => mockLocalFilesManager.moveToTrash(any>())).thenAnswer((_) async => true); + + sut = LocalSyncService( + localAlbumRepository: mockLocalAlbumRepository, + trashedLocalAssetRepository: mockTrashedLocalAssetRepository, + localFilesManager: mockLocalFilesManager, + storageRepository: mockStorageRepository, + nativeSyncApi: mockNativeSyncApi, + ); + + await Store.put(StoreKey.manageLocalMediaAndroid, false); + when(() => mockLocalFilesManager.hasManageMediaPermission()).thenAnswer((_) async => false); + }); + + group('LocalSyncService - syncTrashedAssets gating', () { + test('invokes syncTrashedAssets when Android flag enabled and permission granted', () async { + await Store.put(StoreKey.manageLocalMediaAndroid, true); + when(() => mockLocalFilesManager.hasManageMediaPermission()).thenAnswer((_) async => true); + + await sut.sync(); + + verify(() => mockNativeSyncApi.getTrashedAssets()).called(1); + verify(() => mockTrashedLocalAssetRepository.processTrashSnapshot(any())).called(1); + }); + + test('skips syncTrashedAssets when store flag disabled', () async { + await Store.put(StoreKey.manageLocalMediaAndroid, false); + when(() => mockLocalFilesManager.hasManageMediaPermission()).thenAnswer((_) async => true); + + await sut.sync(); + + verifyNever(() => mockNativeSyncApi.getTrashedAssets()); + }); + + test('skips syncTrashedAssets when MANAGE_MEDIA permission absent', () async { + await Store.put(StoreKey.manageLocalMediaAndroid, true); + when(() => mockLocalFilesManager.hasManageMediaPermission()).thenAnswer((_) async => false); + + await sut.sync(); + + verifyNever(() => mockNativeSyncApi.getTrashedAssets()); + }); + + test('skips syncTrashedAssets on non-Android platforms', () async { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + addTearDown(() => debugDefaultTargetPlatformOverride = TargetPlatform.android); + + await Store.put(StoreKey.manageLocalMediaAndroid, true); + when(() => mockLocalFilesManager.hasManageMediaPermission()).thenAnswer((_) async => true); + + await sut.sync(); + + verifyNever(() => mockNativeSyncApi.getTrashedAssets()); + }); + }); + + group('LocalSyncService - syncTrashedAssets behavior', () { + test('processes trashed snapshot, restores assets, and trashes local files', () async { + final platformAsset = PlatformAsset( + id: 'remote-id', + name: 'remote.jpg', + type: AssetType.image.index, + durationInSeconds: 0, + orientation: 0, + isFavorite: false, + ); + + final assetsToRestore = [LocalAssetStub.image1]; + when(() => mockTrashedLocalAssetRepository.getToRestore()).thenAnswer((_) async => assetsToRestore); + final restoredIds = ['image1']; + when(() => mockLocalFilesManager.restoreAssetsFromTrash(any())).thenAnswer((invocation) async { + final Iterable requested = invocation.positionalArguments.first as Iterable; + expect(requested, orderedEquals(assetsToRestore)); + return restoredIds; + }); + + final localAssetToTrash = LocalAssetStub.image2.copyWith(id: 'local-trash', checksum: 'checksum-trash'); + when(() => mockTrashedLocalAssetRepository.getToTrash()).thenAnswer((_) async => {'album-a': [localAssetToTrash]}); + + final assetEntity = MockAssetEntity(); + when(() => assetEntity.getMediaUrl()).thenAnswer((_) async => 'content://local-trash'); + when(() => mockStorageRepository.getAssetEntityForAsset(localAssetToTrash)).thenAnswer((_) async => assetEntity); + + await sut.processTrashedAssets({'album-a': [platformAsset]}); + + verify(() => mockTrashedLocalAssetRepository.processTrashSnapshot(any())).called(1); + verify(() => mockTrashedLocalAssetRepository.getToTrash()).called(1); + + verify(() => mockLocalFilesManager.restoreAssetsFromTrash(any())).called(1); + verify(() => mockTrashedLocalAssetRepository.applyRestoredAssets(restoredIds)).called(1); + + verify(() => mockStorageRepository.getAssetEntityForAsset(localAssetToTrash)).called(1); + final moveArgs = + verify(() => mockLocalFilesManager.moveToTrash(captureAny())).captured.single as List; + expect(moveArgs, ['content://local-trash']); + final trashArgs = + verify(() => mockTrashedLocalAssetRepository.trashLocalAsset(captureAny())).captured.single + as Map>; + expect(trashArgs.keys, ['album-a']); + expect(trashArgs['album-a'], [localAssetToTrash]); + }); + + test('does not attempt restore when repository has no assets to restore', () async { + when(() => mockTrashedLocalAssetRepository.getToRestore()).thenAnswer((_) async => []); + + await sut.processTrashedAssets({}); + + verifyNever(() => mockLocalFilesManager.restoreAssetsFromTrash(any())); + verifyNever(() => mockTrashedLocalAssetRepository.applyRestoredAssets(any())); + }); + + test('does not move local assets when repository finds nothing to trash', () async { + when(() => mockTrashedLocalAssetRepository.getToTrash()).thenAnswer((_) async => {}); + + await sut.processTrashedAssets({}); + + verifyNever(() => mockLocalFilesManager.moveToTrash(any())); + verifyNever(() => mockTrashedLocalAssetRepository.trashLocalAsset(any())); + }); + }); +} diff --git a/mobile/test/domain/services/store_service_test.dart b/mobile/test/domain/services/store_service_test.dart index d03e493843..996170b518 100644 --- a/mobile/test/domain/services/store_service_test.dart +++ b/mobile/test/domain/services/store_service_test.dart @@ -53,7 +53,7 @@ void main() { }); tearDown(() async { - sut.dispose(); + unawaited(sut.dispose()); await controller.close(); }); @@ -129,7 +129,7 @@ void main() { final stream = sut.watch(StoreKey.accessToken); final events = [_kAccessToken, _kAccessToken.toUpperCase(), null, _kAccessToken.toLowerCase()]; - expectLater(stream, emitsInOrder(events)); + unawaited(expectLater(stream, emitsInOrder(events))); for (final event in events) { valueController.add(event); diff --git a/mobile/test/domain/services/sync_stream_service_test.dart b/mobile/test/domain/services/sync_stream_service_test.dart index 0126b11e46..109b54a907 100644 --- a/mobile/test/domain/services/sync_stream_service_test.dart +++ b/mobile/test/domain/services/sync_stream_service_test.dart @@ -1,14 +1,30 @@ import 'dart:async'; +import 'package:drift/drift.dart' as drift; +import 'package:drift/native.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/sync_event.model.dart'; +import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/domain/services/sync_stream.service.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart'; +import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; import 'package:mocktail/mocktail.dart'; +import '../../fixtures/asset.stub.dart'; import '../../fixtures/sync_stream.stub.dart'; import '../../infrastructure/repository.mock.dart'; +import '../../mocks/asset_entity.mock.dart'; +import '../../repository.mocks.dart'; class _AbortCallbackWrapper { const _AbortCallbackWrapper(); @@ -30,15 +46,40 @@ void main() { late SyncStreamService sut; late SyncStreamRepository mockSyncStreamRepo; late SyncApiRepository mockSyncApiRepo; + late DriftLocalAssetRepository mockLocalAssetRepo; + late DriftTrashedLocalAssetRepository mockTrashedLocalAssetRepo; + late LocalFilesManagerRepository mockLocalFilesManagerRepo; + late StorageRepository mockStorageRepo; late Future Function(List, Function(), Function()) handleEventsCallback; late _MockAbortCallbackWrapper mockAbortCallbackWrapper; late _MockAbortCallbackWrapper mockResetCallbackWrapper; + late Drift db; + late bool hasManageMediaPermission; + + setUpAll(() async { + TestWidgetsFlutterBinding.ensureInitialized(); + debugDefaultTargetPlatformOverride = TargetPlatform.android; + registerFallbackValue(LocalAssetStub.image1); + + db = Drift(drift.DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); + await StoreService.init(storeRepository: DriftStoreRepository(db)); + }); + + tearDownAll(() async { + debugDefaultTargetPlatformOverride = null; + await Store.clear(); + await db.close(); + }); successHandler(Invocation _) async => true; - setUp(() { + setUp(() async { mockSyncStreamRepo = MockSyncStreamRepository(); mockSyncApiRepo = MockSyncApiRepository(); + mockLocalAssetRepo = MockLocalAssetRepository(); + mockTrashedLocalAssetRepo = MockTrashedLocalAssetRepository(); + mockLocalFilesManagerRepo = MockLocalFilesManagerRepository(); + mockStorageRepo = MockStorageRepository(); mockAbortCallbackWrapper = _MockAbortCallbackWrapper(); mockResetCallbackWrapper = _MockAbortCallbackWrapper(); @@ -87,7 +128,25 @@ void main() { when(() => mockSyncStreamRepo.updateAssetFacesV1(any())).thenAnswer(successHandler); when(() => mockSyncStreamRepo.deleteAssetFacesV1(any())).thenAnswer(successHandler); - sut = SyncStreamService(syncApiRepository: mockSyncApiRepo, syncStreamRepository: mockSyncStreamRepo); + sut = SyncStreamService( + syncApiRepository: mockSyncApiRepo, + syncStreamRepository: mockSyncStreamRepo, + localAssetRepository: mockLocalAssetRepo, + trashedLocalAssetRepository: mockTrashedLocalAssetRepo, + localFilesManager: mockLocalFilesManagerRepo, + storageRepository: mockStorageRepo, + ); + + when(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).thenAnswer((_) async => {}); + when(() => mockTrashedLocalAssetRepo.trashLocalAsset(any())).thenAnswer((_) async {}); + when(() => mockTrashedLocalAssetRepo.getToRestore()).thenAnswer((_) async => []); + when(() => mockTrashedLocalAssetRepo.applyRestoredAssets(any())).thenAnswer((_) async {}); + hasManageMediaPermission = false; + when(() => mockLocalFilesManagerRepo.hasManageMediaPermission()).thenAnswer((_) async => hasManageMediaPermission); + when(() => mockLocalFilesManagerRepo.moveToTrash(any())).thenAnswer((_) async => true); + when(() => mockLocalFilesManagerRepo.restoreAssetsFromTrash(any())).thenAnswer((_) async => []); + when(() => mockStorageRepo.getAssetEntityForAsset(any())).thenAnswer((_) async => null); + await Store.put(StoreKey.manageLocalMediaAndroid, false); }); Future simulateEvents(List events) async { @@ -152,6 +211,10 @@ void main() { sut = SyncStreamService( syncApiRepository: mockSyncApiRepo, syncStreamRepository: mockSyncStreamRepo, + localAssetRepository: mockLocalAssetRepo, + trashedLocalAssetRepository: mockTrashedLocalAssetRepo, + localFilesManager: mockLocalFilesManagerRepo, + storageRepository: mockStorageRepo, cancelChecker: cancellationChecker.call, ); await sut.sync(); @@ -187,6 +250,10 @@ void main() { sut = SyncStreamService( syncApiRepository: mockSyncApiRepo, syncStreamRepository: mockSyncStreamRepo, + localAssetRepository: mockLocalAssetRepo, + trashedLocalAssetRepository: mockTrashedLocalAssetRepo, + localFilesManager: mockLocalFilesManagerRepo, + storageRepository: mockStorageRepo, cancelChecker: cancellationChecker.call, ); @@ -296,4 +363,127 @@ void main() { verify(() => mockSyncApiRepo.ack(["5"])).called(1); }); }); + + group("SyncStreamService - remote trash & restore", () { + setUp(() async { + await Store.put(StoreKey.manageLocalMediaAndroid, true); + hasManageMediaPermission = true; + }); + + tearDown(() async { + await Store.put(StoreKey.manageLocalMediaAndroid, false); + hasManageMediaPermission = false; + }); + + test("moves backed up local and merged assets to device trash when remote trash events are received", () async { + final localAsset = LocalAssetStub.image1.copyWith(id: 'local-only', checksum: 'checksum-local', remoteId: null); + final mergedAsset = LocalAssetStub.image2.copyWith( + id: 'merged-local', + checksum: 'checksum-merged', + remoteId: 'remote-merged', + ); + final assetsByAlbum = { + 'album-a': [localAsset], + 'album-b': [mergedAsset], + }; + when(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).thenAnswer((invocation) async { + final Iterable requestedChecksums = invocation.positionalArguments.first as Iterable; + expect(requestedChecksums.toSet(), equals({'checksum-local', 'checksum-merged', 'checksum-remote-only'})); + return assetsByAlbum; + }); + + final localEntity = MockAssetEntity(); + when(() => localEntity.getMediaUrl()).thenAnswer((_) async => 'content://local-only'); + when(() => mockStorageRepo.getAssetEntityForAsset(localAsset)).thenAnswer((_) async => localEntity); + + final mergedEntity = MockAssetEntity(); + when(() => mergedEntity.getMediaUrl()).thenAnswer((_) async => 'content://merged-local'); + when(() => mockStorageRepo.getAssetEntityForAsset(mergedAsset)).thenAnswer((_) async => mergedEntity); + + when(() => mockLocalFilesManagerRepo.moveToTrash(any())).thenAnswer((invocation) async { + final urls = invocation.positionalArguments.first as List; + expect(urls, unorderedEquals(['content://local-only', 'content://merged-local'])); + return true; + }); + + final events = [ + SyncStreamStub.assetTrashed( + id: 'remote-1', + checksum: localAsset.checksum!, + ack: 'asset-remote-local-1', + trashedAt: DateTime(2025, 5, 1), + ), + SyncStreamStub.assetTrashed( + id: 'remote-2', + checksum: mergedAsset.checksum!, + ack: 'asset-remote-merged-2', + trashedAt: DateTime(2025, 5, 2), + ), + SyncStreamStub.assetTrashed( + id: 'remote-3', + checksum: 'checksum-remote-only', + ack: 'asset-remote-only-3', + trashedAt: DateTime(2025, 5, 3), + ), + ]; + + await simulateEvents(events); + + verify(() => mockTrashedLocalAssetRepo.trashLocalAsset(assetsByAlbum)).called(1); + verify(() => mockSyncApiRepo.ack(['asset-remote-only-3'])).called(1); + }); + + test("skips device trashing when no local assets match the remote trash payload", () async { + final events = [ + SyncStreamStub.assetTrashed( + id: 'remote-only', + checksum: 'checksum-only', + ack: 'asset-remote-only-9', + trashedAt: DateTime(2025, 6, 1), + ), + ]; + + await simulateEvents(events); + + verify(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).called(1); + verifyNever(() => mockLocalFilesManagerRepo.moveToTrash(any())); + verifyNever(() => mockTrashedLocalAssetRepo.trashLocalAsset(any())); + }); + + test("does not request local deletions for permanent remote delete events", () async { + final events = [SyncStreamStub.assetDeleteV1]; + + await simulateEvents(events); + + verifyNever(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())); + verifyNever(() => mockLocalFilesManagerRepo.moveToTrash(any())); + verify(() => mockSyncStreamRepo.deleteAssetsV1(any())).called(1); + }); + + test("restores trashed local assets once the matching remote assets leave the trash", () async { + final trashedAssets = [ + LocalAssetStub.image1.copyWith(id: 'trashed-1', checksum: 'checksum-trash', remoteId: 'remote-1'), + ]; + when(() => mockTrashedLocalAssetRepo.getToRestore()).thenAnswer((_) async => trashedAssets); + + final restoredIds = ['trashed-1']; + when(() => mockLocalFilesManagerRepo.restoreAssetsFromTrash(any())).thenAnswer((invocation) async { + final Iterable requestedAssets = invocation.positionalArguments.first as Iterable; + expect(requestedAssets, orderedEquals(trashedAssets)); + return restoredIds; + }); + + final events = [ + SyncStreamStub.assetModified( + id: 'remote-1', + checksum: 'checksum-trash', + ack: 'asset-remote-1-11', + ), + ]; + + await simulateEvents(events); + + verify(() => mockTrashedLocalAssetRepo.applyRestoredAssets(restoredIds)).called(1); + }); + }); } diff --git a/mobile/test/drift/main/generated/schema.dart b/mobile/test/drift/main/generated/schema.dart index 1d78a44317..69dff89fb1 100644 --- a/mobile/test/drift/main/generated/schema.dart +++ b/mobile/test/drift/main/generated/schema.dart @@ -14,6 +14,8 @@ import 'schema_v8.dart' as v8; import 'schema_v9.dart' as v9; import 'schema_v10.dart' as v10; import 'schema_v11.dart' as v11; +import 'schema_v12.dart' as v12; +import 'schema_v13.dart' as v13; class GeneratedHelper implements SchemaInstantiationHelper { @override @@ -41,10 +43,14 @@ class GeneratedHelper implements SchemaInstantiationHelper { return v10.DatabaseAtV10(db); case 11: return v11.DatabaseAtV11(db); + case 12: + return v12.DatabaseAtV12(db); + case 13: + return v13.DatabaseAtV13(db); default: throw MissingSchemaException(version, versions); } } - static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; + static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; } diff --git a/mobile/test/drift/main/generated/schema_v12.dart b/mobile/test/drift/main/generated/schema_v12.dart new file mode 100644 index 0000000000..c42df284ec --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v12.dart @@ -0,0 +1,7198 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final String email; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + const UserEntityData({ + required this.id, + required this.name, + required this.email, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn localDateTime = + GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn libraryId = GeneratedColumn( + 'library_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + libraryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}library_id'], + ), + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String checksum; + final bool isFavorite; + final String ownerId; + final DateTime? localDateTime; + final String? thumbHash; + final DateTime? deletedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + final String? libraryId; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + this.libraryId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + if (!nullToAbsent || libraryId != null) { + map['library_id'] = Variable(libraryId); + } + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + libraryId: serializer.fromJson(json['libraryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + 'libraryId': serializer.toJson(libraryId), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? checksum, + bool? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + Value libraryId = const Value.absent(), + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + libraryId: libraryId.present ? libraryId.value : this.libraryId, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId && + other.libraryId == this.libraryId); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + final Value libraryId; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + Expression? libraryId, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + if (libraryId != null) 'library_id': libraryId, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + Value? libraryId, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + libraryId: libraryId ?? this.libraryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + if (libraryId.present) { + map['library_id'] = Variable(libraryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String? checksum; + final bool isFavorite; + final int orientation; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_activity_enabled" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String? thumbnailAssetId; + final bool isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + Value thumbnailAssetId = const Value.absent(), + bool? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + ownerId = Value(ownerId), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_ios_shared_album" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn linkedRemoteAlbumId = + GeneratedColumn( + 'linked_remote_album_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_ios_shared_album'], + )!, + linkedRemoteAlbumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}linked_remote_album_id'], + ), + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final DateTime updatedAt; + final int backupSelection; + final bool isIosSharedAlbum; + final String? linkedRemoteAlbumId; + final bool? marker_; + const LocalAlbumEntityData({ + required this.id, + required this.name, + required this.updatedAt, + required this.backupSelection, + required this.isIosSharedAlbum, + this.linkedRemoteAlbumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || linkedRemoteAlbumId != null) { + map['linked_remote_album_id'] = Variable(linkedRemoteAlbumId); + } + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + linkedRemoteAlbumId: serializer.fromJson( + json['linkedRemoteAlbumId'], + ), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'linkedRemoteAlbumId': serializer.toJson(linkedRemoteAlbumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + DateTime? updatedAt, + int? backupSelection, + bool? isIosSharedAlbum, + Value linkedRemoteAlbumId = const Value.absent(), + Value marker_ = const Value.absent(), + }) => LocalAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId.present + ? linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumEntityData copyWithCompanion(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + backupSelection: data.backupSelection.present + ? data.backupSelection.value + : this.backupSelection, + isIosSharedAlbum: data.isIosSharedAlbum.present + ? data.isIosSharedAlbum.value + : this.isIosSharedAlbum, + linkedRemoteAlbumId: data.linkedRemoteAlbumId.present + ? data.linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.linkedRemoteAlbumId == this.linkedRemoteAlbumId && + other.marker_ == this.marker_); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value linkedRemoteAlbumId; + final Value marker_; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? linkedRemoteAlbumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (updatedAt != null) 'updated_at': updatedAt, + if (backupSelection != null) 'backup_selection': backupSelection, + if (isIosSharedAlbum != null) 'is_ios_shared_album': isIosSharedAlbum, + if (linkedRemoteAlbumId != null) + 'linked_remote_album_id': linkedRemoteAlbumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? linkedRemoteAlbumId, + Value? marker_, + }) { + return LocalAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId ?? this.linkedRemoteAlbumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (linkedRemoteAlbumId.present) { + map['linked_remote_album_id'] = Variable( + linkedRemoteAlbumId.value, + ); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [assetId, albumId, marker_]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + final bool? marker_; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumAssetEntityData copyWith({ + String? assetId, + String? albumId, + Value marker_ = const Value.absent(), + }) => LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId, marker_); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId && + other.marker_ == this.marker_); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + final Value marker_; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + this.marker_ = const Value.absent(), + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + Value? marker_, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class AuthUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AuthUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_admin" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn quotaSizeInBytes = GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn quotaUsageInBytes = GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn pinCode = GeneratedColumn( + 'pin_code', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'auth_user_entity'; + @override + Set get $primaryKey => {id}; + @override + AuthUserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AuthUserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_admin'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + quotaSizeInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_size_in_bytes'], + )!, + quotaUsageInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_usage_in_bytes'], + )!, + pinCode: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pin_code'], + ), + ); + } + + @override + AuthUserEntity createAlias(String alias) { + return AuthUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AuthUserEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String email; + final bool isAdmin; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + final int quotaSizeInBytes; + final int quotaUsageInBytes; + final String? pinCode; + const AuthUserEntityData({ + required this.id, + required this.name, + required this.email, + required this.isAdmin, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + required this.quotaSizeInBytes, + required this.quotaUsageInBytes, + this.pinCode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['is_admin'] = Variable(isAdmin); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes); + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes); + if (!nullToAbsent || pinCode != null) { + map['pin_code'] = Variable(pinCode); + } + return map; + } + + factory AuthUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AuthUserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + isAdmin: serializer.fromJson(json['isAdmin']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + quotaSizeInBytes: serializer.fromJson(json['quotaSizeInBytes']), + quotaUsageInBytes: serializer.fromJson(json['quotaUsageInBytes']), + pinCode: serializer.fromJson(json['pinCode']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'isAdmin': serializer.toJson(isAdmin), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + 'quotaSizeInBytes': serializer.toJson(quotaSizeInBytes), + 'quotaUsageInBytes': serializer.toJson(quotaUsageInBytes), + 'pinCode': serializer.toJson(pinCode), + }; + } + + AuthUserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? isAdmin, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + int? quotaSizeInBytes, + int? quotaUsageInBytes, + Value pinCode = const Value.absent(), + }) => AuthUserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode.present ? pinCode.value : this.pinCode, + ); + AuthUserEntityData copyWithCompanion(AuthUserEntityCompanion data) { + return AuthUserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + quotaSizeInBytes: data.quotaSizeInBytes.present + ? data.quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: data.quotaUsageInBytes.present + ? data.quotaUsageInBytes.value + : this.quotaUsageInBytes, + pinCode: data.pinCode.present ? data.pinCode.value : this.pinCode, + ); + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AuthUserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.isAdmin == this.isAdmin && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor && + other.quotaSizeInBytes == this.quotaSizeInBytes && + other.quotaUsageInBytes == this.quotaUsageInBytes && + other.pinCode == this.pinCode); +} + +class AuthUserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value isAdmin; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + final Value quotaSizeInBytes; + final Value quotaUsageInBytes; + final Value pinCode; + const AuthUserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }); + AuthUserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + required int avatarColor, + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email), + avatarColor = Value(avatarColor); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? isAdmin, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + Expression? quotaSizeInBytes, + Expression? quotaUsageInBytes, + Expression? pinCode, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (isAdmin != null) 'is_admin': isAdmin, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes, + if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes, + if (pinCode != null) 'pin_code': pinCode, + }); + } + + AuthUserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? isAdmin, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + Value? quotaSizeInBytes, + Value? quotaUsageInBytes, + Value? pinCode, + }) { + return AuthUserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode ?? this.pinCode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + if (quotaSizeInBytes.present) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes.value); + } + if (quotaUsageInBytes.present) { + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes.value); + } + if (pinCode.present) { + map['pin_code'] = Variable(pinCode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("in_timeline" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final bool inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + bool? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn dateTimeOriginal = + GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final DateTime? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson( + json['dateTimeOriginal'], + ), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_saved" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime? deletedAt; + final String ownerId; + final int type; + final String data; + final bool isSaved; + final DateTime memoryAt; + final DateTime? seenAt; + final DateTime? showAt; + final DateTime? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + bool? isSaved, + DateTime? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required DateTime memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES memory_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_hidden" IN (0, 1))', + ), + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final bool isFavorite; + final bool isHidden; + final String? color; + final DateTime? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + bool? isFavorite, + bool? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required bool isFavorite, + required bool isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class AssetFaceEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetFaceEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES person_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AssetFaceEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = Variable(personId); + } + map['image_width'] = Variable(imageWidth); + map['image_height'] = Variable(imageHeight); + map['bounding_box_x1'] = Variable(boundingBoxX1); + map['bounding_box_y1'] = Variable(boundingBoxY1); + map['bounding_box_x2'] = Variable(boundingBoxX2); + map['bounding_box_y2'] = Variable(boundingBoxY2); + map['source_type'] = Variable(sourceType); + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + }; + } + + AssetFaceEntityData copyWith({ + String? id, + String? assetId, + Value personId = const Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + }) => AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + AssetFaceEntityData copyWithCompanion(AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType); +} + +class AssetFaceEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value personId; + final Value imageWidth; + final Value imageHeight; + final Value boundingBoxX1; + final Value boundingBoxY1; + final Value boundingBoxX2; + final Value boundingBoxY2; + final Value sourceType; + const AssetFaceEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.personId = const Value.absent(), + this.imageWidth = const Value.absent(), + this.imageHeight = const Value.absent(), + this.boundingBoxX1 = const Value.absent(), + this.boundingBoxY1 = const Value.absent(), + this.boundingBoxX2 = const Value.absent(), + this.boundingBoxY2 = const Value.absent(), + this.sourceType = const Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + }) : id = Value(id), + assetId = Value(assetId), + imageWidth = Value(imageWidth), + imageHeight = Value(imageHeight), + boundingBoxX1 = Value(boundingBoxX1), + boundingBoxY1 = Value(boundingBoxY1), + boundingBoxX2 = Value(boundingBoxX2), + boundingBoxY2 = Value(boundingBoxY2), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? personId, + Expression? imageWidth, + Expression? imageHeight, + Expression? boundingBoxX1, + Expression? boundingBoxY1, + Expression? boundingBoxX2, + Expression? boundingBoxY2, + Expression? sourceType, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + }) { + return AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } +} + +class StoreEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StoreEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stringValue = GeneratedColumn( + 'string_value', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn intValue = GeneratedColumn( + 'int_value', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + @override + List get $columns => [id, stringValue, intValue]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'store_entity'; + @override + Set get $primaryKey => {id}; + @override + StoreEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StoreEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + stringValue: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}string_value'], + ), + intValue: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}int_value'], + ), + ); + } + + @override + StoreEntity createAlias(String alias) { + return StoreEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StoreEntityData extends DataClass implements Insertable { + final int id; + final String? stringValue; + final int? intValue; + const StoreEntityData({required this.id, this.stringValue, this.intValue}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || stringValue != null) { + map['string_value'] = Variable(stringValue); + } + if (!nullToAbsent || intValue != null) { + map['int_value'] = Variable(intValue); + } + return map; + } + + factory StoreEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StoreEntityData( + id: serializer.fromJson(json['id']), + stringValue: serializer.fromJson(json['stringValue']), + intValue: serializer.fromJson(json['intValue']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'stringValue': serializer.toJson(stringValue), + 'intValue': serializer.toJson(intValue), + }; + } + + StoreEntityData copyWith({ + int? id, + Value stringValue = const Value.absent(), + Value intValue = const Value.absent(), + }) => StoreEntityData( + id: id ?? this.id, + stringValue: stringValue.present ? stringValue.value : this.stringValue, + intValue: intValue.present ? intValue.value : this.intValue, + ); + StoreEntityData copyWithCompanion(StoreEntityCompanion data) { + return StoreEntityData( + id: data.id.present ? data.id.value : this.id, + stringValue: data.stringValue.present + ? data.stringValue.value + : this.stringValue, + intValue: data.intValue.present ? data.intValue.value : this.intValue, + ); + } + + @override + String toString() { + return (StringBuffer('StoreEntityData(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, stringValue, intValue); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StoreEntityData && + other.id == this.id && + other.stringValue == this.stringValue && + other.intValue == this.intValue); +} + +class StoreEntityCompanion extends UpdateCompanion { + final Value id; + final Value stringValue; + final Value intValue; + const StoreEntityCompanion({ + this.id = const Value.absent(), + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }); + StoreEntityCompanion.insert({ + required int id, + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }) : id = Value(id); + static Insertable custom({ + Expression? id, + Expression? stringValue, + Expression? intValue, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (stringValue != null) 'string_value': stringValue, + if (intValue != null) 'int_value': intValue, + }); + } + + StoreEntityCompanion copyWith({ + Value? id, + Value? stringValue, + Value? intValue, + }) { + return StoreEntityCompanion( + id: id ?? this.id, + stringValue: stringValue ?? this.stringValue, + intValue: intValue ?? this.intValue, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (stringValue.present) { + map['string_value'] = Variable(stringValue.value); + } + if (intValue.present) { + map['int_value'] = Variable(intValue.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StoreEntityCompanion(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV12 extends GeneratedDatabase { + DatabaseAtV12(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index idxRemoteAssetOwnerChecksum = Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + late final Index uQRemoteAssetsOwnerChecksum = Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + late final Index uQRemoteAssetsOwnerLibraryChecksum = Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final AuthUserEntity authUserEntity = AuthUserEntity(this); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); + late final StoreEntity storeEntity = StoreEntity(this); + late final Index idxLatLng = Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + idxLatLng, + ]; + @override + int get schemaVersion => 12; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/drift/main/generated/schema_v13.dart b/mobile/test/drift/main/generated/schema_v13.dart new file mode 100644 index 0000000000..da0d853678 --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v13.dart @@ -0,0 +1,7765 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final String email; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + const UserEntityData({ + required this.id, + required this.name, + required this.email, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn localDateTime = + GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn libraryId = GeneratedColumn( + 'library_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + libraryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}library_id'], + ), + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String checksum; + final bool isFavorite; + final String ownerId; + final DateTime? localDateTime; + final String? thumbHash; + final DateTime? deletedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + final String? libraryId; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + this.libraryId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + if (!nullToAbsent || libraryId != null) { + map['library_id'] = Variable(libraryId); + } + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + libraryId: serializer.fromJson(json['libraryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + 'libraryId': serializer.toJson(libraryId), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? checksum, + bool? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + Value libraryId = const Value.absent(), + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + libraryId: libraryId.present ? libraryId.value : this.libraryId, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId && + other.libraryId == this.libraryId); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + final Value libraryId; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + Expression? libraryId, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + if (libraryId != null) 'library_id': libraryId, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + Value? libraryId, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + libraryId: libraryId ?? this.libraryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + if (libraryId.present) { + map['library_id'] = Variable(libraryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String? checksum; + final bool isFavorite; + final int orientation; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_activity_enabled" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String? thumbnailAssetId; + final bool isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + Value thumbnailAssetId = const Value.absent(), + bool? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + ownerId = Value(ownerId), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_ios_shared_album" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn linkedRemoteAlbumId = + GeneratedColumn( + 'linked_remote_album_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_ios_shared_album'], + )!, + linkedRemoteAlbumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}linked_remote_album_id'], + ), + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final DateTime updatedAt; + final int backupSelection; + final bool isIosSharedAlbum; + final String? linkedRemoteAlbumId; + final bool? marker_; + const LocalAlbumEntityData({ + required this.id, + required this.name, + required this.updatedAt, + required this.backupSelection, + required this.isIosSharedAlbum, + this.linkedRemoteAlbumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || linkedRemoteAlbumId != null) { + map['linked_remote_album_id'] = Variable(linkedRemoteAlbumId); + } + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + linkedRemoteAlbumId: serializer.fromJson( + json['linkedRemoteAlbumId'], + ), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'linkedRemoteAlbumId': serializer.toJson(linkedRemoteAlbumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + DateTime? updatedAt, + int? backupSelection, + bool? isIosSharedAlbum, + Value linkedRemoteAlbumId = const Value.absent(), + Value marker_ = const Value.absent(), + }) => LocalAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId.present + ? linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumEntityData copyWithCompanion(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + backupSelection: data.backupSelection.present + ? data.backupSelection.value + : this.backupSelection, + isIosSharedAlbum: data.isIosSharedAlbum.present + ? data.isIosSharedAlbum.value + : this.isIosSharedAlbum, + linkedRemoteAlbumId: data.linkedRemoteAlbumId.present + ? data.linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.linkedRemoteAlbumId == this.linkedRemoteAlbumId && + other.marker_ == this.marker_); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value linkedRemoteAlbumId; + final Value marker_; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? linkedRemoteAlbumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (updatedAt != null) 'updated_at': updatedAt, + if (backupSelection != null) 'backup_selection': backupSelection, + if (isIosSharedAlbum != null) 'is_ios_shared_album': isIosSharedAlbum, + if (linkedRemoteAlbumId != null) + 'linked_remote_album_id': linkedRemoteAlbumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? linkedRemoteAlbumId, + Value? marker_, + }) { + return LocalAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId ?? this.linkedRemoteAlbumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (linkedRemoteAlbumId.present) { + map['linked_remote_album_id'] = Variable( + linkedRemoteAlbumId.value, + ); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [assetId, albumId, marker_]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + final bool? marker_; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumAssetEntityData copyWith({ + String? assetId, + String? albumId, + Value marker_ = const Value.absent(), + }) => LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId, marker_); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId && + other.marker_ == this.marker_); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + final Value marker_; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + this.marker_ = const Value.absent(), + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + Value? marker_, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class AuthUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AuthUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_admin" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn quotaSizeInBytes = GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn quotaUsageInBytes = GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn pinCode = GeneratedColumn( + 'pin_code', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'auth_user_entity'; + @override + Set get $primaryKey => {id}; + @override + AuthUserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AuthUserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_admin'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + quotaSizeInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_size_in_bytes'], + )!, + quotaUsageInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_usage_in_bytes'], + )!, + pinCode: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pin_code'], + ), + ); + } + + @override + AuthUserEntity createAlias(String alias) { + return AuthUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AuthUserEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String email; + final bool isAdmin; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + final int quotaSizeInBytes; + final int quotaUsageInBytes; + final String? pinCode; + const AuthUserEntityData({ + required this.id, + required this.name, + required this.email, + required this.isAdmin, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + required this.quotaSizeInBytes, + required this.quotaUsageInBytes, + this.pinCode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['is_admin'] = Variable(isAdmin); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes); + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes); + if (!nullToAbsent || pinCode != null) { + map['pin_code'] = Variable(pinCode); + } + return map; + } + + factory AuthUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AuthUserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + isAdmin: serializer.fromJson(json['isAdmin']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + quotaSizeInBytes: serializer.fromJson(json['quotaSizeInBytes']), + quotaUsageInBytes: serializer.fromJson(json['quotaUsageInBytes']), + pinCode: serializer.fromJson(json['pinCode']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'isAdmin': serializer.toJson(isAdmin), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + 'quotaSizeInBytes': serializer.toJson(quotaSizeInBytes), + 'quotaUsageInBytes': serializer.toJson(quotaUsageInBytes), + 'pinCode': serializer.toJson(pinCode), + }; + } + + AuthUserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? isAdmin, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + int? quotaSizeInBytes, + int? quotaUsageInBytes, + Value pinCode = const Value.absent(), + }) => AuthUserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode.present ? pinCode.value : this.pinCode, + ); + AuthUserEntityData copyWithCompanion(AuthUserEntityCompanion data) { + return AuthUserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + quotaSizeInBytes: data.quotaSizeInBytes.present + ? data.quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: data.quotaUsageInBytes.present + ? data.quotaUsageInBytes.value + : this.quotaUsageInBytes, + pinCode: data.pinCode.present ? data.pinCode.value : this.pinCode, + ); + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AuthUserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.isAdmin == this.isAdmin && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor && + other.quotaSizeInBytes == this.quotaSizeInBytes && + other.quotaUsageInBytes == this.quotaUsageInBytes && + other.pinCode == this.pinCode); +} + +class AuthUserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value isAdmin; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + final Value quotaSizeInBytes; + final Value quotaUsageInBytes; + final Value pinCode; + const AuthUserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }); + AuthUserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + required int avatarColor, + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email), + avatarColor = Value(avatarColor); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? isAdmin, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + Expression? quotaSizeInBytes, + Expression? quotaUsageInBytes, + Expression? pinCode, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (isAdmin != null) 'is_admin': isAdmin, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes, + if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes, + if (pinCode != null) 'pin_code': pinCode, + }); + } + + AuthUserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? isAdmin, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + Value? quotaSizeInBytes, + Value? quotaUsageInBytes, + Value? pinCode, + }) { + return AuthUserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode ?? this.pinCode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + if (quotaSizeInBytes.present) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes.value); + } + if (quotaUsageInBytes.present) { + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes.value); + } + if (pinCode.present) { + map['pin_code'] = Variable(pinCode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("in_timeline" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final bool inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + bool? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn dateTimeOriginal = + GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final DateTime? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson( + json['dateTimeOriginal'], + ), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_saved" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime? deletedAt; + final String ownerId; + final int type; + final String data; + final bool isSaved; + final DateTime memoryAt; + final DateTime? seenAt; + final DateTime? showAt; + final DateTime? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + bool? isSaved, + DateTime? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required DateTime memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES memory_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_hidden" IN (0, 1))', + ), + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final bool isFavorite; + final bool isHidden; + final String? color; + final DateTime? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + bool? isFavorite, + bool? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required bool isFavorite, + required bool isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class AssetFaceEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetFaceEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES person_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AssetFaceEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = Variable(personId); + } + map['image_width'] = Variable(imageWidth); + map['image_height'] = Variable(imageHeight); + map['bounding_box_x1'] = Variable(boundingBoxX1); + map['bounding_box_y1'] = Variable(boundingBoxY1); + map['bounding_box_x2'] = Variable(boundingBoxX2); + map['bounding_box_y2'] = Variable(boundingBoxY2); + map['source_type'] = Variable(sourceType); + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + }; + } + + AssetFaceEntityData copyWith({ + String? id, + String? assetId, + Value personId = const Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + }) => AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + AssetFaceEntityData copyWithCompanion(AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType); +} + +class AssetFaceEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value personId; + final Value imageWidth; + final Value imageHeight; + final Value boundingBoxX1; + final Value boundingBoxY1; + final Value boundingBoxX2; + final Value boundingBoxY2; + final Value sourceType; + const AssetFaceEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.personId = const Value.absent(), + this.imageWidth = const Value.absent(), + this.imageHeight = const Value.absent(), + this.boundingBoxX1 = const Value.absent(), + this.boundingBoxY1 = const Value.absent(), + this.boundingBoxX2 = const Value.absent(), + this.boundingBoxY2 = const Value.absent(), + this.sourceType = const Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + }) : id = Value(id), + assetId = Value(assetId), + imageWidth = Value(imageWidth), + imageHeight = Value(imageHeight), + boundingBoxX1 = Value(boundingBoxX1), + boundingBoxY1 = Value(boundingBoxY1), + boundingBoxX2 = Value(boundingBoxX2), + boundingBoxY2 = Value(boundingBoxY2), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? personId, + Expression? imageWidth, + Expression? imageHeight, + Expression? boundingBoxX1, + Expression? boundingBoxY1, + Expression? boundingBoxX2, + Expression? boundingBoxY2, + Expression? sourceType, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + }) { + return AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } +} + +class StoreEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StoreEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stringValue = GeneratedColumn( + 'string_value', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn intValue = GeneratedColumn( + 'int_value', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + @override + List get $columns => [id, stringValue, intValue]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'store_entity'; + @override + Set get $primaryKey => {id}; + @override + StoreEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StoreEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + stringValue: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}string_value'], + ), + intValue: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}int_value'], + ), + ); + } + + @override + StoreEntity createAlias(String alias) { + return StoreEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StoreEntityData extends DataClass implements Insertable { + final int id; + final String? stringValue; + final int? intValue; + const StoreEntityData({required this.id, this.stringValue, this.intValue}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || stringValue != null) { + map['string_value'] = Variable(stringValue); + } + if (!nullToAbsent || intValue != null) { + map['int_value'] = Variable(intValue); + } + return map; + } + + factory StoreEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StoreEntityData( + id: serializer.fromJson(json['id']), + stringValue: serializer.fromJson(json['stringValue']), + intValue: serializer.fromJson(json['intValue']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'stringValue': serializer.toJson(stringValue), + 'intValue': serializer.toJson(intValue), + }; + } + + StoreEntityData copyWith({ + int? id, + Value stringValue = const Value.absent(), + Value intValue = const Value.absent(), + }) => StoreEntityData( + id: id ?? this.id, + stringValue: stringValue.present ? stringValue.value : this.stringValue, + intValue: intValue.present ? intValue.value : this.intValue, + ); + StoreEntityData copyWithCompanion(StoreEntityCompanion data) { + return StoreEntityData( + id: data.id.present ? data.id.value : this.id, + stringValue: data.stringValue.present + ? data.stringValue.value + : this.stringValue, + intValue: data.intValue.present ? data.intValue.value : this.intValue, + ); + } + + @override + String toString() { + return (StringBuffer('StoreEntityData(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, stringValue, intValue); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StoreEntityData && + other.id == this.id && + other.stringValue == this.stringValue && + other.intValue == this.intValue); +} + +class StoreEntityCompanion extends UpdateCompanion { + final Value id; + final Value stringValue; + final Value intValue; + const StoreEntityCompanion({ + this.id = const Value.absent(), + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }); + StoreEntityCompanion.insert({ + required int id, + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }) : id = Value(id); + static Insertable custom({ + Expression? id, + Expression? stringValue, + Expression? intValue, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (stringValue != null) 'string_value': stringValue, + if (intValue != null) 'int_value': intValue, + }); + } + + StoreEntityCompanion copyWith({ + Value? id, + Value? stringValue, + Value? intValue, + }) { + return StoreEntityCompanion( + id: id ?? this.id, + stringValue: stringValue ?? this.stringValue, + intValue: intValue ?? this.intValue, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (stringValue.present) { + map['string_value'] = Variable(stringValue.value); + } + if (intValue.present) { + map['int_value'] = Variable(intValue.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StoreEntityCompanion(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } +} + +class TrashedLocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + TrashedLocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'trashed_local_asset_entity'; + @override + Set get $primaryKey => {id, albumId}; + @override + TrashedLocalAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TrashedLocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + ); + } + + @override + TrashedLocalAssetEntity createAlias(String alias) { + return TrashedLocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class TrashedLocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String albumId; + final String? checksum; + final bool isFavorite; + final int orientation; + const TrashedLocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.albumId, + this.checksum, + required this.isFavorite, + required this.orientation, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + return map; + } + + factory TrashedLocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TrashedLocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + albumId: serializer.fromJson(json['albumId']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'albumId': serializer.toJson(albumId), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + }; + } + + TrashedLocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? albumId, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + }) => TrashedLocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + TrashedLocalAssetEntityData copyWithCompanion( + TrashedLocalAssetEntityCompanion data, + ) { + return TrashedLocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + ); + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TrashedLocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.albumId == this.albumId && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation); +} + +class TrashedLocalAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value albumId; + final Value checksum; + final Value isFavorite; + final Value orientation; + const TrashedLocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.albumId = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }); + TrashedLocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String albumId, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + albumId = Value(albumId); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? albumId, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (albumId != null) 'album_id': albumId, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + }); + } + + TrashedLocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? albumId, + Value? checksum, + Value? isFavorite, + Value? orientation, + }) { + return TrashedLocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV13 extends GeneratedDatabase { + DatabaseAtV13(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index idxRemoteAssetOwnerChecksum = Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + late final Index uQRemoteAssetsOwnerChecksum = Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + late final Index uQRemoteAssetsOwnerLibraryChecksum = Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final AuthUserEntity authUserEntity = AuthUserEntity(this); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); + late final StoreEntity storeEntity = StoreEntity(this); + late final TrashedLocalAssetEntity trashedLocalAssetEntity = + TrashedLocalAssetEntity(this); + late final Index idxLatLng = Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + late final Index idxTrashedLocalAssetChecksum = Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + late final Index idxTrashedLocalAssetAlbum = Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + idxLatLng, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + ]; + @override + int get schemaVersion => 13; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/fixtures/sync_stream.stub.dart b/mobile/test/fixtures/sync_stream.stub.dart index 23c750d6d9..523984f966 100644 --- a/mobile/test/fixtures/sync_stream.stub.dart +++ b/mobile/test/fixtures/sync_stream.stub.dart @@ -81,4 +81,67 @@ abstract final class SyncStreamStub { data: SyncMemoryAssetDeleteV1(assetId: "asset-2", memoryId: "memory-1"), ack: "8", ); + + static final assetDeleteV1 = SyncEvent( + type: SyncEntityType.assetDeleteV1, + data: SyncAssetDeleteV1(assetId: "remote-asset"), + ack: "asset-delete-ack", + ); + + static SyncEvent assetTrashed({ + required String id, + required String checksum, + required String ack, + DateTime? trashedAt, + }) { + return _assetV1( + id: id, + checksum: checksum, + deletedAt: trashedAt ?? DateTime(2025, 1, 1), + ack: ack, + ); + } + + static SyncEvent assetModified({ + required String id, + required String checksum, + required String ack, + }) { + return _assetV1( + id: id, + checksum: checksum, + deletedAt: null, + ack: ack, + ); + } + + static SyncEvent _assetV1({ + required String id, + required String checksum, + required DateTime? deletedAt, + required String ack, + }) { + return SyncEvent( + type: SyncEntityType.assetV1, + data: SyncAssetV1( + checksum: checksum, + deletedAt: deletedAt, + duration: '0', + fileCreatedAt: DateTime(2025), + fileModifiedAt: DateTime(2025, 1, 2), + id: id, + isFavorite: false, + libraryId: null, + livePhotoVideoId: null, + localDateTime: DateTime(2025, 1, 3), + originalFileName: '$id.jpg', + ownerId: 'owner', + stackId: null, + thumbhash: null, + type: AssetTypeEnum.IMAGE, + visibility: AssetVisibility.timeline, + ), + ack: ack, + ); + } } diff --git a/mobile/test/infrastructure/repositories/store_repository_test.dart b/mobile/test/infrastructure/repositories/store_repository_test.dart index f6424beabc..18d41e32e0 100644 --- a/mobile/test/infrastructure/repositories/store_repository_test.dart +++ b/mobile/test/infrastructure/repositories/store_repository_test.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter_test/flutter_test.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; @@ -99,7 +101,7 @@ void main() { final count = await db.storeValues.count(); expect(count, isNot(isZero)); await sut.deleteAll(); - expectLater(await db.storeValues.count(), isZero); + unawaited(expectLater(await db.storeValues.count(), isZero)); }); }); @@ -124,29 +126,31 @@ void main() { test('watch()', () async { final stream = sut.watch(StoreKey.version); - expectLater(stream, emitsInOrder([_kTestVersion, _kTestVersion + 10])); + unawaited(expectLater(stream, emitsInOrder([_kTestVersion, _kTestVersion + 10]))); await pumpEventQueue(); await sut.upsert(StoreKey.version, _kTestVersion + 10); }); test('watchAll()', () async { final stream = sut.watchAll(); - expectLater( - stream, - emitsInOrder([ - [ - const StoreDto(StoreKey.version, _kTestVersion), - StoreDto(StoreKey.backupFailedSince, _kTestBackupFailed), - const StoreDto(StoreKey.accessToken, _kTestAccessToken), - const StoreDto(StoreKey.colorfulInterface, _kTestColorfulInterface), - ], - [ - const StoreDto(StoreKey.version, _kTestVersion + 10), - StoreDto(StoreKey.backupFailedSince, _kTestBackupFailed), - const StoreDto(StoreKey.accessToken, _kTestAccessToken), - const StoreDto(StoreKey.colorfulInterface, _kTestColorfulInterface), - ], - ]), + unawaited( + expectLater( + stream, + emitsInOrder([ + [ + const StoreDto(StoreKey.version, _kTestVersion), + StoreDto(StoreKey.backupFailedSince, _kTestBackupFailed), + const StoreDto(StoreKey.accessToken, _kTestAccessToken), + const StoreDto(StoreKey.colorfulInterface, _kTestColorfulInterface), + ], + [ + const StoreDto(StoreKey.version, _kTestVersion + 10), + StoreDto(StoreKey.backupFailedSince, _kTestBackupFailed), + const StoreDto(StoreKey.accessToken, _kTestAccessToken), + const StoreDto(StoreKey.colorfulInterface, _kTestColorfulInterface), + ], + ]), + ), ); await sut.upsert(StoreKey.version, _kTestVersion + 10); }); diff --git a/mobile/test/infrastructure/repositories/sync_api_repository_test.dart b/mobile/test/infrastructure/repositories/sync_api_repository_test.dart index 467e19bf3f..660b8206bb 100644 --- a/mobile/test/infrastructure/repositories/sync_api_repository_test.dart +++ b/mobile/test/infrastructure/repositories/sync_api_repository_test.dart @@ -80,6 +80,7 @@ void main() { int onDataCallCount = 0; bool abortWasCalledInCallback = false; List receivedEventsBatch1 = []; + final Completer firstBatchReceived = Completer(); Future onDataCallback(List events, Function() abort, Function() _) async { onDataCallCount++; @@ -87,6 +88,7 @@ void main() { receivedEventsBatch1 = events; abort(); abortWasCalledInCallback = true; + firstBatchReceived.complete(); } else { fail("onData called more than once after abort was invoked"); } @@ -94,7 +96,8 @@ void main() { final streamChangesFuture = streamChanges(onDataCallback); - await pumpEventQueue(); + // Give the stream subscription time to start (longer delay to account for mock delay) + await Future.delayed(const Duration(milliseconds: 50)); for (int i = 0; i < testBatchSize; i++) { responseStreamController.add( @@ -104,6 +107,11 @@ void main() { ); } + await firstBatchReceived.future.timeout( + const Duration(seconds: 5), + onTimeout: () => fail('First batch was not processed within timeout'), + ); + for (int i = testBatchSize; i < testBatchSize * 2; i++) { responseStreamController.add( utf8.encode( @@ -124,12 +132,14 @@ void main() { test('streamChanges does not process remaining lines in finally block if aborted', () async { int onDataCallCount = 0; bool abortWasCalledInCallback = false; + final Completer firstBatchReceived = Completer(); Future onDataCallback(List events, Function() abort, Function() _) async { onDataCallCount++; if (onDataCallCount == 1) { abort(); abortWasCalledInCallback = true; + firstBatchReceived.complete(); } else { fail("onData called more than once after abort was invoked"); } @@ -137,7 +147,7 @@ void main() { final streamChangesFuture = streamChanges(onDataCallback); - await pumpEventQueue(); + await Future.delayed(const Duration(milliseconds: 50)); for (int i = 0; i < testBatchSize; i++) { responseStreamController.add( @@ -147,6 +157,11 @@ void main() { ); } + await firstBatchReceived.future.timeout( + const Duration(seconds: 5), + onTimeout: () => fail('First batch was not processed within timeout'), + ); + // emit a single event to skip batching and trigger finally responseStreamController.add( utf8.encode( @@ -166,13 +181,17 @@ void main() { int onDataCallCount = 0; List receivedEventsBatch1 = []; List receivedEventsBatch2 = []; + final Completer firstBatchReceived = Completer(); + final Completer secondBatchReceived = Completer(); Future onDataCallback(List events, Function() _, Function() __) async { onDataCallCount++; if (onDataCallCount == 1) { receivedEventsBatch1 = events; + firstBatchReceived.complete(); } else if (onDataCallCount == 2) { receivedEventsBatch2 = events; + secondBatchReceived.complete(); } else { fail("onData called more than expected"); } @@ -180,7 +199,7 @@ void main() { final streamChangesFuture = streamChanges(onDataCallback); - await pumpEventQueue(); + await Future.delayed(const Duration(milliseconds: 50)); // Batch 1 for (int i = 0; i < testBatchSize; i++) { @@ -191,7 +210,11 @@ void main() { ); } - // Partial Batch 2 + await firstBatchReceived.future.timeout( + const Duration(seconds: 5), + onTimeout: () => fail('First batch was not processed within timeout'), + ); + responseStreamController.add( utf8.encode( _createJsonLine(SyncEntityType.userDeleteV1.toString(), SyncUserDeleteV1(userId: "user100").toJson(), 'ack100'), @@ -199,6 +222,12 @@ void main() { ); await responseStreamController.close(); + + await secondBatchReceived.future.timeout( + const Duration(seconds: 5), + onTimeout: () => fail('Second batch was not processed within timeout'), + ); + await expectLater(streamChangesFuture, completes); expect(onDataCallCount, 2); @@ -217,7 +246,7 @@ void main() { final streamChangesFuture = streamChanges(onDataCallback); - await pumpEventQueue(); + await Future.delayed(const Duration(milliseconds: 50)); responseStreamController.add( utf8.encode( diff --git a/mobile/test/infrastructure/repository.mock.dart b/mobile/test/infrastructure/repository.mock.dart index 1b66451dda..becfafe33d 100644 --- a/mobile/test/infrastructure/repository.mock.dart +++ b/mobile/test/infrastructure/repository.mock.dart @@ -1,3 +1,4 @@ +import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/device_asset.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; @@ -7,9 +8,11 @@ import 'package:immich_mobile/infrastructure/repositories/storage.repository.dar import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart'; import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; +import 'package:immich_mobile/repositories/upload.repository.dart'; import 'package:mocktail/mocktail.dart'; class MockStoreRepository extends Mock implements IsarStoreRepository {} @@ -30,8 +33,16 @@ class MockRemoteAlbumRepository extends Mock implements DriftRemoteAlbumReposito class MockLocalAssetRepository extends Mock implements DriftLocalAssetRepository {} +class MockDriftLocalAssetRepository extends Mock implements DriftLocalAssetRepository {} + +class MockTrashedLocalAssetRepository extends Mock implements DriftTrashedLocalAssetRepository {} + class MockStorageRepository extends Mock implements StorageRepository {} +class MockDriftBackupRepository extends Mock implements DriftBackupRepository {} + +class MockUploadRepository extends Mock implements UploadRepository {} + // API Repos class MockUserApiRepository extends Mock implements UserApiRepository {} diff --git a/mobile/test/mocks/asset_entity.mock.dart b/mobile/test/mocks/asset_entity.mock.dart new file mode 100644 index 0000000000..fdea58315d --- /dev/null +++ b/mobile/test/mocks/asset_entity.mock.dart @@ -0,0 +1,4 @@ +import 'package:mocktail/mocktail.dart'; +import 'package:photo_manager/photo_manager.dart'; + +class MockAssetEntity extends Mock implements AssetEntity {} diff --git a/mobile/test/modules/activity/activities_page_test.dart b/mobile/test/modules/activity/activities_page_test.dart index 05eac98111..39350530ea 100644 --- a/mobile/test/modules/activity/activities_page_test.dart +++ b/mobile/test/modules/activity/activities_page_test.dart @@ -64,9 +64,9 @@ void main() { TestUtils.init(); db = await TestUtils.initIsar(); await StoreService.init(storeRepository: IsarStoreRepository(db)); - Store.put(StoreKey.currentUser, UserStub.admin); - Store.put(StoreKey.serverEndpoint, ''); - Store.put(StoreKey.accessToken, ''); + await Store.put(StoreKey.currentUser, UserStub.admin); + await Store.put(StoreKey.serverEndpoint, ''); + await Store.put(StoreKey.accessToken, ''); }); setUp(() async { diff --git a/mobile/test/modules/activity/activity_provider_test.dart b/mobile/test/modules/activity/activity_provider_test.dart index 7964b43cad..84eba62b70 100644 --- a/mobile/test/modules/activity/activity_provider_test.dart +++ b/mobile/test/modules/activity/activity_provider_test.dart @@ -33,6 +33,7 @@ final _activities = [ void main() { late ActivityServiceMock activityMock; late ActivityStatisticsMock activityStatisticsMock; + late ActivityStatisticsMock albumActivityStatisticsMock; late ProviderContainer container; late AlbumActivityProvider provider; late ListenerMock>> listener; @@ -44,17 +45,23 @@ void main() { setUp(() async { activityMock = ActivityServiceMock(); activityStatisticsMock = ActivityStatisticsMock(); + albumActivityStatisticsMock = ActivityStatisticsMock(); + container = TestUtils.createContainer( overrides: [ activityServiceProvider.overrideWith((ref) => activityMock), activityStatisticsProvider('test-album', 'test-asset').overrideWith(() => activityStatisticsMock), + activityStatisticsProvider('test-album').overrideWith(() => albumActivityStatisticsMock), ], ); // Mock values + when(() => activityStatisticsMock.build(any(), any())).thenReturn(0); + when(() => albumActivityStatisticsMock.build(any())).thenReturn(0); when( () => activityMock.getAllActivities('test-album', assetId: 'test-asset'), ).thenAnswer((_) async => [..._activities]); + when(() => activityMock.getAllActivities('test-album')).thenAnswer((_) async => [..._activities]); // Init and wait for providers future to complete provider = albumActivityProvider('test-album', 'test-asset'); @@ -89,6 +96,10 @@ void main() { () => activityMock.addActivity('test-album', ActivityType.like, assetId: 'test-asset'), ).thenAnswer((_) async => AsyncData(like)); + final albumProvider = albumActivityProvider('test-album'); + container.read(albumProvider.notifier); + await container.read(albumProvider.future); + await container.read(provider.notifier).addLike(); verify(() => activityMock.addActivity('test-album', ActivityType.like, assetId: 'test-asset')); @@ -99,6 +110,11 @@ void main() { // Never bump activity count for new likes verifyNever(() => activityStatisticsMock.addActivity()); + verifyNever(() => albumActivityStatisticsMock.addActivity()); + + final albumActivities = container.read(albumProvider).requireValue; + expect(albumActivities, hasLength(5)); + expect(albumActivities, contains(like)); }); test('Like failed', () async { @@ -107,6 +123,10 @@ void main() { () => activityMock.addActivity('test-album', ActivityType.like, assetId: 'test-asset'), ).thenAnswer((_) async => AsyncError(Exception('Mock'), StackTrace.current)); + final albumProvider = albumActivityProvider('test-album'); + container.read(albumProvider.notifier); + await container.read(albumProvider.future); + await container.read(provider.notifier).addLike(); verify(() => activityMock.addActivity('test-album', ActivityType.like, assetId: 'test-asset')); @@ -114,6 +134,12 @@ void main() { final activities = await container.read(provider.future); expect(activities, hasLength(4)); expect(activities, isNot(contains(like))); + + verifyNever(() => albumActivityStatisticsMock.addActivity()); + + final albumActivities = container.read(albumProvider).requireValue; + expect(albumActivities, hasLength(4)); + expect(albumActivities, isNot(contains(like))); }); }); @@ -130,6 +156,7 @@ void main() { expect(activities, isNot(anyElement(predicate((Activity a) => a.id == '3')))); verifyNever(() => activityStatisticsMock.removeActivity()); + verifyNever(() => albumActivityStatisticsMock.removeActivity()); }); test('Remove Like failed', () async { @@ -140,6 +167,9 @@ void main() { final activities = await container.read(provider.future); expect(activities, hasLength(4)); expect(activities, anyElement(predicate((Activity a) => a.id == '3'))); + + verifyNever(() => activityStatisticsMock.removeActivity()); + verifyNever(() => albumActivityStatisticsMock.removeActivity()); }); test('Comment successfully removed', () async { @@ -151,23 +181,35 @@ void main() { expect(activities, isNot(anyElement(predicate((Activity a) => a.id == '1')))); verify(() => activityStatisticsMock.removeActivity()); + verify(() => albumActivityStatisticsMock.removeActivity()); + }); + + test('Removes activity from album state when asset scoped', () async { + when(() => activityMock.removeActivity('3')).thenAnswer((_) async => true); + when(() => activityMock.getAllActivities('test-album')).thenAnswer((_) async => [..._activities]); + + final albumProvider = albumActivityProvider('test-album'); + container.read(albumProvider.notifier); + await container.read(albumProvider.future); + + await container.read(provider.notifier).removeActivity('3'); + + final assetActivities = container.read(provider).requireValue; + final albumActivities = container.read(albumProvider).requireValue; + + expect(assetActivities, hasLength(3)); + expect(assetActivities, isNot(anyElement(predicate((Activity a) => a.id == '3')))); + + expect(albumActivities, hasLength(3)); + expect(albumActivities, isNot(anyElement(predicate((Activity a) => a.id == '3')))); + + verify(() => activityMock.removeActivity('3')); + verifyNever(() => activityStatisticsMock.removeActivity()); + verifyNever(() => albumActivityStatisticsMock.removeActivity()); }); }); group('addComment()', () { - late ActivityStatisticsMock albumActivityStatisticsMock; - - setUp(() { - albumActivityStatisticsMock = ActivityStatisticsMock(); - container = TestUtils.createContainer( - overrides: [ - activityServiceProvider.overrideWith((ref) => activityMock), - activityStatisticsProvider('test-album', 'test-asset').overrideWith(() => activityStatisticsMock), - activityStatisticsProvider('test-album').overrideWith(() => albumActivityStatisticsMock), - ], - ); - }); - test('Comment successfully added', () async { final comment = Activity( id: '5', @@ -178,6 +220,10 @@ void main() { assetId: 'test-asset', ); + final albumProvider = albumActivityProvider('test-album'); + container.read(albumProvider.notifier); + await container.read(albumProvider.future); + when( () => activityMock.addActivity( 'test-album', @@ -206,6 +252,10 @@ void main() { verify(() => activityStatisticsMock.addActivity()); verify(() => albumActivityStatisticsMock.addActivity()); + + final albumActivities = container.read(albumProvider).requireValue; + expect(albumActivities, hasLength(5)); + expect(albumActivities, contains(comment)); }); test('Comment successfully added without assetId', () async { @@ -225,6 +275,8 @@ void main() { when(() => activityMock.getAllActivities('test-album')).thenAnswer((_) async => [..._activities]); final albumProvider = albumActivityProvider('test-album'); + container.read(albumProvider.notifier); + await container.read(albumProvider.future); await container.read(albumProvider.notifier).addComment('Test-Comment'); verify( @@ -258,6 +310,10 @@ void main() { ), ).thenAnswer((_) async => AsyncError(Exception('Error'), StackTrace.current)); + final albumProvider = albumActivityProvider('test-album'); + container.read(albumProvider.notifier); + await container.read(albumProvider.future); + await container.read(provider.notifier).addComment('Test-Comment'); final activities = await container.read(provider.future); @@ -266,6 +322,10 @@ void main() { verifyNever(() => activityStatisticsMock.addActivity()); verifyNever(() => albumActivityStatisticsMock.addActivity()); + + final albumActivities = container.read(albumProvider).requireValue; + expect(albumActivities, hasLength(4)); + expect(albumActivities, isNot(contains(comment))); }); }); } diff --git a/mobile/test/modules/activity/activity_text_field_test.dart b/mobile/test/modules/activity/activity_text_field_test.dart index 1163330c54..8f28b7f28e 100644 --- a/mobile/test/modules/activity/activity_text_field_test.dart +++ b/mobile/test/modules/activity/activity_text_field_test.dart @@ -35,8 +35,8 @@ void main() { TestUtils.init(); db = await TestUtils.initIsar(); await StoreService.init(storeRepository: IsarStoreRepository(db)); - Store.put(StoreKey.currentUser, UserStub.admin); - Store.put(StoreKey.serverEndpoint, ''); + await Store.put(StoreKey.currentUser, UserStub.admin); + await Store.put(StoreKey.serverEndpoint, ''); }); setUp(() { diff --git a/mobile/test/modules/activity/activity_tile_test.dart b/mobile/test/modules/activity/activity_tile_test.dart index eb4bb25848..718dfcce21 100644 --- a/mobile/test/modules/activity/activity_tile_test.dart +++ b/mobile/test/modules/activity/activity_tile_test.dart @@ -31,9 +31,9 @@ void main() { db = await TestUtils.initIsar(); // For UserCircleAvatar await StoreService.init(storeRepository: IsarStoreRepository(db)); - Store.put(StoreKey.currentUser, UserStub.admin); - Store.put(StoreKey.serverEndpoint, ''); - Store.put(StoreKey.accessToken, ''); + await Store.put(StoreKey.currentUser, UserStub.admin); + await Store.put(StoreKey.serverEndpoint, ''); + await Store.put(StoreKey.accessToken, ''); }); setUp(() { diff --git a/mobile/test/modules/activity/dismissible_activity_test.dart b/mobile/test/modules/activity/dismissible_activity_test.dart index e5f6258ee9..32516e73ea 100644 --- a/mobile/test/modules/activity/dismissible_activity_test.dart +++ b/mobile/test/modules/activity/dismissible_activity_test.dart @@ -29,7 +29,10 @@ void main() { }); testWidgets('Returns a Dismissible', (tester) async { - await tester.pumpConsumerWidget(DismissibleActivity('1', ActivityTile(activity)), overrides: overrides); + await tester.pumpConsumerWidget( + DismissibleActivity('1', ActivityTile(activity), onDismiss: (_) {}), + overrides: overrides, + ); expect(find.byType(Dismissible), findsOneWidget); }); @@ -81,20 +84,16 @@ void main() { testWidgets('No delete dialog if onDismiss is not set', (tester) async { await tester.pumpConsumerWidget(DismissibleActivity('1', ActivityTile(activity)), overrides: overrides); - final dismissible = find.byType(Dismissible); - await tester.drag(dismissible, const Offset(500, 0)); - await tester.pumpAndSettle(); - + // When onDismiss is not set, the widget should not be wrapped by a Dismissible + expect(find.byType(Dismissible), findsNothing); expect(find.byType(ConfirmDialog), findsNothing); }); testWidgets('No icon for background if onDismiss is not set', (tester) async { await tester.pumpConsumerWidget(DismissibleActivity('1', ActivityTile(activity)), overrides: overrides); - final dismissible = find.byType(Dismissible); - await tester.drag(dismissible, const Offset(-500, 0)); - await tester.pumpAndSettle(); - + // No Dismissible should exist when onDismiss is not provided, so no delete icon either + expect(find.byType(Dismissible), findsNothing); expect(find.byIcon(Icons.delete_sweep_rounded), findsNothing); }); } diff --git a/mobile/test/modules/utils/async_mutex_test.dart b/mobile/test/modules/utils/async_mutex_test.dart index d50567721b..08cafeb307 100644 --- a/mobile/test/modules/utils/async_mutex_test.dart +++ b/mobile/test/modules/utils/async_mutex_test.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter_test/flutter_test.dart'; import 'package:immich_mobile/utils/async_mutex.dart'; @@ -7,11 +9,11 @@ void main() { AsyncMutex lock = AsyncMutex(); List events = []; expect(0, lock.enqueued); - lock.run(() => Future.delayed(const Duration(milliseconds: 10), () => events.add(1))); + unawaited(lock.run(() => Future.delayed(const Duration(milliseconds: 10), () => events.add(1)))); expect(1, lock.enqueued); - lock.run(() => Future.delayed(const Duration(milliseconds: 3), () => events.add(2))); + unawaited(lock.run(() => Future.delayed(const Duration(milliseconds: 3), () => events.add(2)))); expect(2, lock.enqueued); - lock.run(() => Future.delayed(const Duration(milliseconds: 1), () => events.add(3))); + unawaited(lock.run(() => Future.delayed(const Duration(milliseconds: 1), () => events.add(3)))); expect(3, lock.enqueued); await lock.run(() => Future.delayed(const Duration(milliseconds: 10), () => events.add(4))); expect(0, lock.enqueued); diff --git a/mobile/test/services/hash_service_test.dart b/mobile/test/services/hash_service_test.dart index 74b8575e40..9429d434b0 100644 --- a/mobile/test/services/hash_service_test.dart +++ b/mobile/test/services/hash_service_test.dart @@ -12,16 +12,14 @@ import 'package:immich_mobile/infrastructure/repositories/device_asset.repositor import 'package:immich_mobile/services/background.service.dart'; import 'package:immich_mobile/services/hash.service.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:photo_manager/photo_manager.dart'; import '../fixtures/asset.stub.dart'; import '../infrastructure/repository.mock.dart'; import '../service.mocks.dart'; +import '../mocks/asset_entity.mock.dart'; class MockAsset extends Mock implements Asset {} -class MockAssetEntity extends Mock implements AssetEntity {} - void main() { late HashService sut; late BackgroundService mockBackgroundService; diff --git a/mobile/test/services/upload.service_test.dart b/mobile/test/services/upload.service_test.dart new file mode 100644 index 0000000000..d33126782f --- /dev/null +++ b/mobile/test/services/upload.service_test.dart @@ -0,0 +1,168 @@ +import 'dart:io'; + +import 'package:drift/drift.dart' hide isNull, isNotNull; +import 'package:drift/native.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/services/store.service.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/services/upload.service.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../domain/service.mock.dart'; +import '../fixtures/asset.stub.dart'; +import '../infrastructure/repository.mock.dart'; +import '../repository.mocks.dart'; +import '../mocks/asset_entity.mock.dart'; + +void main() { + late UploadService sut; + late MockUploadRepository mockUploadRepository; + late MockDriftBackupRepository mockBackupRepository; + late MockStorageRepository mockStorageRepository; + late MockDriftLocalAssetRepository mockLocalAssetRepository; + late MockAppSettingsService mockAppSettingsService; + late MockAssetMediaRepository mockAssetMediaRepository; + late Drift db; + + setUpAll(() async { + registerFallbackValue(AppSettingsEnum.useCellularForUploadPhotos); + + TestWidgetsFlutterBinding.ensureInitialized(); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( + const MethodChannel('plugins.flutter.io/path_provider'), + (MethodCall methodCall) async => 'test', + ); + db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); + await StoreService.init(storeRepository: DriftStoreRepository(db)); + + await Store.put(StoreKey.serverEndpoint, 'http://test-server.com'); + await Store.put(StoreKey.deviceId, 'test-device-id'); + }); + + setUp(() { + mockUploadRepository = MockUploadRepository(); + mockBackupRepository = MockDriftBackupRepository(); + mockStorageRepository = MockStorageRepository(); + mockLocalAssetRepository = MockDriftLocalAssetRepository(); + mockAppSettingsService = MockAppSettingsService(); + mockAssetMediaRepository = MockAssetMediaRepository(); + + when(() => mockAppSettingsService.getSetting(AppSettingsEnum.useCellularForUploadVideos)).thenReturn(false); + when(() => mockAppSettingsService.getSetting(AppSettingsEnum.useCellularForUploadPhotos)).thenReturn(false); + + sut = UploadService( + mockUploadRepository, + mockBackupRepository, + mockStorageRepository, + mockLocalAssetRepository, + mockAppSettingsService, + mockAssetMediaRepository, + ); + + mockUploadRepository.onUploadStatus = (_) {}; + mockUploadRepository.onTaskProgress = (_) {}; + }); + + tearDown(() { + sut.dispose(); + }); + + group('getUploadTask', () { + test('should call getOriginalFilename from AssetMediaRepository for regular photo', () async { + final asset = LocalAssetStub.image1; + final mockEntity = MockAssetEntity(); + final mockFile = File('/path/to/file.jpg'); + + when(() => mockEntity.isLivePhoto).thenReturn(false); + when(() => mockStorageRepository.getAssetEntityForAsset(asset)).thenAnswer((_) async => mockEntity); + when(() => mockStorageRepository.getFileForAsset(asset.id)).thenAnswer((_) async => mockFile); + when(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).thenAnswer((_) async => 'OriginalPhoto.jpg'); + + final task = await sut.getUploadTask(asset); + + expect(task, isNotNull); + expect(task!.fields['filename'], equals('OriginalPhoto.jpg')); + verify(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).called(1); + }); + + test('should call getOriginalFilename when original filename is null', () async { + final asset = LocalAssetStub.image2; + final mockEntity = MockAssetEntity(); + final mockFile = File('/path/to/file.jpg'); + + when(() => mockEntity.isLivePhoto).thenReturn(false); + when(() => mockStorageRepository.getAssetEntityForAsset(asset)).thenAnswer((_) async => mockEntity); + when(() => mockStorageRepository.getFileForAsset(asset.id)).thenAnswer((_) async => mockFile); + when(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).thenAnswer((_) async => null); + + final task = await sut.getUploadTask(asset); + + expect(task, isNotNull); + expect(task!.fields['filename'], equals(asset.name)); + verify(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).called(1); + }); + + test('should call getOriginalFilename for live photo', () async { + final asset = LocalAssetStub.image1; + final mockEntity = MockAssetEntity(); + final mockFile = File('/path/to/file.mov'); + + when(() => mockEntity.isLivePhoto).thenReturn(true); + when(() => mockStorageRepository.getAssetEntityForAsset(asset)).thenAnswer((_) async => mockEntity); + when(() => mockStorageRepository.getMotionFileForAsset(asset)).thenAnswer((_) async => mockFile); + when( + () => mockAssetMediaRepository.getOriginalFilename(asset.id), + ).thenAnswer((_) async => 'OriginalLivePhoto.HEIC'); + + final task = await sut.getUploadTask(asset); + expect(task, isNotNull); + // For live photos, extension should be changed to match the video file + expect(task!.fields['filename'], equals('OriginalLivePhoto.mov')); + verify(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).called(1); + }); + }); + + group('getLivePhotoUploadTask', () { + test('should call getOriginalFilename for live photo upload task', () async { + final asset = LocalAssetStub.image1; + final mockEntity = MockAssetEntity(); + final mockFile = File('/path/to/livephoto.heic'); + + when(() => mockEntity.isLivePhoto).thenReturn(true); + when(() => mockStorageRepository.getAssetEntityForAsset(asset)).thenAnswer((_) async => mockEntity); + when(() => mockStorageRepository.getFileForAsset(asset.id)).thenAnswer((_) async => mockFile); + when( + () => mockAssetMediaRepository.getOriginalFilename(asset.id), + ).thenAnswer((_) async => 'OriginalLivePhoto.HEIC'); + + final task = await sut.getLivePhotoUploadTask(asset, 'video-id-123'); + + expect(task, isNotNull); + expect(task!.fields['filename'], equals('OriginalLivePhoto.HEIC')); + expect(task.fields['livePhotoVideoId'], equals('video-id-123')); + verify(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).called(1); + }); + + test('should call getOriginalFilename when original filename is null', () async { + final asset = LocalAssetStub.image2; + final mockEntity = MockAssetEntity(); + final mockFile = File('/path/to/fallback.heic'); + + when(() => mockEntity.isLivePhoto).thenReturn(true); + when(() => mockStorageRepository.getAssetEntityForAsset(asset)).thenAnswer((_) async => mockEntity); + when(() => mockStorageRepository.getFileForAsset(asset.id)).thenAnswer((_) async => mockFile); + when(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).thenAnswer((_) async => null); + + final task = await sut.getLivePhotoUploadTask(asset, 'video-id-456'); + expect(task, isNotNull); + // Should fall back to asset.name when original filename is null + expect(task!.fields['filename'], equals(asset.name)); + verify(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).called(1); + }); + }); +} diff --git a/mobile/test/utils/action_button_utils_test.dart b/mobile/test/utils/action_button_utils_test.dart index f8c51173d7..d93d59d3c7 100644 --- a/mobile/test/utils/action_button_utils_test.dart +++ b/mobile/test/utils/action_button_utils_test.dart @@ -82,6 +82,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -112,6 +113,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -127,6 +129,7 @@ void main() { isInLockedView: true, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -145,6 +148,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -161,6 +165,7 @@ void main() { isInLockedView: true, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -177,6 +182,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -195,6 +201,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -211,6 +218,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -227,6 +235,7 @@ void main() { isInLockedView: true, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -243,6 +252,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -259,6 +269,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -277,6 +288,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -293,6 +305,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -309,6 +322,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -327,6 +341,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -343,6 +358,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -359,6 +375,7 @@ void main() { isInLockedView: true, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -366,6 +383,42 @@ void main() { }); }); + group('similar photos button', () { + test('should show when not locked and has remote', () { + final remoteAsset = createRemoteAsset(); + final context = ActionButtonContext( + asset: remoteAsset, + isOwner: true, + isArchived: false, + isTrashEnabled: true, + isInLockedView: false, + isStacked: false, + currentAlbum: null, + advancedTroubleshooting: false, + source: ActionSource.timeline, + ); + + expect(ActionButtonType.similarPhotos.shouldShow(context), isTrue); + }); + + test('should not show when in locked view', () { + final remoteAsset = createRemoteAsset(); + final context = ActionButtonContext( + asset: remoteAsset, + isOwner: true, + isArchived: false, + isTrashEnabled: true, + isInLockedView: true, + currentAlbum: null, + isStacked: false, + advancedTroubleshooting: false, + source: ActionSource.timeline, + ); + + expect(ActionButtonType.similarPhotos.shouldShow(context), isFalse); + }); + }); + group('trash button', () { test('should show when owner, not locked, has remote, and trash enabled', () { final remoteAsset = createRemoteAsset(); @@ -377,6 +430,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -393,6 +447,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -411,6 +466,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -427,6 +483,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -445,6 +502,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -463,6 +521,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -481,6 +540,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -497,6 +557,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -512,6 +573,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -530,6 +592,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -548,6 +611,7 @@ void main() { isInLockedView: false, currentAlbum: album, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -563,6 +627,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -581,6 +646,7 @@ void main() { isInLockedView: false, currentAlbum: album, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -597,6 +663,7 @@ void main() { isInLockedView: false, currentAlbum: album, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -613,6 +680,7 @@ void main() { isInLockedView: false, currentAlbum: album, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -628,6 +696,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -645,6 +714,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: true, + isStacked: false, source: ActionSource.timeline, ); @@ -660,6 +730,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -668,6 +739,59 @@ void main() { }); }); + group('unstack button', () { + test('should show when owner, not locked, has remote, and is stacked', () { + final remoteAsset = createRemoteAsset(); + final context = ActionButtonContext( + asset: remoteAsset, + isOwner: true, + isArchived: false, + isTrashEnabled: true, + isInLockedView: false, + currentAlbum: null, + advancedTroubleshooting: false, + isStacked: true, + source: ActionSource.timeline, + ); + + expect(ActionButtonType.unstack.shouldShow(context), isTrue); + }); + + test('should not show when not stacked', () { + final remoteAsset = createRemoteAsset(); + final context = ActionButtonContext( + asset: remoteAsset, + isOwner: true, + isArchived: false, + isTrashEnabled: true, + isInLockedView: false, + currentAlbum: null, + advancedTroubleshooting: false, + isStacked: false, + source: ActionSource.timeline, + ); + + expect(ActionButtonType.unstack.shouldShow(context), isFalse); + }); + + test('should not show when not owner', () { + final remoteAsset = createRemoteAsset(); + final context = ActionButtonContext( + asset: remoteAsset, + isOwner: false, + isArchived: true, + isTrashEnabled: true, + isInLockedView: false, + currentAlbum: null, + advancedTroubleshooting: false, + isStacked: false, + source: ActionSource.timeline, + ); + + expect(ActionButtonType.unstack.shouldShow(context), isFalse); + }); + }); + group('ActionButtonType.buildButton', () { late BaseAsset asset; late ActionButtonContext context; @@ -682,12 +806,15 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); }); test('should build correct widget for each button type', () { for (final buttonType in ActionButtonType.values) { + var buttonContext = context; + if (buttonType == ActionButtonType.removeFromAlbum) { final album = createRemoteAlbum(); final contextWithAlbum = ActionButtonContext( @@ -698,12 +825,42 @@ void main() { isInLockedView: false, currentAlbum: album, advancedTroubleshooting: false, + isStacked: false, + source: ActionSource.timeline, + ); + final widget = buttonType.buildButton(contextWithAlbum); + expect(widget, isA()); + } else if (buttonType == ActionButtonType.similarPhotos) { + final contextWithAlbum = ActionButtonContext( + asset: createRemoteAsset(), + isOwner: true, + isArchived: false, + isTrashEnabled: true, + isInLockedView: false, + currentAlbum: null, + advancedTroubleshooting: false, + isStacked: false, + source: ActionSource.timeline, + ); + final widget = buttonType.buildButton(contextWithAlbum); + expect(widget, isA()); + } else if (buttonType == ActionButtonType.unstack) { + final album = createRemoteAlbum(); + final contextWithAlbum = ActionButtonContext( + asset: asset, + isOwner: true, + isArchived: false, + isTrashEnabled: true, + isInLockedView: false, + currentAlbum: album, + advancedTroubleshooting: false, + isStacked: true, source: ActionSource.timeline, ); final widget = buttonType.buildButton(contextWithAlbum); expect(widget, isA()); } else { - final widget = buttonType.buildButton(context); + final widget = buttonType.buildButton(buttonContext); expect(widget, isA()); } } @@ -721,6 +878,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -741,6 +899,7 @@ void main() { isInLockedView: false, currentAlbum: album, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -759,6 +918,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -778,6 +938,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); @@ -791,6 +952,7 @@ void main() { isInLockedView: false, currentAlbum: null, advancedTroubleshooting: false, + isStacked: false, source: ActionSource.timeline, ); diff --git a/mobile/test/utils/semver_test.dart b/mobile/test/utils/semver_test.dart new file mode 100644 index 0000000000..8f1958a879 --- /dev/null +++ b/mobile/test/utils/semver_test.dart @@ -0,0 +1,92 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/utils/semver.dart'; + +void main() { + group('SemVer', () { + test('Parses valid semantic version strings correctly', () { + final version = SemVer.fromString('1.2.3'); + expect(version.major, 1); + expect(version.minor, 2); + expect(version.patch, 3); + }); + + test('Throws FormatException for invalid version strings', () { + expect(() => SemVer.fromString('1.2'), throwsFormatException); + expect(() => SemVer.fromString('a.b.c'), throwsFormatException); + expect(() => SemVer.fromString('1.2.3.4'), throwsFormatException); + }); + + test('Compares equal versons correctly', () { + final v1 = SemVer.fromString('1.2.3'); + final v2 = SemVer.fromString('1.2.3'); + expect(v1 == v2, isTrue); + expect(v1 > v2, isFalse); + expect(v1 < v2, isFalse); + }); + + test('Compares major version correctly', () { + final v1 = SemVer.fromString('2.0.0'); + final v2 = SemVer.fromString('1.9.9'); + expect(v1 == v2, isFalse); + expect(v1 > v2, isTrue); + expect(v1 < v2, isFalse); + }); + + test('Compares minor version correctly', () { + final v1 = SemVer.fromString('1.3.0'); + final v2 = SemVer.fromString('1.2.9'); + expect(v1 == v2, isFalse); + expect(v1 > v2, isTrue); + expect(v1 < v2, isFalse); + }); + + test('Compares patch version correctly', () { + final v1 = SemVer.fromString('1.2.4'); + final v2 = SemVer.fromString('1.2.3'); + expect(v1 == v2, isFalse); + expect(v1 > v2, isTrue); + expect(v1 < v2, isFalse); + }); + + test('Gives correct major difference type', () { + final v1 = SemVer.fromString('2.0.0'); + final v2 = SemVer.fromString('1.9.9'); + expect(v1.differenceType(v2), SemVerType.major); + }); + + test('Gives correct minor difference type', () { + final v1 = SemVer.fromString('1.3.0'); + final v2 = SemVer.fromString('1.2.9'); + expect(v1.differenceType(v2), SemVerType.minor); + }); + + test('Gives correct patch difference type', () { + final v1 = SemVer.fromString('1.2.4'); + final v2 = SemVer.fromString('1.2.3'); + expect(v1.differenceType(v2), SemVerType.patch); + }); + + test('Gives null difference type for equal versions', () { + final v1 = SemVer.fromString('1.2.3'); + final v2 = SemVer.fromString('1.2.3'); + expect(v1.differenceType(v2), isNull); + }); + + test('toString returns correct format', () { + final version = SemVer.fromString('1.2.3'); + expect(version.toString(), '1.2.3'); + }); + + test('Parses versions with leading v correctly', () { + final version1 = SemVer.fromString('v1.2.3'); + expect(version1.major, 1); + expect(version1.minor, 2); + expect(version1.patch, 3); + + final version2 = SemVer.fromString('V1.2.3'); + expect(version2.major, 1); + expect(version2.minor, 2); + expect(version2.patch, 3); + }); + }); +} diff --git a/open-api/bin/generate-open-api.sh b/open-api/bin/generate-open-api.sh index 1ce33b96e6..43292089d7 100755 --- a/open-api/bin/generate-open-api.sh +++ b/open-api/bin/generate-open-api.sh @@ -15,7 +15,7 @@ function dart { patch --no-backup-if-mismatch -u api.mustache (`/admin/users/${encodeURIComponent(id)}/sessions`, { + ...opts + })); +} +/** + * Retrieve user statistics */ export function getUserStatisticsAdmin({ id, isFavorite, isTrashed, visibility }: { id: string; @@ -1868,7 +2022,7 @@ export function getUserStatisticsAdmin({ id, isFavorite, isTrashed, visibility } })); } /** - * This endpoint requires the `album.read` permission. + * List all albums */ export function getAllAlbums({ assetId, shared }: { assetId?: string; @@ -1885,7 +2039,7 @@ export function getAllAlbums({ assetId, shared }: { })); } /** - * This endpoint requires the `album.create` permission. + * Create an album */ export function createAlbum({ createAlbumDto }: { createAlbumDto: CreateAlbumDto; @@ -1900,7 +2054,7 @@ export function createAlbum({ createAlbumDto }: { }))); } /** - * This endpoint requires the `albumAsset.create` permission. + * Add assets to albums */ export function addAssetsToAlbums({ key, slug, albumsAddAssetsDto }: { key?: string; @@ -1920,7 +2074,7 @@ export function addAssetsToAlbums({ key, slug, albumsAddAssetsDto }: { }))); } /** - * This endpoint requires the `album.statistics` permission. + * Retrieve album statistics */ export function getAlbumStatistics(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -1931,7 +2085,7 @@ export function getAlbumStatistics(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `album.delete` permission. + * Delete an album */ export function deleteAlbum({ id }: { id: string; @@ -1942,7 +2096,7 @@ export function deleteAlbum({ id }: { })); } /** - * This endpoint requires the `album.read` permission. + * Retrieve an album */ export function getAlbumInfo({ id, key, slug, withoutAssets }: { id: string; @@ -1962,7 +2116,7 @@ export function getAlbumInfo({ id, key, slug, withoutAssets }: { })); } /** - * This endpoint requires the `album.update` permission. + * Update an album */ export function updateAlbumInfo({ id, updateAlbumDto }: { id: string; @@ -1978,7 +2132,7 @@ export function updateAlbumInfo({ id, updateAlbumDto }: { }))); } /** - * This endpoint requires the `albumAsset.delete` permission. + * Remove assets from an album */ export function removeAssetFromAlbum({ id, bulkIdsDto }: { id: string; @@ -1994,7 +2148,7 @@ export function removeAssetFromAlbum({ id, bulkIdsDto }: { }))); } /** - * This endpoint requires the `albumAsset.create` permission. + * Add assets to an album */ export function addAssetsToAlbum({ id, key, slug, bulkIdsDto }: { id: string; @@ -2015,7 +2169,7 @@ export function addAssetsToAlbum({ id, key, slug, bulkIdsDto }: { }))); } /** - * This endpoint requires the `albumUser.delete` permission. + * Remove user from album */ export function removeUserFromAlbum({ id, userId }: { id: string; @@ -2027,7 +2181,7 @@ export function removeUserFromAlbum({ id, userId }: { })); } /** - * This endpoint requires the `albumUser.update` permission. + * Update user role */ export function updateAlbumUser({ id, userId, updateAlbumUserDto }: { id: string; @@ -2041,7 +2195,7 @@ export function updateAlbumUser({ id, userId, updateAlbumUserDto }: { }))); } /** - * This endpoint requires the `albumUser.create` permission. + * Share album with users */ export function addUsersToAlbum({ id, addUsersDto }: { id: string; @@ -2057,7 +2211,7 @@ export function addUsersToAlbum({ id, addUsersDto }: { }))); } /** - * This endpoint requires the `apiKey.read` permission. + * List all API keys */ export function getApiKeys(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -2068,7 +2222,7 @@ export function getApiKeys(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `apiKey.create` permission. + * Create an API key */ export function createApiKey({ apiKeyCreateDto }: { apiKeyCreateDto: ApiKeyCreateDto; @@ -2082,6 +2236,9 @@ export function createApiKey({ apiKeyCreateDto }: { body: apiKeyCreateDto }))); } +/** + * Retrieve the current API key + */ export function getMyApiKey(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; @@ -2091,7 +2248,7 @@ export function getMyApiKey(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `apiKey.delete` permission. + * Delete an API key */ export function deleteApiKey({ id }: { id: string; @@ -2102,7 +2259,7 @@ export function deleteApiKey({ id }: { })); } /** - * This endpoint requires the `apiKey.read` permission. + * Retrieve an API key */ export function getApiKey({ id }: { id: string; @@ -2115,7 +2272,7 @@ export function getApiKey({ id }: { })); } /** - * This endpoint requires the `apiKey.update` permission. + * Update an API key */ export function updateApiKey({ id, apiKeyUpdateDto }: { id: string; @@ -2131,7 +2288,7 @@ export function updateApiKey({ id, apiKeyUpdateDto }: { }))); } /** - * This endpoint requires the `asset.delete` permission. + * Delete assets */ export function deleteAssets({ assetBulkDeleteDto }: { assetBulkDeleteDto: AssetBulkDeleteDto; @@ -2143,7 +2300,7 @@ export function deleteAssets({ assetBulkDeleteDto }: { }))); } /** - * This endpoint requires the `asset.upload` permission. + * Upload asset */ export function uploadAsset({ key, slug, xImmichChecksum, assetMediaCreateDto }: { key?: string; @@ -2167,7 +2324,7 @@ export function uploadAsset({ key, slug, xImmichChecksum, assetMediaCreateDto }: }))); } /** - * This endpoint requires the `asset.update` permission. + * Update assets */ export function updateAssets({ assetBulkUpdateDto }: { assetBulkUpdateDto: AssetBulkUpdateDto; @@ -2179,7 +2336,7 @@ export function updateAssets({ assetBulkUpdateDto }: { }))); } /** - * checkBulkUpload + * Check bulk upload */ export function checkBulkUpload({ assetBulkUploadCheckDto }: { assetBulkUploadCheckDto: AssetBulkUploadCheckDto; @@ -2194,7 +2351,19 @@ export function checkBulkUpload({ assetBulkUploadCheckDto }: { }))); } /** - * getAllUserAssetsByDeviceId + * Copy asset + */ +export function copyAsset({ assetCopyDto }: { + assetCopyDto: AssetCopyDto; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchText("/assets/copy", oazapfts.json({ + ...opts, + method: "PUT", + body: assetCopyDto + }))); +} +/** + * Retrieve assets by device ID */ export function getAllUserAssetsByDeviceId({ deviceId }: { deviceId: string; @@ -2207,7 +2376,7 @@ export function getAllUserAssetsByDeviceId({ deviceId }: { })); } /** - * checkExistingAssets + * Check existing assets */ export function checkExistingAssets({ checkExistingAssetsDto }: { checkExistingAssetsDto: CheckExistingAssetsDto; @@ -2221,6 +2390,9 @@ export function checkExistingAssets({ checkExistingAssetsDto }: { body: checkExistingAssetsDto }))); } +/** + * Run an asset job + */ export function runAssetJobs({ assetJobsDto }: { assetJobsDto: AssetJobsDto; }, opts?: Oazapfts.RequestOpts) { @@ -2231,7 +2403,7 @@ export function runAssetJobs({ assetJobsDto }: { }))); } /** - * This property was deprecated in v1.116.0. This endpoint requires the `asset.read` permission. + * Get random assets */ export function getRandom({ count }: { count?: number; @@ -2246,7 +2418,7 @@ export function getRandom({ count }: { })); } /** - * This endpoint requires the `asset.statistics` permission. + * Get asset statistics */ export function getAssetStatistics({ isFavorite, isTrashed, visibility }: { isFavorite?: boolean; @@ -2265,7 +2437,7 @@ export function getAssetStatistics({ isFavorite, isTrashed, visibility }: { })); } /** - * This endpoint requires the `asset.read` permission. + * Retrieve an asset */ export function getAssetInfo({ id, key, slug }: { id: string; @@ -2283,7 +2455,7 @@ export function getAssetInfo({ id, key, slug }: { })); } /** - * This endpoint requires the `asset.update` permission. + * Update an asset */ export function updateAsset({ id, updateAssetDto }: { id: string; @@ -2299,7 +2471,7 @@ export function updateAsset({ id, updateAssetDto }: { }))); } /** - * This endpoint requires the `asset.read` permission. + * Get asset metadata */ export function getAssetMetadata({ id }: { id: string; @@ -2312,7 +2484,7 @@ export function getAssetMetadata({ id }: { })); } /** - * This endpoint requires the `asset.update` permission. + * Update asset metadata */ export function updateAssetMetadata({ id, assetMetadataUpsertDto }: { id: string; @@ -2328,7 +2500,7 @@ export function updateAssetMetadata({ id, assetMetadataUpsertDto }: { }))); } /** - * This endpoint requires the `asset.update` permission. + * Delete asset metadata by key */ export function deleteAssetMetadata({ id, key }: { id: string; @@ -2340,7 +2512,7 @@ export function deleteAssetMetadata({ id, key }: { })); } /** - * This endpoint requires the `asset.read` permission. + * Retrieve asset metadata by key */ export function getAssetMetadataByKey({ id, key }: { id: string; @@ -2354,7 +2526,20 @@ export function getAssetMetadataByKey({ id, key }: { })); } /** - * This endpoint requires the `asset.download` permission. + * Retrieve asset OCR data + */ +export function getAssetOcr({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 200; + data: AssetOcrResponseDto[]; + }>(`/assets/${encodeURIComponent(id)}/ocr`, { + ...opts + })); +} +/** + * Download original asset */ export function downloadAsset({ id, key, slug }: { id: string; @@ -2372,7 +2557,7 @@ export function downloadAsset({ id, key, slug }: { })); } /** - * Replace the asset with new file, without changing its id + * Replace asset */ export function replaceAsset({ id, key, slug, assetMediaReplaceDto }: { id: string; @@ -2393,7 +2578,7 @@ export function replaceAsset({ id, key, slug, assetMediaReplaceDto }: { }))); } /** - * This endpoint requires the `asset.view` permission. + * View asset thumbnail */ export function viewAsset({ id, key, size, slug }: { id: string; @@ -2413,7 +2598,7 @@ export function viewAsset({ id, key, size, slug }: { })); } /** - * This endpoint requires the `asset.view` permission. + * Play asset video */ export function playAssetVideo({ id, key, slug }: { id: string; @@ -2430,6 +2615,9 @@ export function playAssetVideo({ id, key, slug }: { ...opts })); } +/** + * Register admin + */ export function signUpAdmin({ signUpDto }: { signUpDto: SignUpDto; }, opts?: Oazapfts.RequestOpts) { @@ -2443,7 +2631,7 @@ export function signUpAdmin({ signUpDto }: { }))); } /** - * This endpoint requires the `auth.changePassword` permission. + * Change password */ export function changePassword({ changePasswordDto }: { changePasswordDto: ChangePasswordDto; @@ -2457,6 +2645,9 @@ export function changePassword({ changePasswordDto }: { body: changePasswordDto }))); } +/** + * Login + */ export function login({ loginCredentialDto }: { loginCredentialDto: LoginCredentialDto; }, opts?: Oazapfts.RequestOpts) { @@ -2469,6 +2660,9 @@ export function login({ loginCredentialDto }: { body: loginCredentialDto }))); } +/** + * Logout + */ export function logout(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; @@ -2479,7 +2673,7 @@ export function logout(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `pinCode.delete` permission. + * Reset pin code */ export function resetPinCode({ pinCodeResetDto }: { pinCodeResetDto: PinCodeResetDto; @@ -2491,7 +2685,7 @@ export function resetPinCode({ pinCodeResetDto }: { }))); } /** - * This endpoint requires the `pinCode.create` permission. + * Setup pin code */ export function setupPinCode({ pinCodeSetupDto }: { pinCodeSetupDto: PinCodeSetupDto; @@ -2503,7 +2697,7 @@ export function setupPinCode({ pinCodeSetupDto }: { }))); } /** - * This endpoint requires the `pinCode.update` permission. + * Change pin code */ export function changePinCode({ pinCodeChangeDto }: { pinCodeChangeDto: PinCodeChangeDto; @@ -2514,12 +2708,18 @@ export function changePinCode({ pinCodeChangeDto }: { body: pinCodeChangeDto }))); } +/** + * Lock auth session + */ export function lockAuthSession(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchText("/auth/session/lock", { ...opts, method: "POST" })); } +/** + * Unlock auth session + */ export function unlockAuthSession({ sessionUnlockDto }: { sessionUnlockDto: SessionUnlockDto; }, opts?: Oazapfts.RequestOpts) { @@ -2529,6 +2729,9 @@ export function unlockAuthSession({ sessionUnlockDto }: { body: sessionUnlockDto }))); } +/** + * Retrieve auth status + */ export function getAuthStatus(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; @@ -2537,6 +2740,9 @@ export function getAuthStatus(opts?: Oazapfts.RequestOpts) { ...opts })); } +/** + * Validate access token + */ export function validateAccessToken(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; @@ -2547,7 +2753,7 @@ export function validateAccessToken(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `asset.download` permission. + * Download asset archive */ export function downloadArchive({ key, slug, assetIdsDto }: { key?: string; @@ -2567,7 +2773,7 @@ export function downloadArchive({ key, slug, assetIdsDto }: { }))); } /** - * This endpoint requires the `asset.download` permission. + * Retrieve download information */ export function getDownloadInfo({ key, slug, downloadInfoDto }: { key?: string; @@ -2587,7 +2793,7 @@ export function getDownloadInfo({ key, slug, downloadInfoDto }: { }))); } /** - * This endpoint requires the `duplicate.delete` permission. + * Delete duplicates */ export function deleteDuplicates({ bulkIdsDto }: { bulkIdsDto: BulkIdsDto; @@ -2599,7 +2805,7 @@ export function deleteDuplicates({ bulkIdsDto }: { }))); } /** - * This endpoint requires the `duplicate.read` permission. + * Retrieve duplicates */ export function getAssetDuplicates(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -2610,7 +2816,7 @@ export function getAssetDuplicates(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `duplicate.delete` permission. + * Delete a duplicate */ export function deleteDuplicate({ id }: { id: string; @@ -2621,7 +2827,7 @@ export function deleteDuplicate({ id }: { })); } /** - * This endpoint requires the `face.read` permission. + * Retrieve faces for asset */ export function getFaces({ id }: { id: string; @@ -2636,7 +2842,7 @@ export function getFaces({ id }: { })); } /** - * This endpoint requires the `face.create` permission. + * Create a face */ export function createFace({ assetFaceCreateDto }: { assetFaceCreateDto: AssetFaceCreateDto; @@ -2648,7 +2854,7 @@ export function createFace({ assetFaceCreateDto }: { }))); } /** - * This endpoint requires the `face.delete` permission. + * Delete a face */ export function deleteFace({ id, assetFaceDeleteDto }: { id: string; @@ -2661,7 +2867,7 @@ export function deleteFace({ id, assetFaceDeleteDto }: { }))); } /** - * This endpoint requires the `face.update` permission. + * Re-assign a face to another person */ export function reassignFacesById({ id, faceDto }: { id: string; @@ -2677,18 +2883,18 @@ export function reassignFacesById({ id, faceDto }: { }))); } /** - * This endpoint is an admin-only route, and requires the `job.read` permission. + * Retrieve queue counts and status */ -export function getAllJobsStatus(opts?: Oazapfts.RequestOpts) { +export function getQueuesLegacy(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; - data: AllJobStatusResponseDto; + data: QueuesResponseDto; }>("/jobs", { ...opts })); } /** - * This endpoint is an admin-only route, and requires the `job.create` permission. + * Create a manual job */ export function createJob({ jobCreateDto }: { jobCreateDto: JobCreateDto; @@ -2700,23 +2906,23 @@ export function createJob({ jobCreateDto }: { }))); } /** - * This endpoint is an admin-only route, and requires the `job.create` permission. + * Run jobs */ -export function sendJobCommand({ id, jobCommandDto }: { - id: JobName; - jobCommandDto: JobCommandDto; +export function runQueueCommandLegacy({ name, queueCommandDto }: { + name: QueueName; + queueCommandDto: QueueCommandDto; }, opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; - data: JobStatusDto; - }>(`/jobs/${encodeURIComponent(id)}`, oazapfts.json({ + data: QueueResponseDto; + }>(`/jobs/${encodeURIComponent(name)}`, oazapfts.json({ ...opts, method: "PUT", - body: jobCommandDto + body: queueCommandDto }))); } /** - * This endpoint is an admin-only route, and requires the `library.read` permission. + * Retrieve libraries */ export function getAllLibraries(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -2727,7 +2933,7 @@ export function getAllLibraries(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint is an admin-only route, and requires the `library.create` permission. + * Create a library */ export function createLibrary({ createLibraryDto }: { createLibraryDto: CreateLibraryDto; @@ -2742,7 +2948,7 @@ export function createLibrary({ createLibraryDto }: { }))); } /** - * This endpoint is an admin-only route, and requires the `library.delete` permission. + * Delete a library */ export function deleteLibrary({ id }: { id: string; @@ -2753,7 +2959,7 @@ export function deleteLibrary({ id }: { })); } /** - * This endpoint is an admin-only route, and requires the `library.read` permission. + * Retrieve a library */ export function getLibrary({ id }: { id: string; @@ -2766,7 +2972,7 @@ export function getLibrary({ id }: { })); } /** - * This endpoint is an admin-only route, and requires the `library.update` permission. + * Update a library */ export function updateLibrary({ id, updateLibraryDto }: { id: string; @@ -2782,7 +2988,7 @@ export function updateLibrary({ id, updateLibraryDto }: { }))); } /** - * This endpoint is an admin-only route, and requires the `library.update` permission. + * Scan a library */ export function scanLibrary({ id }: { id: string; @@ -2793,7 +2999,7 @@ export function scanLibrary({ id }: { })); } /** - * This endpoint is an admin-only route, and requires the `library.statistics` permission. + * Retrieve library statistics */ export function getLibraryStatistics({ id }: { id: string; @@ -2805,6 +3011,9 @@ export function getLibraryStatistics({ id }: { ...opts })); } +/** + * Validate library settings + */ export function validate({ id, validateLibraryDto }: { id: string; validateLibraryDto: ValidateLibraryDto; @@ -2818,11 +3027,14 @@ export function validate({ id, validateLibraryDto }: { body: validateLibraryDto }))); } -export function getMapMarkers({ isArchived, isFavorite, fileCreatedAfter, fileCreatedBefore, withPartners, withSharedAlbums }: { - isArchived?: boolean; - isFavorite?: boolean; +/** + * Retrieve map markers + */ +export function getMapMarkers({ fileCreatedAfter, fileCreatedBefore, isArchived, isFavorite, withPartners, withSharedAlbums }: { fileCreatedAfter?: string; fileCreatedBefore?: string; + isArchived?: boolean; + isFavorite?: boolean; withPartners?: boolean; withSharedAlbums?: boolean; }, opts?: Oazapfts.RequestOpts) { @@ -2830,16 +3042,19 @@ export function getMapMarkers({ isArchived, isFavorite, fileCreatedAfter, fileCr status: 200; data: MapMarkerResponseDto[]; }>(`/map/markers${QS.query(QS.explode({ - isArchived, - isFavorite, fileCreatedAfter, fileCreatedBefore, + isArchived, + isFavorite, withPartners, withSharedAlbums }))}`, { ...opts })); } +/** + * Reverse geocode coordinates + */ export function reverseGeocode({ lat, lon }: { lat: number; lon: number; @@ -2855,12 +3070,14 @@ export function reverseGeocode({ lat, lon }: { })); } /** - * This endpoint requires the `memory.read` permission. + * Retrieve memories */ -export function searchMemories({ $for, isSaved, isTrashed, $type }: { +export function searchMemories({ $for, isSaved, isTrashed, order, size, $type }: { $for?: string; isSaved?: boolean; isTrashed?: boolean; + order?: MemorySearchOrder; + size?: number; $type?: MemoryType; }, opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -2870,13 +3087,15 @@ export function searchMemories({ $for, isSaved, isTrashed, $type }: { "for": $for, isSaved, isTrashed, + order, + size, "type": $type }))}`, { ...opts })); } /** - * This endpoint requires the `memory.create` permission. + * Create a memory */ export function createMemory({ memoryCreateDto }: { memoryCreateDto: MemoryCreateDto; @@ -2891,12 +3110,14 @@ export function createMemory({ memoryCreateDto }: { }))); } /** - * This endpoint requires the `memory.statistics` permission. + * Retrieve memories statistics */ -export function memoriesStatistics({ $for, isSaved, isTrashed, $type }: { +export function memoriesStatistics({ $for, isSaved, isTrashed, order, size, $type }: { $for?: string; isSaved?: boolean; isTrashed?: boolean; + order?: MemorySearchOrder; + size?: number; $type?: MemoryType; }, opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -2906,13 +3127,15 @@ export function memoriesStatistics({ $for, isSaved, isTrashed, $type }: { "for": $for, isSaved, isTrashed, + order, + size, "type": $type }))}`, { ...opts })); } /** - * This endpoint requires the `memory.delete` permission. + * Delete a memory */ export function deleteMemory({ id }: { id: string; @@ -2923,7 +3146,7 @@ export function deleteMemory({ id }: { })); } /** - * This endpoint requires the `memory.read` permission. + * Retrieve a memory */ export function getMemory({ id }: { id: string; @@ -2936,7 +3159,7 @@ export function getMemory({ id }: { })); } /** - * This endpoint requires the `memory.update` permission. + * Update a memory */ export function updateMemory({ id, memoryUpdateDto }: { id: string; @@ -2952,7 +3175,7 @@ export function updateMemory({ id, memoryUpdateDto }: { }))); } /** - * This endpoint requires the `memoryAsset.delete` permission. + * Remove assets from a memory */ export function removeMemoryAssets({ id, bulkIdsDto }: { id: string; @@ -2968,7 +3191,7 @@ export function removeMemoryAssets({ id, bulkIdsDto }: { }))); } /** - * This endpoint requires the `memoryAsset.create` permission. + * Add assets to a memory */ export function addMemoryAssets({ id, bulkIdsDto }: { id: string; @@ -2984,7 +3207,7 @@ export function addMemoryAssets({ id, bulkIdsDto }: { }))); } /** - * This endpoint requires the `notification.delete` permission. + * Delete notifications */ export function deleteNotifications({ notificationDeleteAllDto }: { notificationDeleteAllDto: NotificationDeleteAllDto; @@ -2996,7 +3219,7 @@ export function deleteNotifications({ notificationDeleteAllDto }: { }))); } /** - * This endpoint requires the `notification.read` permission. + * Retrieve notifications */ export function getNotifications({ id, level, $type, unread }: { id?: string; @@ -3017,7 +3240,7 @@ export function getNotifications({ id, level, $type, unread }: { })); } /** - * This endpoint requires the `notification.update` permission. + * Update notifications */ export function updateNotifications({ notificationUpdateAllDto }: { notificationUpdateAllDto: NotificationUpdateAllDto; @@ -3029,7 +3252,7 @@ export function updateNotifications({ notificationUpdateAllDto }: { }))); } /** - * This endpoint requires the `notification.delete` permission. + * Delete a notification */ export function deleteNotification({ id }: { id: string; @@ -3040,7 +3263,7 @@ export function deleteNotification({ id }: { })); } /** - * This endpoint requires the `notification.read` permission. + * Get a notification */ export function getNotification({ id }: { id: string; @@ -3053,7 +3276,7 @@ export function getNotification({ id }: { })); } /** - * This endpoint requires the `notification.update` permission. + * Update a notification */ export function updateNotification({ id, notificationUpdateDto }: { id: string; @@ -3068,6 +3291,9 @@ export function updateNotification({ id, notificationUpdateDto }: { body: notificationUpdateDto }))); } +/** + * Start OAuth + */ export function startOAuth({ oAuthConfigDto }: { oAuthConfigDto: OAuthConfigDto; }, opts?: Oazapfts.RequestOpts) { @@ -3080,6 +3306,9 @@ export function startOAuth({ oAuthConfigDto }: { body: oAuthConfigDto }))); } +/** + * Finish OAuth + */ export function finishOAuth({ oAuthCallbackDto }: { oAuthCallbackDto: OAuthCallbackDto; }, opts?: Oazapfts.RequestOpts) { @@ -3092,6 +3321,9 @@ export function finishOAuth({ oAuthCallbackDto }: { body: oAuthCallbackDto }))); } +/** + * Link OAuth account + */ export function linkOAuthAccount({ oAuthCallbackDto }: { oAuthCallbackDto: OAuthCallbackDto; }, opts?: Oazapfts.RequestOpts) { @@ -3104,11 +3336,17 @@ export function linkOAuthAccount({ oAuthCallbackDto }: { body: oAuthCallbackDto }))); } +/** + * Redirect OAuth to mobile + */ export function redirectOAuthToMobile(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchText("/oauth/mobile-redirect", { ...opts })); } +/** + * Unlink OAuth account + */ export function unlinkOAuthAccount(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; @@ -3119,7 +3357,7 @@ export function unlinkOAuthAccount(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `partner.read` permission. + * Retrieve partners */ export function getPartners({ direction }: { direction: PartnerDirection; @@ -3134,7 +3372,7 @@ export function getPartners({ direction }: { })); } /** - * This endpoint requires the `partner.create` permission. + * Create a partner */ export function createPartner({ partnerCreateDto }: { partnerCreateDto: PartnerCreateDto; @@ -3149,7 +3387,7 @@ export function createPartner({ partnerCreateDto }: { }))); } /** - * This endpoint requires the `partner.delete` permission. + * Remove a partner */ export function removePartner({ id }: { id: string; @@ -3160,7 +3398,7 @@ export function removePartner({ id }: { })); } /** - * This property was deprecated in v1.141.0. This endpoint requires the `partner.create` permission. + * Create a partner */ export function createPartnerDeprecated({ id }: { id: string; @@ -3174,7 +3412,7 @@ export function createPartnerDeprecated({ id }: { })); } /** - * This endpoint requires the `partner.update` permission. + * Update a partner */ export function updatePartner({ id, partnerUpdateDto }: { id: string; @@ -3190,7 +3428,7 @@ export function updatePartner({ id, partnerUpdateDto }: { }))); } /** - * This endpoint requires the `person.delete` permission. + * Delete people */ export function deletePeople({ bulkIdsDto }: { bulkIdsDto: BulkIdsDto; @@ -3202,7 +3440,7 @@ export function deletePeople({ bulkIdsDto }: { }))); } /** - * This endpoint requires the `person.read` permission. + * Get all people */ export function getAllPeople({ closestAssetId, closestPersonId, page, size, withHidden }: { closestAssetId?: string; @@ -3225,7 +3463,7 @@ export function getAllPeople({ closestAssetId, closestPersonId, page, size, with })); } /** - * This endpoint requires the `person.create` permission. + * Create a person */ export function createPerson({ personCreateDto }: { personCreateDto: PersonCreateDto; @@ -3240,7 +3478,7 @@ export function createPerson({ personCreateDto }: { }))); } /** - * This endpoint requires the `person.update` permission. + * Update people */ export function updatePeople({ peopleUpdateDto }: { peopleUpdateDto: PeopleUpdateDto; @@ -3255,7 +3493,7 @@ export function updatePeople({ peopleUpdateDto }: { }))); } /** - * This endpoint requires the `person.delete` permission. + * Delete person */ export function deletePerson({ id }: { id: string; @@ -3266,7 +3504,7 @@ export function deletePerson({ id }: { })); } /** - * This endpoint requires the `person.read` permission. + * Get a person */ export function getPerson({ id }: { id: string; @@ -3279,7 +3517,7 @@ export function getPerson({ id }: { })); } /** - * This endpoint requires the `person.update` permission. + * Update person */ export function updatePerson({ id, personUpdateDto }: { id: string; @@ -3295,7 +3533,7 @@ export function updatePerson({ id, personUpdateDto }: { }))); } /** - * This endpoint requires the `person.merge` permission. + * Merge people */ export function mergePerson({ id, mergePersonDto }: { id: string; @@ -3311,7 +3549,7 @@ export function mergePerson({ id, mergePersonDto }: { }))); } /** - * This endpoint requires the `person.reassign` permission. + * Reassign faces */ export function reassignFaces({ id, assetFaceUpdateDto }: { id: string; @@ -3327,7 +3565,7 @@ export function reassignFaces({ id, assetFaceUpdateDto }: { }))); } /** - * This endpoint requires the `person.statistics` permission. + * Get person statistics */ export function getPersonStatistics({ id }: { id: string; @@ -3340,7 +3578,7 @@ export function getPersonStatistics({ id }: { })); } /** - * This endpoint requires the `person.read` permission. + * Get person thumbnail */ export function getPersonThumbnail({ id }: { id: string; @@ -3353,7 +3591,31 @@ export function getPersonThumbnail({ id }: { })); } /** - * This endpoint requires the `asset.read` permission. + * List all plugins + */ +export function getPlugins(opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 200; + data: PluginResponseDto[]; + }>("/plugins", { + ...opts + })); +} +/** + * Retrieve a plugin + */ +export function getPlugin({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 200; + data: PluginResponseDto; + }>(`/plugins/${encodeURIComponent(id)}`, { + ...opts + })); +} +/** + * Retrieve assets by city */ export function getAssetsByCity(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -3364,7 +3626,7 @@ export function getAssetsByCity(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `asset.read` permission. + * Retrieve explore data */ export function getExploreData(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -3375,9 +3637,9 @@ export function getExploreData(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `asset.read` permission. + * Search large assets */ -export function searchLargeAssets({ albumIds, city, country, createdAfter, createdBefore, deviceId, isEncoded, isFavorite, isMotion, isNotInAlbum, isOffline, lensModel, libraryId, make, minFileSize, model, personIds, rating, size, state, tagIds, takenAfter, takenBefore, trashedAfter, trashedBefore, $type, updatedAfter, updatedBefore, visibility, withDeleted, withExif }: { +export function searchLargeAssets({ albumIds, city, country, createdAfter, createdBefore, deviceId, isEncoded, isFavorite, isMotion, isNotInAlbum, isOffline, lensModel, libraryId, make, minFileSize, model, ocr, personIds, rating, size, state, tagIds, takenAfter, takenBefore, trashedAfter, trashedBefore, $type, updatedAfter, updatedBefore, visibility, withDeleted, withExif }: { albumIds?: string[]; city?: string | null; country?: string | null; @@ -3394,6 +3656,7 @@ export function searchLargeAssets({ albumIds, city, country, createdAfter, creat make?: string; minFileSize?: number; model?: string | null; + ocr?: string; personIds?: string[]; rating?: number; size?: number; @@ -3430,6 +3693,7 @@ export function searchLargeAssets({ albumIds, city, country, createdAfter, creat make, minFileSize, model, + ocr, personIds, rating, size, @@ -3451,7 +3715,7 @@ export function searchLargeAssets({ albumIds, city, country, createdAfter, creat })); } /** - * This endpoint requires the `asset.read` permission. + * Search assets by metadata */ export function searchAssets({ metadataSearchDto }: { metadataSearchDto: MetadataSearchDto; @@ -3466,7 +3730,7 @@ export function searchAssets({ metadataSearchDto }: { }))); } /** - * This endpoint requires the `person.read` permission. + * Search people */ export function searchPerson({ name, withHidden }: { name: string; @@ -3483,7 +3747,7 @@ export function searchPerson({ name, withHidden }: { })); } /** - * This endpoint requires the `asset.read` permission. + * Search places */ export function searchPlaces({ name }: { name: string; @@ -3498,7 +3762,7 @@ export function searchPlaces({ name }: { })); } /** - * This endpoint requires the `asset.read` permission. + * Search random assets */ export function searchRandom({ randomSearchDto }: { randomSearchDto: RandomSearchDto; @@ -3513,7 +3777,7 @@ export function searchRandom({ randomSearchDto }: { }))); } /** - * This endpoint requires the `asset.read` permission. + * Smart asset search */ export function searchSmart({ smartSearchDto }: { smartSearchDto: SmartSearchDto; @@ -3528,7 +3792,7 @@ export function searchSmart({ smartSearchDto }: { }))); } /** - * This endpoint requires the `asset.statistics` permission. + * Search asset statistics */ export function searchAssetStatistics({ statisticsSearchDto }: { statisticsSearchDto: StatisticsSearchDto; @@ -3543,11 +3807,12 @@ export function searchAssetStatistics({ statisticsSearchDto }: { }))); } /** - * This endpoint requires the `asset.read` permission. + * Retrieve search suggestions */ -export function getSearchSuggestions({ country, includeNull, make, model, state, $type }: { +export function getSearchSuggestions({ country, includeNull, lensModel, make, model, state, $type }: { country?: string; includeNull?: boolean; + lensModel?: string; make?: string; model?: string; state?: string; @@ -3559,6 +3824,7 @@ export function getSearchSuggestions({ country, includeNull, make, model, state, }>(`/search/suggestions${QS.query(QS.explode({ country, includeNull, + lensModel, make, model, state, @@ -3568,7 +3834,7 @@ export function getSearchSuggestions({ country, includeNull, make, model, state, })); } /** - * This endpoint requires the `server.about` permission. + * Get server information */ export function getAboutInfo(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -3579,7 +3845,7 @@ export function getAboutInfo(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `server.apkLinks` permission. + * Get APK links */ export function getApkLinks(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -3589,6 +3855,9 @@ export function getApkLinks(opts?: Oazapfts.RequestOpts) { ...opts })); } +/** + * Get config + */ export function getServerConfig(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; @@ -3597,6 +3866,9 @@ export function getServerConfig(opts?: Oazapfts.RequestOpts) { ...opts })); } +/** + * Get features + */ export function getServerFeatures(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; @@ -3606,7 +3878,7 @@ export function getServerFeatures(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint is an admin-only route, and requires the `serverLicense.delete` permission. + * Delete server product key */ export function deleteServerLicense(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchText("/server/license", { @@ -3615,7 +3887,7 @@ export function deleteServerLicense(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint is an admin-only route, and requires the `serverLicense.read` permission. + * Get product key */ export function getServerLicense(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -3628,7 +3900,7 @@ export function getServerLicense(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint is an admin-only route, and requires the `serverLicense.update` permission. + * Set server product key */ export function setServerLicense({ licenseKeyDto }: { licenseKeyDto: LicenseKeyDto; @@ -3642,6 +3914,9 @@ export function setServerLicense({ licenseKeyDto }: { body: licenseKeyDto }))); } +/** + * Get supported media types + */ export function getSupportedMediaTypes(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; @@ -3650,6 +3925,9 @@ export function getSupportedMediaTypes(opts?: Oazapfts.RequestOpts) { ...opts })); } +/** + * Ping + */ export function pingServer(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; @@ -3659,7 +3937,7 @@ export function pingServer(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint is an admin-only route, and requires the `server.statistics` permission. + * Get statistics */ export function getServerStatistics(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -3670,7 +3948,7 @@ export function getServerStatistics(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `server.storage` permission. + * Get storage */ export function getStorage(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -3680,6 +3958,9 @@ export function getStorage(opts?: Oazapfts.RequestOpts) { ...opts })); } +/** + * Get theme + */ export function getTheme(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; @@ -3688,6 +3969,9 @@ export function getTheme(opts?: Oazapfts.RequestOpts) { ...opts })); } +/** + * Get server version + */ export function getServerVersion(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; @@ -3697,7 +3981,7 @@ export function getServerVersion(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `server.versionCheck` permission. + * Get version check status */ export function getVersionCheck(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -3707,6 +3991,9 @@ export function getVersionCheck(opts?: Oazapfts.RequestOpts) { ...opts })); } +/** + * Get version history + */ export function getVersionHistory(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; @@ -3716,7 +4003,7 @@ export function getVersionHistory(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `session.delete` permission. + * Delete all sessions */ export function deleteAllSessions(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchText("/sessions", { @@ -3725,7 +4012,7 @@ export function deleteAllSessions(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `session.read` permission. + * Retrieve sessions */ export function getSessions(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -3736,7 +4023,7 @@ export function getSessions(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `session.create` permission. + * Create a session */ export function createSession({ sessionCreateDto }: { sessionCreateDto: SessionCreateDto; @@ -3751,7 +4038,7 @@ export function createSession({ sessionCreateDto }: { }))); } /** - * This endpoint requires the `session.delete` permission. + * Delete a session */ export function deleteSession({ id }: { id: string; @@ -3762,7 +4049,7 @@ export function deleteSession({ id }: { })); } /** - * This endpoint requires the `session.update` permission. + * Update a session */ export function updateSession({ id, sessionUpdateDto }: { id: string; @@ -3778,7 +4065,7 @@ export function updateSession({ id, sessionUpdateDto }: { }))); } /** - * This endpoint requires the `session.lock` permission. + * Lock a session */ export function lockSession({ id }: { id: string; @@ -3789,7 +4076,7 @@ export function lockSession({ id }: { })); } /** - * This endpoint requires the `sharedLink.read` permission. + * Retrieve all shared links */ export function getAllSharedLinks({ albumId }: { albumId?: string; @@ -3804,7 +4091,7 @@ export function getAllSharedLinks({ albumId }: { })); } /** - * This endpoint requires the `sharedLink.create` permission. + * Create a shared link */ export function createSharedLink({ sharedLinkCreateDto }: { sharedLinkCreateDto: SharedLinkCreateDto; @@ -3818,26 +4105,29 @@ export function createSharedLink({ sharedLinkCreateDto }: { body: sharedLinkCreateDto }))); } -export function getMySharedLink({ password, token, key, slug }: { - password?: string; - token?: string; +/** + * Retrieve current shared link + */ +export function getMySharedLink({ key, password, slug, token }: { key?: string; + password?: string; slug?: string; + token?: string; }, opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; data: SharedLinkResponseDto; }>(`/shared-links/me${QS.query(QS.explode({ - password, - token, key, - slug + password, + slug, + token }))}`, { ...opts })); } /** - * This endpoint requires the `sharedLink.delete` permission. + * Delete a shared link */ export function removeSharedLink({ id }: { id: string; @@ -3848,7 +4138,7 @@ export function removeSharedLink({ id }: { })); } /** - * This endpoint requires the `sharedLink.read` permission. + * Retrieve a shared link */ export function getSharedLinkById({ id }: { id: string; @@ -3861,7 +4151,7 @@ export function getSharedLinkById({ id }: { })); } /** - * This endpoint requires the `sharedLink.update` permission. + * Update a shared link */ export function updateSharedLink({ id, sharedLinkEditDto }: { id: string; @@ -3876,6 +4166,9 @@ export function updateSharedLink({ id, sharedLinkEditDto }: { body: sharedLinkEditDto }))); } +/** + * Remove assets from a shared link + */ export function removeSharedLinkAssets({ id, key, slug, assetIdsDto }: { id: string; key?: string; @@ -3894,6 +4187,9 @@ export function removeSharedLinkAssets({ id, key, slug, assetIdsDto }: { body: assetIdsDto }))); } +/** + * Add assets to a shared link + */ export function addSharedLinkAssets({ id, key, slug, assetIdsDto }: { id: string; key?: string; @@ -3913,7 +4209,7 @@ export function addSharedLinkAssets({ id, key, slug, assetIdsDto }: { }))); } /** - * This endpoint requires the `stack.delete` permission. + * Delete stacks */ export function deleteStacks({ bulkIdsDto }: { bulkIdsDto: BulkIdsDto; @@ -3925,7 +4221,7 @@ export function deleteStacks({ bulkIdsDto }: { }))); } /** - * This endpoint requires the `stack.read` permission. + * Retrieve stacks */ export function searchStacks({ primaryAssetId }: { primaryAssetId?: string; @@ -3940,7 +4236,7 @@ export function searchStacks({ primaryAssetId }: { })); } /** - * This endpoint requires the `stack.create` permission. + * Create a stack */ export function createStack({ stackCreateDto }: { stackCreateDto: StackCreateDto; @@ -3955,7 +4251,7 @@ export function createStack({ stackCreateDto }: { }))); } /** - * This endpoint requires the `stack.delete` permission. + * Delete a stack */ export function deleteStack({ id }: { id: string; @@ -3966,7 +4262,7 @@ export function deleteStack({ id }: { })); } /** - * This endpoint requires the `stack.read` permission. + * Retrieve a stack */ export function getStack({ id }: { id: string; @@ -3979,7 +4275,7 @@ export function getStack({ id }: { })); } /** - * This endpoint requires the `stack.update` permission. + * Update a stack */ export function updateStack({ id, stackUpdateDto }: { id: string; @@ -3995,7 +4291,7 @@ export function updateStack({ id, stackUpdateDto }: { }))); } /** - * This endpoint requires the `stack.update` permission. + * Remove an asset from a stack */ export function removeAssetFromStack({ assetId, id }: { assetId: string; @@ -4007,7 +4303,7 @@ export function removeAssetFromStack({ assetId, id }: { })); } /** - * This endpoint requires the `syncCheckpoint.delete` permission. + * Delete acknowledgements */ export function deleteSyncAck({ syncAckDeleteDto }: { syncAckDeleteDto: SyncAckDeleteDto; @@ -4019,7 +4315,7 @@ export function deleteSyncAck({ syncAckDeleteDto }: { }))); } /** - * This endpoint requires the `syncCheckpoint.read` permission. + * Retrieve acknowledgements */ export function getSyncAck(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -4030,7 +4326,7 @@ export function getSyncAck(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `syncCheckpoint.update` permission. + * Acknowledge changes */ export function sendSyncAck({ syncAckSetDto }: { syncAckSetDto: SyncAckSetDto; @@ -4041,6 +4337,9 @@ export function sendSyncAck({ syncAckSetDto }: { body: syncAckSetDto }))); } +/** + * Get delta sync for user + */ export function getDeltaSync({ assetDeltaSyncDto }: { assetDeltaSyncDto: AssetDeltaSyncDto; }, opts?: Oazapfts.RequestOpts) { @@ -4053,6 +4352,9 @@ export function getDeltaSync({ assetDeltaSyncDto }: { body: assetDeltaSyncDto }))); } +/** + * Get full sync for user + */ export function getFullSyncForUser({ assetFullSyncDto }: { assetFullSyncDto: AssetFullSyncDto; }, opts?: Oazapfts.RequestOpts) { @@ -4066,7 +4368,7 @@ export function getFullSyncForUser({ assetFullSyncDto }: { }))); } /** - * This endpoint requires the `sync.stream` permission. + * Stream sync changes */ export function getSyncStream({ syncStreamDto }: { syncStreamDto: SyncStreamDto; @@ -4078,7 +4380,7 @@ export function getSyncStream({ syncStreamDto }: { }))); } /** - * This endpoint is an admin-only route, and requires the `systemConfig.read` permission. + * Get system configuration */ export function getConfig(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -4089,7 +4391,7 @@ export function getConfig(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint is an admin-only route, and requires the `systemConfig.update` permission. + * Update system configuration */ export function updateConfig({ systemConfigDto }: { systemConfigDto: SystemConfigDto; @@ -4104,7 +4406,7 @@ export function updateConfig({ systemConfigDto }: { }))); } /** - * This endpoint is an admin-only route, and requires the `systemConfig.read` permission. + * Get system configuration defaults */ export function getConfigDefaults(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -4115,7 +4417,7 @@ export function getConfigDefaults(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint is an admin-only route, and requires the `systemConfig.read` permission. + * Get storage template options */ export function getStorageTemplateOptions(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -4126,7 +4428,7 @@ export function getStorageTemplateOptions(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint is an admin-only route, and requires the `systemMetadata.read` permission. + * Retrieve admin onboarding */ export function getAdminOnboarding(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -4137,7 +4439,7 @@ export function getAdminOnboarding(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint is an admin-only route, and requires the `systemMetadata.update` permission. + * Update admin onboarding */ export function updateAdminOnboarding({ adminOnboardingUpdateDto }: { adminOnboardingUpdateDto: AdminOnboardingUpdateDto; @@ -4149,7 +4451,7 @@ export function updateAdminOnboarding({ adminOnboardingUpdateDto }: { }))); } /** - * This endpoint is an admin-only route, and requires the `systemMetadata.read` permission. + * Retrieve reverse geocoding state */ export function getReverseGeocodingState(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -4160,7 +4462,7 @@ export function getReverseGeocodingState(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint is an admin-only route, and requires the `systemMetadata.read` permission. + * Retrieve version check state */ export function getVersionCheckState(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -4171,7 +4473,7 @@ export function getVersionCheckState(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `tag.read` permission. + * Retrieve tags */ export function getAllTags(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -4182,7 +4484,7 @@ export function getAllTags(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `tag.create` permission. + * Create a tag */ export function createTag({ tagCreateDto }: { tagCreateDto: TagCreateDto; @@ -4197,7 +4499,7 @@ export function createTag({ tagCreateDto }: { }))); } /** - * This endpoint requires the `tag.create` permission. + * Upsert tags */ export function upsertTags({ tagUpsertDto }: { tagUpsertDto: TagUpsertDto; @@ -4212,7 +4514,7 @@ export function upsertTags({ tagUpsertDto }: { }))); } /** - * This endpoint requires the `tag.asset` permission. + * Tag assets */ export function bulkTagAssets({ tagBulkAssetsDto }: { tagBulkAssetsDto: TagBulkAssetsDto; @@ -4227,7 +4529,7 @@ export function bulkTagAssets({ tagBulkAssetsDto }: { }))); } /** - * This endpoint requires the `tag.delete` permission. + * Delete a tag */ export function deleteTag({ id }: { id: string; @@ -4238,7 +4540,7 @@ export function deleteTag({ id }: { })); } /** - * This endpoint requires the `tag.read` permission. + * Retrieve a tag */ export function getTagById({ id }: { id: string; @@ -4251,7 +4553,7 @@ export function getTagById({ id }: { })); } /** - * This endpoint requires the `tag.update` permission. + * Update a tag */ export function updateTag({ id, tagUpdateDto }: { id: string; @@ -4267,7 +4569,7 @@ export function updateTag({ id, tagUpdateDto }: { }))); } /** - * This endpoint requires the `tag.asset` permission. + * Untag assets */ export function untagAssets({ id, bulkIdsDto }: { id: string; @@ -4283,7 +4585,7 @@ export function untagAssets({ id, bulkIdsDto }: { }))); } /** - * This endpoint requires the `tag.asset` permission. + * Tag assets */ export function tagAssets({ id, bulkIdsDto }: { id: string; @@ -4299,7 +4601,7 @@ export function tagAssets({ id, bulkIdsDto }: { }))); } /** - * This endpoint requires the `asset.read` permission. + * Get time bucket */ export function getTimeBucket({ albumId, isFavorite, isTrashed, key, order, personId, slug, tagId, timeBucket, userId, visibility, withCoordinates, withPartners, withStacked }: { albumId?: string; @@ -4340,7 +4642,7 @@ export function getTimeBucket({ albumId, isFavorite, isTrashed, key, order, pers })); } /** - * This endpoint requires the `asset.read` permission. + * Get time buckets */ export function getTimeBuckets({ albumId, isFavorite, isTrashed, key, order, personId, slug, tagId, userId, visibility, withCoordinates, withPartners, withStacked }: { albumId?: string; @@ -4379,7 +4681,7 @@ export function getTimeBuckets({ albumId, isFavorite, isTrashed, key, order, per })); } /** - * This endpoint requires the `asset.delete` permission. + * Empty trash */ export function emptyTrash(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -4391,7 +4693,7 @@ export function emptyTrash(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `asset.delete` permission. + * Restore trash */ export function restoreTrash(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -4403,7 +4705,7 @@ export function restoreTrash(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `asset.delete` permission. + * Restore assets */ export function restoreAssets({ bulkIdsDto }: { bulkIdsDto: BulkIdsDto; @@ -4418,7 +4720,7 @@ export function restoreAssets({ bulkIdsDto }: { }))); } /** - * This endpoint requires the `user.read` permission. + * Get all users */ export function searchUsers(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -4429,7 +4731,7 @@ export function searchUsers(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `user.read` permission. + * Get current user */ export function getMyUser(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -4440,7 +4742,7 @@ export function getMyUser(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `user.update` permission. + * Update current user */ export function updateMyUser({ userUpdateMeDto }: { userUpdateMeDto: UserUpdateMeDto; @@ -4455,7 +4757,7 @@ export function updateMyUser({ userUpdateMeDto }: { }))); } /** - * This endpoint requires the `userLicense.delete` permission. + * Delete user product key */ export function deleteUserLicense(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchText("/users/me/license", { @@ -4464,7 +4766,7 @@ export function deleteUserLicense(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `userLicense.read` permission. + * Retrieve user product key */ export function getUserLicense(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -4475,7 +4777,7 @@ export function getUserLicense(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `userLicense.update` permission. + * Set user product key */ export function setUserLicense({ licenseKeyDto }: { licenseKeyDto: LicenseKeyDto; @@ -4490,7 +4792,7 @@ export function setUserLicense({ licenseKeyDto }: { }))); } /** - * This endpoint requires the `userOnboarding.delete` permission. + * Delete user onboarding */ export function deleteUserOnboarding(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchText("/users/me/onboarding", { @@ -4499,7 +4801,7 @@ export function deleteUserOnboarding(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `userOnboarding.read` permission. + * Retrieve user onboarding */ export function getUserOnboarding(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -4510,7 +4812,7 @@ export function getUserOnboarding(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `userOnboarding.update` permission. + * Update user onboarding */ export function setUserOnboarding({ onboardingDto }: { onboardingDto: OnboardingDto; @@ -4525,7 +4827,7 @@ export function setUserOnboarding({ onboardingDto }: { }))); } /** - * This endpoint requires the `userPreference.read` permission. + * Get my preferences */ export function getMyPreferences(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ @@ -4536,7 +4838,7 @@ export function getMyPreferences(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `userPreference.update` permission. + * Update my preferences */ export function updateMyPreferences({ userPreferencesUpdateDto }: { userPreferencesUpdateDto: UserPreferencesUpdateDto; @@ -4551,7 +4853,7 @@ export function updateMyPreferences({ userPreferencesUpdateDto }: { }))); } /** - * This endpoint requires the `userProfileImage.delete` permission. + * Delete user profile image */ export function deleteProfileImage(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchText("/users/profile-image", { @@ -4560,7 +4862,7 @@ export function deleteProfileImage(opts?: Oazapfts.RequestOpts) { })); } /** - * This endpoint requires the `userProfileImage.update` permission. + * Create user profile image */ export function createProfileImage({ createProfileImageDto }: { createProfileImageDto: CreateProfileImageDto; @@ -4575,7 +4877,7 @@ export function createProfileImage({ createProfileImageDto }: { }))); } /** - * This endpoint requires the `user.read` permission. + * Retrieve a user */ export function getUser({ id }: { id: string; @@ -4588,7 +4890,7 @@ export function getUser({ id }: { })); } /** - * This endpoint requires the `userProfileImage.read` permission. + * Retrieve user profile image */ export function getProfileImage({ id }: { id: string; @@ -4600,6 +4902,9 @@ export function getProfileImage({ id }: { ...opts })); } +/** + * Retrieve assets by original path + */ export function getAssetsByOriginalPath({ path }: { path: string; }, opts?: Oazapfts.RequestOpts) { @@ -4612,6 +4917,9 @@ export function getAssetsByOriginalPath({ path }: { ...opts })); } +/** + * Retrieve unique paths + */ export function getUniqueOriginalPaths(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; @@ -4620,6 +4928,72 @@ export function getUniqueOriginalPaths(opts?: Oazapfts.RequestOpts) { ...opts })); } +/** + * List all workflows + */ +export function getWorkflows(opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 200; + data: WorkflowResponseDto[]; + }>("/workflows", { + ...opts + })); +} +/** + * Create a workflow + */ +export function createWorkflow({ workflowCreateDto }: { + workflowCreateDto: WorkflowCreateDto; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 201; + data: WorkflowResponseDto; + }>("/workflows", oazapfts.json({ + ...opts, + method: "POST", + body: workflowCreateDto + }))); +} +/** + * Delete a workflow + */ +export function deleteWorkflow({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchText(`/workflows/${encodeURIComponent(id)}`, { + ...opts, + method: "DELETE" + })); +} +/** + * Retrieve a workflow + */ +export function getWorkflow({ id }: { + id: string; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 200; + data: WorkflowResponseDto; + }>(`/workflows/${encodeURIComponent(id)}`, { + ...opts + })); +} +/** + * Update a workflow + */ +export function updateWorkflow({ id, workflowUpdateDto }: { + id: string; + workflowUpdateDto: WorkflowUpdateDto; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 200; + data: WorkflowResponseDto; + }>(`/workflows/${encodeURIComponent(id)}`, oazapfts.json({ + ...opts, + method: "PUT", + body: workflowUpdateDto + }))); +} export enum ReactionLevel { Album = "album", Asset = "asset" @@ -4650,6 +5024,8 @@ export enum NotificationType { JobFailed = "JobFailed", BackupFailed = "BackupFailed", SystemMessage = "SystemMessage", + AlbumInvite = "AlbumInvite", + AlbumUpdate = "AlbumUpdate", Custom = "Custom" } export enum UserStatus { @@ -4714,6 +5090,7 @@ export enum Permission { AssetDownload = "asset.download", AssetUpload = "asset.upload", AssetReplace = "asset.replace", + AssetCopy = "asset.copy", AlbumCreate = "album.create", AlbumRead = "album.read", AlbumUpdate = "album.update", @@ -4769,6 +5146,10 @@ export enum Permission { PinCodeCreate = "pinCode.create", PinCodeUpdate = "pinCode.update", PinCodeDelete = "pinCode.delete", + PluginCreate = "plugin.create", + PluginRead = "plugin.read", + PluginUpdate = "plugin.update", + PluginDelete = "plugin.delete", ServerAbout = "server.about", ServerApkLinks = "server.apkLinks", ServerStorage = "server.storage", @@ -4818,10 +5199,15 @@ export enum Permission { UserProfileImageRead = "userProfileImage.read", UserProfileImageUpdate = "userProfileImage.update", UserProfileImageDelete = "userProfileImage.delete", + WorkflowCreate = "workflow.create", + WorkflowRead = "workflow.read", + WorkflowUpdate = "workflow.update", + WorkflowDelete = "workflow.delete", AdminUserCreate = "adminUser.create", AdminUserRead = "adminUser.read", AdminUserUpdate = "adminUser.update", AdminUserDelete = "adminUser.delete", + AdminSessionRead = "adminSession.read", AdminAuthUnlinkAll = "adminAuth.unlinkAll" } export enum AssetMetadataKey { @@ -4859,7 +5245,7 @@ export enum ManualJobName { MemoryCreate = "memory-create", BackupDatabase = "backup-database" } -export enum JobName { +export enum QueueName { ThumbnailGeneration = "thumbnailGeneration", MetadataExtraction = "metadataExtraction", VideoConversion = "videoConversion", @@ -4874,15 +5260,22 @@ export enum JobName { Sidecar = "sidecar", Library = "library", Notifications = "notifications", - BackupDatabase = "backupDatabase" + BackupDatabase = "backupDatabase", + Ocr = "ocr", + Workflow = "workflow" } -export enum JobCommand { +export enum QueueCommand { Start = "start", Pause = "pause", Resume = "resume", Empty = "empty", ClearFailed = "clear-failed" } +export enum MemorySearchOrder { + Asc = "asc", + Desc = "desc", + Random = "random" +} export enum MemoryType { OnThisDay = "on_this_day" } @@ -4890,12 +5283,18 @@ export enum PartnerDirection { SharedBy = "shared-by", SharedWith = "shared-with" } +export enum PluginContext { + Asset = "asset", + Album = "album", + Person = "person" +} export enum SearchSuggestionType { Country = "country", State = "state", City = "city", CameraMake = "camera-make", - CameraModel = "camera-model" + CameraModel = "camera-model", + CameraLensModel = "camera-lens-model" } export enum SharedLinkType { Album = "ALBUM", @@ -5040,3 +5439,11 @@ export enum OAuthTokenEndpointAuthMethod { ClientSecretPost = "client_secret_post", ClientSecretBasic = "client_secret_basic" } +export enum TriggerType { + AssetCreate = "AssetCreate", + PersonRecognized = "PersonRecognized" +} +export enum PluginTriggerType { + AssetCreate = "AssetCreate", + PersonRecognized = "PersonRecognized" +} diff --git a/package.json b/package.json index 18610fe4bc..d08ea46edf 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "description": "Monorepo for Immich", "private": true, - "packageManager": "pnpm@10.15.1+sha512.34e538c329b5553014ca8e8f4535997f96180a1d0f614339357449935350d924e22f8614682191264ec33d1462ac21561aff97f6bb18065351c162c7e8f6de67", + "packageManager": "pnpm@10.20.0+sha512.cf9998222162dd85864d0a8102e7892e7ba4ceadebbf5a31f9c2fce48dfce317a9c53b9f6464d1ef9042cba2e02ae02a9f7c143a2b438cd93c91840f0192b9dd", "engines": { "pnpm": ">=10.0.0" } diff --git a/plugins/.gitignore b/plugins/.gitignore new file mode 100644 index 0000000000..76add878f8 --- /dev/null +++ b/plugins/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist \ No newline at end of file diff --git a/plugins/LICENSE b/plugins/LICENSE new file mode 100644 index 0000000000..53f0fa6953 --- /dev/null +++ b/plugins/LICENSE @@ -0,0 +1,26 @@ +Copyright 2024, The Extism Authors. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/plugins/esbuild.js b/plugins/esbuild.js new file mode 100644 index 0000000000..04cb6e85aa --- /dev/null +++ b/plugins/esbuild.js @@ -0,0 +1,12 @@ +const esbuild = require('esbuild'); + +esbuild + .build({ + entryPoints: ['src/index.ts'], + outdir: 'dist', + bundle: true, + sourcemap: true, + minify: false, // might want to use true for production build + format: 'cjs', // needs to be CJS for now + target: ['es2020'] // don't go over es2020 because quickjs doesn't support it + }) \ No newline at end of file diff --git a/plugins/manifest.json b/plugins/manifest.json new file mode 100644 index 0000000000..1172530c1e --- /dev/null +++ b/plugins/manifest.json @@ -0,0 +1,127 @@ +{ + "name": "immich-core", + "version": "2.0.0", + "title": "Immich Core", + "description": "Core workflow capabilities for Immich", + "author": "Immich Team", + + "wasm": { + "path": "dist/plugin.wasm" + }, + + "filters": [ + { + "methodName": "filterFileName", + "title": "Filter by filename", + "description": "Filter assets by filename pattern using text matching or regular expressions", + "supportedContexts": ["asset"], + "schema": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Text or regex pattern to match against filename" + }, + "matchType": { + "type": "string", + "enum": ["contains", "regex", "exact"], + "default": "contains", + "description": "Type of pattern matching to perform" + }, + "caseSensitive": { + "type": "boolean", + "default": false, + "description": "Whether matching should be case-sensitive" + } + }, + "required": ["pattern"] + } + }, + { + "methodName": "filterFileType", + "title": "Filter by file type", + "description": "Filter assets by file type", + "supportedContexts": ["asset"], + "schema": { + "type": "object", + "properties": { + "fileTypes": { + "type": "array", + "items": { + "type": "string", + "enum": ["IMAGE", "VIDEO"] + }, + "description": "Allowed file types" + } + }, + "required": ["fileTypes"] + } + }, + { + "methodName": "filterPerson", + "title": "Filter by person", + "description": "Filter by detected person", + "supportedContexts": ["person"], + "schema": { + "type": "object", + "properties": { + "personIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of person to match" + }, + "matchAny": { + "type": "boolean", + "default": true, + "description": "Match any name (true) or require all names (false)" + } + }, + "required": ["personIds"] + } + } + ], + + "actions": [ + { + "methodName": "actionArchive", + "title": "Archive", + "description": "Move the asset to archive", + "supportedContexts": ["asset"], + "schema": {} + }, + { + "methodName": "actionFavorite", + "title": "Favorite", + "description": "Mark the asset as favorite or unfavorite", + "supportedContexts": ["asset"], + "schema": { + "type": "object", + "properties": { + "favorite": { + "type": "boolean", + "default": true, + "description": "Set favorite (true) or unfavorite (false)" + } + } + } + }, + { + "methodName": "actionAddToAlbum", + "title": "Add to Album", + "description": "Add the item to a specified album", + "supportedContexts": ["asset", "person"], + "schema": { + "type": "object", + "properties": { + "albumId": { + "type": "string", + "description": "Target album ID" + } + }, + "required": ["albumId"] + } + } + ] +} diff --git a/plugins/mise.toml b/plugins/mise.toml new file mode 100644 index 0000000000..c1001e574b --- /dev/null +++ b/plugins/mise.toml @@ -0,0 +1,11 @@ +[tools] +"github:extism/cli" = "1.6.3" +"github:webassembly/binaryen" = "version_124" +"github:extism/js-pdk" = "1.5.1" + +[tasks.install] +run = "pnpm install --frozen-lockfile" + +[tasks.build] +depends = ["install"] +run = "pnpm run build" diff --git a/plugins/package-lock.json b/plugins/package-lock.json new file mode 100644 index 0000000000..3b0f0b34cb --- /dev/null +++ b/plugins/package-lock.json @@ -0,0 +1,443 @@ +{ + "name": "js-pdk-template", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "js-pdk-template", + "version": "1.0.0", + "license": "BSD-3-Clause", + "devDependencies": { + "@extism/js-pdk": "^1.0.1", + "esbuild": "^0.19.6", + "typescript": "^5.3.2" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@extism/js-pdk": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@extism/js-pdk/-/js-pdk-1.0.1.tgz", + "integrity": "sha512-YJWfHGeOuJnQw4V8NPNHvbSr6S8iDd2Ga6VEukwlRP7tu62ozTxIgokYw8i+rajD/16zz/gK0KYARBpm2qPAmQ==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "node_modules/typescript": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", + "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + } + } +} diff --git a/plugins/package.json b/plugins/package.json new file mode 100644 index 0000000000..ab6b2f8435 --- /dev/null +++ b/plugins/package.json @@ -0,0 +1,19 @@ +{ + "name": "plugins", + "version": "1.0.0", + "description": "", + "main": "src/index.ts", + "scripts": { + "build": "pnpm build:tsc && pnpm build:wasm", + "build:tsc": "tsc --noEmit && node esbuild.js", + "build:wasm": "extism-js dist/index.js -i src/index.d.ts -o dist/plugin.wasm" + }, + "keywords": [], + "author": "", + "license": "AGPL-3.0", + "devDependencies": { + "@extism/js-pdk": "^1.0.1", + "esbuild": "^0.19.6", + "typescript": "^5.3.2" + } +} diff --git a/plugins/src/index.d.ts b/plugins/src/index.d.ts new file mode 100644 index 0000000000..7f805aafe6 --- /dev/null +++ b/plugins/src/index.d.ts @@ -0,0 +1,12 @@ +declare module 'main' { + export function filterFileName(): I32; + export function actionAddToAlbum(): I32; + export function actionArchive(): I32; +} + +declare module 'extism:host' { + interface user { + updateAsset(ptr: PTR): I32; + addAssetToAlbum(ptr: PTR): I32; + } +} diff --git a/plugins/src/index.ts b/plugins/src/index.ts new file mode 100644 index 0000000000..9566c02cd8 --- /dev/null +++ b/plugins/src/index.ts @@ -0,0 +1,71 @@ +const { updateAsset, addAssetToAlbum } = Host.getFunctions(); + +function parseInput() { + return JSON.parse(Host.inputString()); +} + +function returnOutput(output: any) { + Host.outputString(JSON.stringify(output)); + return 0; +} + +export function filterFileName() { + const input = parseInput(); + const { data, config } = input; + const { pattern, matchType = 'contains', caseSensitive = false } = config; + + const fileName = data.asset.originalFileName || data.asset.fileName || ''; + const searchName = caseSensitive ? fileName : fileName.toLowerCase(); + const searchPattern = caseSensitive ? pattern : pattern.toLowerCase(); + + let passed = false; + + if (matchType === 'exact') { + passed = searchName === searchPattern; + } else if (matchType === 'regex') { + const flags = caseSensitive ? '' : 'i'; + const regex = new RegExp(searchPattern, flags); + passed = regex.test(fileName); + } else { + // contains + passed = searchName.includes(searchPattern); + } + + return returnOutput({ passed }); +} + +export function actionAddToAlbum() { + const input = parseInput(); + const { authToken, config, data } = input; + const { albumId } = config; + + const ptr = Memory.fromString( + JSON.stringify({ + authToken, + assetId: data.asset.id, + albumId: albumId, + }) + ); + + addAssetToAlbum(ptr.offset); + ptr.free(); + + return returnOutput({ success: true }); +} + +export function actionArchive() { + const input = parseInput(); + const { authToken, data } = input; + const ptr = Memory.fromString( + JSON.stringify({ + authToken, + id: data.asset.id, + visibility: 'archive', + }) + ); + + updateAsset(ptr.offset); + ptr.free(); + + return returnOutput({ success: true }); +} diff --git a/plugins/tsconfig.json b/plugins/tsconfig.json new file mode 100644 index 0000000000..86c9e766bf --- /dev/null +++ b/plugins/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "es2020", // Specify ECMAScript target version + "module": "commonjs", // Specify module code generation + "lib": [ + "es2020" + ], // Specify a list of library files to be included in the compilation + "types": [ + "@extism/js-pdk", + "./src/index.d.ts" + ], // Specify a list of type definition files to be included in the compilation + "strict": true, // Enable all strict type-checking options + "esModuleInterop": true, // Enables compatibility with Babel-style module imports + "skipLibCheck": true, // Skip type checking of declaration files + "allowJs": true, // Allow JavaScript files to be compiled + "noEmit": true // Do not emit outputs (no .js or .d.ts files) + }, + "include": [ + "src/**/*.ts" // Include all TypeScript files in src directory + ], + "exclude": [ + "node_modules" // Exclude the node_modules directory + ] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7032720c81..c0e4b5ea78 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,9 +7,9 @@ settings: overrides: canvas: 2.11.2 - sharp: ^0.34.3 + sharp: ^0.34.4 -packageExtensionsChecksum: sha256-DAYr0FTkvKYnvBH4muAER9UE1FVGKhqfRU4/QwA2xPQ= +packageExtensionsChecksum: sha256-3l4AQg4iuprBDup+q+2JaPvbPg/7XodWCE0ZteH+s54= pnpmfileChecksum: sha256-AG/qwrPNpmy9q60PZwCpecoYVptglTHgH+N6RKQHOM0= @@ -43,7 +43,7 @@ importers: devDependencies: '@eslint/js': specifier: ^9.8.0 - version: 9.35.0 + version: 9.38.0 '@immich/sdk': specifier: file:../open-api/typescript-sdk version: link:../open-api/typescript-sdk @@ -58,16 +58,16 @@ importers: version: 4.17.12 '@types/micromatch': specifier: ^4.0.9 - version: 4.0.9 + version: 4.0.10 '@types/mock-fs': specifier: ^4.13.1 version: 4.13.4 '@types/node': - specifier: ^22.18.1 - version: 22.18.5 + specifier: ^22.19.0 + version: 22.19.0 '@vitest/coverage-v8': specifier: ^3.0.0 - version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.18.5)(happy-dom@18.0.1)(jiti@2.5.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)) + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.0)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) byte-size: specifier: ^9.0.0 version: 9.0.1 @@ -79,16 +79,16 @@ importers: version: 12.1.0 eslint: specifier: ^9.14.0 - version: 9.35.0(jiti@2.5.1) + version: 9.38.0(jiti@2.6.1) eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@9.35.0(jiti@2.5.1)) + version: 10.1.8(eslint@9.38.0(jiti@2.6.1)) eslint-plugin-prettier: specifier: ^5.1.3 - version: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1))(prettier@3.6.2) + version: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1))(prettier@3.6.2) eslint-plugin-unicorn: specifier: ^60.0.0 - version: 60.0.0(eslint@9.35.0(jiti@2.5.1)) + version: 60.0.0(eslint@9.38.0(jiti@2.6.1)) globals: specifier: ^16.0.0 version: 16.4.0 @@ -100,25 +100,25 @@ importers: version: 3.6.2 prettier-plugin-organize-imports: specifier: ^4.0.0 - version: 4.2.0(prettier@3.6.2)(typescript@5.9.2) + version: 4.3.0(prettier@3.6.2)(typescript@5.9.3) typescript: specifier: ^5.3.3 - version: 5.9.2 + version: 5.9.3 typescript-eslint: specifier: ^8.28.0 - version: 8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) + version: 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) vite: specifier: ^7.0.0 - version: 7.1.5(@types/node@22.18.5)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) + version: 7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) vite-tsconfig-paths: specifier: ^5.0.0 - version: 5.1.4(typescript@5.9.2)(vite@7.1.5(@types/node@22.18.5)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)) + version: 5.1.4(typescript@5.9.3)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) vitest: specifier: ^3.0.0 - version: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.5)(happy-dom@18.0.1)(jiti@2.5.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.0)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) vitest-fetch-mock: specifier: ^0.4.0 - version: 0.4.5(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.18.5)(happy-dom@18.0.1)(jiti@2.5.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)) + version: 0.4.5(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.0)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) yaml: specifier: ^2.3.1 version: 2.8.1 @@ -126,14 +126,14 @@ importers: docs: dependencies: '@docusaurus/core': - specifier: ~3.8.0 - version: 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + specifier: ~3.9.0 + version: 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) '@docusaurus/preset-classic': - specifier: ~3.8.0 - version: 3.8.1(@algolia/client-search@5.29.0)(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(@types/react@19.1.13)(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.9.2) + specifier: ~3.9.0 + version: 3.9.2(@algolia/client-search@5.41.0)(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.9.3) '@docusaurus/theme-common': - specifier: ~3.8.0 - version: 3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ~3.9.0 + version: 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mdi/js': specifier: ^7.3.67 version: 7.4.47 @@ -142,16 +142,13 @@ importers: version: 1.6.1 '@mdx-js/react': specifier: ^3.0.0 - version: 3.1.1(@types/react@19.1.13)(react@18.3.1) + version: 3.1.1(@types/react@19.2.2)(react@18.3.1) autoprefixer: specifier: ^10.4.17 version: 10.4.21(postcss@8.5.6) docusaurus-lunr-search: specifier: ^3.3.2 - version: 3.6.0(@docusaurus/core@3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - docusaurus-preset-openapi: - specifier: ^0.7.5 - version: 0.7.6(@algolia/client-search@5.29.0)(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(@types/react@19.1.13)(acorn@8.15.0)(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(redux@4.2.1)(search-insights@2.17.3)(typescript@5.9.2) + version: 3.6.0(@docusaurus/core@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) lunr: specifier: ^2.3.9 version: 2.3.9 @@ -163,7 +160,7 @@ importers: version: 2.4.1(react@18.3.1) raw-loader: specifier: ^4.0.2 - version: 4.0.2(webpack@5.100.2) + version: 4.0.2(webpack@5.102.1) react: specifier: ^18.0.0 version: 18.3.1 @@ -172,32 +169,32 @@ importers: version: 18.3.1(react@18.3.1) tailwindcss: specifier: ^3.2.4 - version: 3.4.17 + version: 3.4.18(yaml@2.8.1) url: specifier: ^0.11.0 version: 0.11.4 devDependencies: '@docusaurus/module-type-aliases': - specifier: ~3.8.0 - version: 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ~3.9.0 + version: 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/tsconfig': specifier: ^3.7.0 - version: 3.8.1 + version: 3.9.2 '@docusaurus/types': specifier: ^3.7.0 - version: 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) prettier: specifier: ^3.2.4 version: 3.6.2 typescript: specifier: ^5.1.6 - version: 5.9.2 + version: 5.9.3 e2e: devDependencies: '@eslint/js': specifier: ^9.8.0 - version: 9.35.0 + version: 9.38.0 '@immich/cli': specifier: file:../cli version: link:../cli @@ -206,7 +203,7 @@ importers: version: link:../open-api/typescript-sdk '@playwright/test': specifier: ^1.44.1 - version: 1.55.0 + version: 1.56.1 '@socket.io/component-emitter': specifier: ^3.1.2 version: 3.1.2 @@ -214,14 +211,14 @@ importers: specifier: ^3.4.2 version: 3.7.1 '@types/node': - specifier: ^22.18.1 - version: 22.18.5 + specifier: ^22.19.0 + version: 22.19.0 '@types/oidc-provider': specifier: ^9.0.0 version: 9.5.0 '@types/pg': specifier: ^8.15.1 - version: 8.15.5 + version: 8.15.6 '@types/pngjs': specifier: ^6.0.4 version: 6.0.5 @@ -230,19 +227,19 @@ importers: version: 6.0.3 eslint: specifier: ^9.14.0 - version: 9.35.0(jiti@2.5.1) + version: 9.38.0(jiti@2.6.1) eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@9.35.0(jiti@2.5.1)) + version: 10.1.8(eslint@9.38.0(jiti@2.6.1)) eslint-plugin-prettier: specifier: ^5.1.3 - version: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1))(prettier@3.6.2) + version: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1))(prettier@3.6.2) eslint-plugin-unicorn: specifier: ^60.0.0 - version: 60.0.0(eslint@9.35.0(jiti@2.5.1)) + version: 60.0.0(eslint@9.38.0(jiti@2.6.1)) exiftool-vendored: - specifier: ^28.3.1 - version: 28.8.0 + specifier: ^31.1.0 + version: 31.3.0 globals: specifier: ^16.0.0 version: 16.4.0 @@ -254,7 +251,7 @@ importers: version: 3.7.2 oidc-provider: specifier: ^9.0.0 - version: 9.5.1 + version: 9.5.2 pg: specifier: ^8.11.3 version: 8.16.3 @@ -266,10 +263,10 @@ importers: version: 3.6.2 prettier-plugin-organize-imports: specifier: ^4.0.0 - version: 4.2.0(prettier@3.6.2)(typescript@5.9.2) + version: 4.3.0(prettier@3.6.2)(typescript@5.9.3) sharp: - specifier: ^0.34.3 - version: 0.34.3 + specifier: ^0.34.4 + version: 0.34.4 socket.io-client: specifier: ^4.7.4 version: 4.8.1 @@ -278,16 +275,16 @@ importers: version: 7.1.4 typescript: specifier: ^5.3.3 - version: 5.9.2 + version: 5.9.3 typescript-eslint: specifier: ^8.28.0 - version: 8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) + version: 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) utimes: specifier: ^5.2.1 version: 5.2.1(encoding@0.1.13) vitest: specifier: ^3.0.0 - version: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.5)(happy-dom@18.0.1)(jiti@2.5.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.0)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) open-api/typescript-sdk: dependencies: @@ -296,80 +293,98 @@ importers: version: 1.0.4 devDependencies: '@types/node': - specifier: ^22.18.1 - version: 22.18.5 + specifier: ^22.19.0 + version: 22.19.0 typescript: specifier: ^5.3.3 - version: 5.9.2 + version: 5.9.3 + + plugins: + devDependencies: + '@extism/js-pdk': + specifier: ^1.0.1 + version: 1.1.1 + esbuild: + specifier: ^0.19.6 + version: 0.19.12 + typescript: + specifier: ^5.3.2 + version: 5.9.3 server: dependencies: + '@extism/extism': + specifier: 2.0.0-rc13 + version: 2.0.0-rc13 '@nestjs/bullmq': specifier: ^11.0.1 - version: 11.0.3(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6)(bullmq@5.58.5) + version: 11.0.4(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8)(bullmq@5.62.1) '@nestjs/common': specifier: ^11.0.4 - version: 11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + version: 11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': specifier: ^11.0.4 - version: 11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.6)(@nestjs/websockets@11.1.6)(reflect-metadata@0.2.2)(rxjs@7.8.2) + version: 11.1.8(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.8)(@nestjs/websockets@11.1.8)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/platform-express': specifier: ^11.0.4 - version: 11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6) + version: 11.1.8(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8) '@nestjs/platform-socket.io': specifier: ^11.0.4 - version: 11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.6)(rxjs@7.8.2) + version: 11.1.8(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.8)(rxjs@7.8.2) '@nestjs/schedule': specifier: ^6.0.0 - version: 6.0.0(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6) + version: 6.0.1(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8) '@nestjs/swagger': specifier: ^11.0.2 - version: 11.2.0(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2) + version: 11.2.1(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2) '@nestjs/websockets': specifier: ^11.0.4 - version: 11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6)(@nestjs/platform-socket.io@11.1.6)(reflect-metadata@0.2.2)(rxjs@7.8.2) + version: 11.1.8(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8)(@nestjs/platform-socket.io@11.1.8)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@opentelemetry/api': specifier: ^1.9.0 version: 1.9.0 '@opentelemetry/context-async-hooks': specifier: ^2.0.0 - version: 2.1.0(@opentelemetry/api@1.9.0) + version: 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/exporter-prometheus': - specifier: ^0.205.0 - version: 0.205.0(@opentelemetry/api@1.9.0) + specifier: ^0.207.0 + version: 0.207.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-http': - specifier: ^0.205.0 - version: 0.205.0(@opentelemetry/api@1.9.0) + specifier: ^0.207.0 + version: 0.207.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-ioredis': - specifier: ^0.53.0 - version: 0.53.0(@opentelemetry/api@1.9.0) + specifier: ^0.55.0 + version: 0.55.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-nestjs-core': - specifier: ^0.51.0 - version: 0.51.0(@opentelemetry/api@1.9.0) + specifier: ^0.54.0 + version: 0.54.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-pg': - specifier: ^0.58.0 - version: 0.58.0(@opentelemetry/api@1.9.0) + specifier: ^0.60.0 + version: 0.60.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': specifier: ^2.0.1 - version: 2.1.0(@opentelemetry/api@1.9.0) + version: 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-metrics': specifier: ^2.0.1 - version: 2.1.0(@opentelemetry/api@1.9.0) + version: 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-node': - specifier: ^0.205.0 - version: 0.205.0(@opentelemetry/api@1.9.0) + specifier: ^0.207.0 + version: 0.207.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': specifier: ^1.34.0 version: 1.37.0 '@react-email/components': specifier: ^0.5.0 - version: 0.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 0.5.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@react-email/render': specifier: ^1.1.2 - version: 1.2.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 1.4.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@socket.io/redis-adapter': specifier: ^8.3.0 version: 8.3.0(socket.io-adapter@2.5.5) + ajv: + specifier: ^8.17.1 + version: 8.17.1 archiver: specifier: ^7.0.0 version: 7.0.1 @@ -384,7 +399,7 @@ importers: version: 2.2.0 bullmq: specifier: ^5.51.0 - version: 5.58.5 + version: 5.62.1 chokidar: specifier: ^4.0.3 version: 4.0.3 @@ -404,11 +419,11 @@ importers: specifier: ^1.4.7 version: 1.4.7 cron: - specifier: 4.3.0 - version: 4.3.0 + specifier: 4.3.3 + version: 4.3.3 exiftool-vendored: - specifier: ^28.8.0 - version: 28.8.0 + specifier: ^31.1.0 + version: 31.3.0 express: specifier: ^5.1.0 version: 5.1.0 @@ -428,17 +443,20 @@ importers: specifier: ^7.6.0 version: 7.14.0 ioredis: - specifier: ^5.3.2 - version: 5.7.0 + specifier: ^5.8.2 + version: 5.8.2 js-yaml: specifier: ^4.1.0 version: 4.1.0 + jsonwebtoken: + specifier: ^9.0.2 + version: 9.0.2 kysely: specifier: 0.28.2 version: 0.28.2 kysely-postgres-js: - specifier: ^2.0.0 - version: 2.0.0(kysely@0.28.2)(postgres@3.4.7) + specifier: ^3.0.0 + version: 3.0.0(kysely@0.28.2)(postgres@3.4.7) lodash: specifier: ^4.17.21 version: 4.17.21 @@ -453,22 +471,22 @@ importers: version: 2.0.2 nest-commander: specifier: ^3.16.0 - version: 3.19.1(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6)(@types/inquirer@8.2.11)(@types/node@22.18.5)(typescript@5.9.2) + version: 3.20.1(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8)(@types/inquirer@8.2.11)(@types/node@22.19.0)(typescript@5.9.3) nestjs-cls: specifier: ^5.0.0 - version: 5.4.3(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6)(reflect-metadata@0.2.2)(rxjs@7.8.2) + version: 5.4.3(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8)(reflect-metadata@0.2.2)(rxjs@7.8.2) nestjs-kysely: - specifier: ^3.0.0 - version: 3.0.0(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6)(kysely@0.28.2)(reflect-metadata@0.2.2) + specifier: 3.1.2 + version: 3.1.2(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8)(kysely@0.28.2)(reflect-metadata@0.2.2) nestjs-otel: specifier: ^7.0.0 - version: 7.0.1(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6) + version: 7.0.1(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8) nodemailer: specifier: ^7.0.0 - version: 7.0.6 + version: 7.0.10 openid-client: specifier: ^6.3.3 - version: 6.8.0 + version: 6.8.1 pg: specifier: ^8.11.3 version: 8.16.3 @@ -483,13 +501,13 @@ importers: version: 3.4.7 react: specifier: ^19.0.0 - version: 19.1.1 + version: 19.2.0 react-dom: specifier: ^19.0.0 - version: 19.1.1(react@19.1.1) + version: 19.2.0(react@19.2.0) react-email: specifier: ^4.0.0 - version: 4.2.11 + version: 4.3.2 reflect-metadata: specifier: ^0.2.0 version: 0.2.2 @@ -504,10 +522,10 @@ importers: version: 2.17.0 semver: specifier: ^7.6.2 - version: 7.7.2 + version: 7.7.3 sharp: - specifier: ^0.34.3 - version: 0.34.3 + specifier: ^0.34.4 + version: 0.34.4 sirv: specifier: ^3.0.0 version: 3.0.2 @@ -516,38 +534,38 @@ importers: version: 4.8.1 tailwindcss-preset-email: specifier: ^1.4.0 - version: 1.4.0(tailwindcss@3.4.17) + version: 1.4.1(tailwindcss@3.4.18(yaml@2.8.1)) thumbhash: specifier: ^0.1.1 version: 0.1.1 ua-parser-js: specifier: ^2.0.0 - version: 2.0.5 + version: 2.0.6 uuid: specifier: ^11.1.0 version: 11.1.0 validator: specifier: ^13.12.0 - version: 13.15.15 + version: 13.15.20 devDependencies: '@eslint/js': specifier: ^9.8.0 - version: 9.35.0 + version: 9.38.0 '@nestjs/cli': specifier: ^11.0.2 - version: 11.0.10(@swc/core@1.13.5(@swc/helpers@0.5.17))(@types/node@22.18.5) + version: 11.0.10(@swc/core@1.14.0(@swc/helpers@0.5.17))(@types/node@22.19.0) '@nestjs/schematics': specifier: ^11.0.0 - version: 11.0.7(chokidar@4.0.3)(typescript@5.9.2) + version: 11.0.9(chokidar@4.0.3)(typescript@5.9.3) '@nestjs/testing': specifier: ^11.0.4 - version: 11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6)(@nestjs/platform-express@11.1.6) + version: 11.1.8(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8)(@nestjs/platform-express@11.1.8) '@swc/core': specifier: ^1.4.14 - version: 1.13.5(@swc/helpers@0.5.17) + version: 1.14.0(@swc/helpers@0.5.17) '@types/archiver': specifier: ^6.0.0 - version: 6.0.3 + version: 6.0.4 '@types/async-lock': specifier: ^1.4.2 version: 1.4.2 @@ -562,16 +580,19 @@ importers: version: 1.8.1 '@types/cookie-parser': specifier: ^1.4.8 - version: 1.4.9(@types/express@5.0.3) + version: 1.4.10(@types/express@5.0.5) '@types/express': specifier: ^5.0.0 - version: 5.0.3 + version: 5.0.5 '@types/fluent-ffmpeg': specifier: ^2.1.21 - version: 2.1.27 + version: 2.1.28 '@types/js-yaml': specifier: ^4.0.9 version: 4.0.9 + '@types/jsonwebtoken': + specifier: ^9.0.10 + version: 9.0.10 '@types/lodash': specifier: ^4.14.197 version: 4.17.20 @@ -585,11 +606,11 @@ importers: specifier: ^2.0.0 version: 2.0.0 '@types/node': - specifier: ^22.18.1 - version: 22.18.5 + specifier: ^22.19.0 + version: 22.19.0 '@types/nodemailer': specifier: ^7.0.0 - version: 7.0.1 + version: 7.0.3 '@types/picomatch': specifier: ^4.0.0 version: 4.0.2 @@ -598,7 +619,7 @@ importers: version: 6.0.5 '@types/react': specifier: ^19.0.0 - version: 19.1.13 + version: 19.2.2 '@types/sanitize-html': specifier: ^2.13.0 version: 2.16.0 @@ -613,22 +634,22 @@ importers: version: 0.7.39 '@types/validator': specifier: ^13.15.2 - version: 13.15.3 + version: 13.15.4 '@vitest/coverage-v8': specifier: ^3.0.0 - version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.18.5)(happy-dom@18.0.1)(jiti@2.5.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)) + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.0)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) eslint: specifier: ^9.14.0 - version: 9.35.0(jiti@2.5.1) + version: 9.38.0(jiti@2.6.1) eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@9.35.0(jiti@2.5.1)) + version: 10.1.8(eslint@9.38.0(jiti@2.6.1)) eslint-plugin-prettier: specifier: ^5.1.3 - version: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1))(prettier@3.6.2) + version: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1))(prettier@3.6.2) eslint-plugin-unicorn: specifier: ^60.0.0 - version: 60.0.0(eslint@9.35.0(jiti@2.5.1)) + version: 60.0.0(eslint@9.38.0(jiti@2.6.1)) globals: specifier: ^16.0.0 version: 16.4.0 @@ -637,7 +658,7 @@ importers: version: 5.5.0 node-gyp: specifier: ^11.2.0 - version: 11.4.2 + version: 11.5.0 pngjs: specifier: ^7.0.0 version: 7.0.0 @@ -646,46 +667,49 @@ importers: version: 3.6.2 prettier-plugin-organize-imports: specifier: ^4.0.0 - version: 4.2.0(prettier@3.6.2)(typescript@5.9.2) + version: 4.3.0(prettier@3.6.2)(typescript@5.9.3) sql-formatter: specifier: ^15.0.0 - version: 15.6.9 + version: 15.6.10 supertest: specifier: ^7.1.0 version: 7.1.4 tailwindcss: specifier: ^3.4.0 - version: 3.4.17 + version: 3.4.18(yaml@2.8.1) testcontainers: specifier: ^11.0.0 - version: 11.5.1 + version: 11.7.2 typescript: specifier: ^5.9.2 - version: 5.9.2 + version: 5.9.3 typescript-eslint: specifier: ^8.28.0 - version: 8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) + version: 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) unplugin-swc: specifier: ^1.4.5 - version: 1.5.7(@swc/core@1.13.5(@swc/helpers@0.5.17))(rollup@4.50.1) + version: 1.5.8(@swc/core@1.14.0(@swc/helpers@0.5.17))(rollup@4.52.5) vite-tsconfig-paths: specifier: ^5.0.0 - version: 5.1.4(typescript@5.9.2)(vite@7.1.5(@types/node@22.18.5)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)) + version: 5.1.4(typescript@5.9.3)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) vitest: specifier: ^3.0.0 - version: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.5)(happy-dom@18.0.1)(jiti@2.5.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.0)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) web: dependencies: '@formatjs/icu-messageformat-parser': specifier: ^2.9.8 - version: 2.11.2 + version: 2.11.4 + '@immich/justified-layout-wasm': + specifier: ^0.4.3 + version: 0.4.3 '@immich/sdk': specifier: file:../open-api/typescript-sdk version: link:../open-api/typescript-sdk '@immich/ui': - specifier: ^0.29.0 - version: 0.29.0(@internationalized/date@3.8.2)(svelte@5.38.10) + specifier: ^0.43.0 + version: 0.43.0(@internationalized/date@3.8.2)(svelte@5.43.0) '@mapbox/mapbox-gl-rtl-text': specifier: 0.2.3 version: 0.2.3(mapbox-gl@1.13.3) @@ -712,10 +736,10 @@ importers: version: 7946.0.16 '@zoom-image/core': specifier: ^0.41.0 - version: 0.41.0 + version: 0.41.3 '@zoom-image/svelte': specifier: ^0.3.0 - version: 0.3.4(svelte@5.38.10) + version: 0.3.7(svelte@5.43.0) async-mutex: specifier: ^0.5.0 version: 0.5.0 @@ -735,11 +759,11 @@ importers: specifier: ^4.7.8 version: 4.7.8 happy-dom: - specifier: ^18.0.1 - version: 18.0.1 + specifier: ^20.0.0 + version: 20.0.10 intl-messageformat: specifier: ^10.7.11 - version: 10.7.16 + version: 10.7.18 justified-layout: specifier: ^4.1.0 version: 4.1.0 @@ -751,7 +775,7 @@ importers: version: 3.7.2 maplibre-gl: specifier: ^5.6.2 - version: 5.7.1 + version: 5.10.0 pmtiles: specifier: ^4.3.0 version: 4.3.0 @@ -760,7 +784,7 @@ importers: version: 1.5.4 simple-icons: specifier: ^15.15.0 - version: 15.15.0 + version: 15.18.0 socket.io-client: specifier: ~4.8.0 version: 4.8.1 @@ -769,53 +793,53 @@ importers: version: 5.2.2 svelte-i18n: specifier: ^4.0.1 - version: 4.0.1(svelte@5.38.10) + version: 4.0.1(svelte@5.43.0) svelte-maplibre: - specifier: ^1.2.0 - version: 1.2.1(svelte@5.38.10) + specifier: ^1.2.5 + version: 1.2.5(svelte@5.43.0) svelte-persisted-store: specifier: ^0.12.0 - version: 0.12.0(svelte@5.38.10) + version: 0.12.0(svelte@5.43.0) tabbable: specifier: ^6.2.0 - version: 6.2.0 + version: 6.3.0 thumbhash: specifier: ^0.1.1 version: 0.1.1 devDependencies: '@eslint/js': - specifier: ^9.18.0 - version: 9.35.0 + specifier: ^9.36.0 + version: 9.38.0 '@faker-js/faker': specifier: ^10.0.0 - version: 10.0.0 + version: 10.1.0 '@koddsson/eslint-plugin-tscompat': specifier: ^0.2.0 - version: 0.2.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) + version: 0.2.0(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) '@socket.io/component-emitter': specifier: ^3.1.0 version: 3.1.2 '@sveltejs/adapter-static': specifier: ^3.0.8 - version: 3.0.9(@sveltejs/kit@2.38.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.0(svelte@5.38.10)(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)))(svelte@5.38.10)(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1))) + version: 3.0.10(@sveltejs/kit@2.48.3(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.0)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))(svelte@5.43.0)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))) '@sveltejs/enhanced-img': specifier: ^0.8.0 - version: 0.8.1(@sveltejs/vite-plugin-svelte@6.2.0(svelte@5.38.10)(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)))(rollup@4.50.1)(svelte@5.38.10)(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)) + version: 0.8.4(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.0)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))(rollup@4.52.5)(svelte@5.43.0)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) '@sveltejs/kit': specifier: ^2.27.1 - version: 2.38.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.0(svelte@5.38.10)(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)))(svelte@5.38.10)(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)) + version: 2.48.3(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.0)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))(svelte@5.43.0)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) '@sveltejs/vite-plugin-svelte': - specifier: 6.2.0 - version: 6.2.0(svelte@5.38.10)(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)) + specifier: 6.2.1 + version: 6.2.1(svelte@5.43.0)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) '@tailwindcss/vite': specifier: ^4.1.7 - version: 4.1.13(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)) + version: 4.1.16(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) '@testing-library/jest-dom': specifier: ^6.4.2 - version: 6.8.0 + version: 6.9.1 '@testing-library/svelte': specifier: ^5.2.8 - version: 5.2.8(svelte@5.38.10)(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.5.1)(happy-dom@18.0.1)(jiti@2.5.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)) + version: 5.2.8(svelte@5.43.0)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.0)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) '@testing-library/user-event': specifier: ^14.5.2 version: 14.6.1(@testing-library/dom@10.4.0) @@ -836,31 +860,28 @@ importers: version: 3.7.1 '@types/qrcode': specifier: ^1.5.5 - version: 1.5.5 + version: 1.5.6 '@vitest/coverage-v8': specifier: ^3.0.0 - version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.5.1)(happy-dom@18.0.1)(jiti@2.5.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)) + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.0)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) dotenv: specifier: ^17.0.0 - version: 17.2.2 + version: 17.2.3 eslint: - specifier: ^9.18.0 - version: 9.35.0(jiti@2.5.1) + specifier: ^9.36.0 + version: 9.38.0(jiti@2.6.1) eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@9.35.0(jiti@2.5.1)) - eslint-p: - specifier: ^0.26.0 - version: 0.26.0(jiti@2.5.1) + version: 10.1.8(eslint@9.38.0(jiti@2.6.1)) eslint-plugin-compat: specifier: ^6.0.2 - version: 6.0.2(eslint@9.35.0(jiti@2.5.1)) + version: 6.0.2(eslint@9.38.0(jiti@2.6.1)) eslint-plugin-svelte: - specifier: ^3.9.0 - version: 3.12.3(eslint@9.35.0(jiti@2.5.1))(svelte@5.38.10) + specifier: ^3.12.4 + version: 3.12.5(eslint@9.38.0(jiti@2.6.1))(svelte@5.43.0) eslint-plugin-unicorn: - specifier: ^60.0.0 - version: 60.0.0(eslint@9.35.0(jiti@2.5.1)) + specifier: ^61.0.2 + version: 61.0.2(eslint@9.38.0(jiti@2.6.1)) factory.ts: specifier: ^1.4.1 version: 1.4.2 @@ -872,119 +893,143 @@ importers: version: 3.6.2 prettier-plugin-organize-imports: specifier: ^4.0.0 - version: 4.2.0(prettier@3.6.2)(typescript@5.9.2) + version: 4.3.0(prettier@3.6.2)(typescript@5.9.3) prettier-plugin-sort-json: specifier: ^4.1.1 version: 4.1.1(prettier@3.6.2) prettier-plugin-svelte: specifier: ^3.3.3 - version: 3.4.0(prettier@3.6.2)(svelte@5.38.10) + version: 3.4.0(prettier@3.6.2)(svelte@5.43.0) rollup-plugin-visualizer: specifier: ^6.0.0 - version: 6.0.3(rollup@4.50.1) + version: 6.0.5(rollup@4.52.5) svelte: - specifier: 5.38.10 - version: 5.38.10 + specifier: 5.43.0 + version: 5.43.0 svelte-check: specifier: ^4.1.5 - version: 4.3.1(picomatch@4.0.3)(svelte@5.38.10)(typescript@5.9.2) + version: 4.3.3(picomatch@4.0.3)(svelte@5.43.0)(typescript@5.9.3) svelte-eslint-parser: - specifier: ^1.2.0 - version: 1.3.2(svelte@5.38.10) + specifier: ^1.3.3 + version: 1.4.0(svelte@5.43.0) tailwindcss: specifier: ^4.1.7 - version: 4.1.13 + version: 4.1.16 typescript: specifier: ^5.8.3 - version: 5.9.2 + version: 5.9.3 typescript-eslint: - specifier: ^8.28.0 - version: 8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) + specifier: ^8.45.0 + version: 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) vite: specifier: ^7.1.2 - version: 7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) + version: 7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) vitest: specifier: ^3.0.0 - version: 3.2.4(@types/debug@4.1.12)(@types/node@24.5.1)(happy-dom@18.0.1)(jiti@2.5.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.0)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) packages: '@adobe/css-tools@4.4.4': resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} - '@algolia/autocomplete-core@1.17.9': - resolution: {integrity: sha512-O7BxrpLDPJWWHv/DLA9DRFWs+iY1uOJZkqUwjS5HSZAGcl0hIVCQ97LTLewiZmZ402JYUrun+8NqFP+hCknlbQ==} + '@ai-sdk/gateway@2.0.3': + resolution: {integrity: sha512-/vCoMKtod+A74/BbkWsaAflWKz1ovhX5lmJpIaXQXtd6gyexZncjotBTbFM8rVJT9LKJ/Kx7iVVo3vh+KT+IJg==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 - '@algolia/autocomplete-plugin-algolia-insights@1.17.9': - resolution: {integrity: sha512-u1fEHkCbWF92DBeB/KHeMacsjsoI0wFhjZtlCq2ddZbAehshbZST6Hs0Avkc0s+4UyBGbMDnSuXHLuvRWK5iDQ==} + '@ai-sdk/provider-utils@3.0.14': + resolution: {integrity: sha512-CYRU6L7IlR7KslSBVxvlqlybQvXJln/PI57O8swhOaDIURZbjRP2AY3igKgUsrmWqqnFFUHP+AwTN8xqJAknnA==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider@2.0.0': + resolution: {integrity: sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==} + engines: {node: '>=18'} + + '@ai-sdk/react@2.0.82': + resolution: {integrity: sha512-InaGqykKGFq/XA6Vhh2Hyy38nzeMpqp8eWxjTNEQA5Gwcal0BVNuZyTbTIL5t5VNXV+pQPDhe9ak1+mc9qxjog==} + engines: {node: '>=18'} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + zod: ^3.25.76 || ^4.1.8 + peerDependenciesMeta: + zod: + optional: true + + '@algolia/abtesting@1.7.0': + resolution: {integrity: sha512-hOEItTFOvNLI6QX6TSGu7VE4XcUcdoKZT8NwDY+5mWwu87rGhkjlY7uesKTInlg6Sh8cyRkDBYRumxbkoBbBhA==} + engines: {node: '>= 14.0.0'} + + '@algolia/autocomplete-core@1.19.2': + resolution: {integrity: sha512-mKv7RyuAzXvwmq+0XRK8HqZXt9iZ5Kkm2huLjgn5JoCPtDy+oh9yxUMfDDaVCw0oyzZ1isdJBc7l9nuCyyR7Nw==} + + '@algolia/autocomplete-plugin-algolia-insights@1.19.2': + resolution: {integrity: sha512-TjxbcC/r4vwmnZaPwrHtkXNeqvlpdyR+oR9Wi2XyfORkiGkLTVhX2j+O9SaCCINbKoDfc+c2PB8NjfOnz7+oKg==} peerDependencies: search-insights: '>= 1 < 3' - '@algolia/autocomplete-preset-algolia@1.17.9': - resolution: {integrity: sha512-Na1OuceSJeg8j7ZWn5ssMu/Ax3amtOwk76u4h5J4eK2Nx2KB5qt0Z4cOapCsxot9VcEN11ADV5aUSlQF4RhGjQ==} + '@algolia/autocomplete-shared@1.19.2': + resolution: {integrity: sha512-jEazxZTVD2nLrC+wYlVHQgpBoBB5KPStrJxLzsIFl6Kqd1AlG9sIAGl39V5tECLpIQzB3Qa2T6ZPJ1ChkwMK/w==} peerDependencies: '@algolia/client-search': '>= 4.9.1 < 6' algoliasearch: '>= 4.9.1 < 6' - '@algolia/autocomplete-shared@1.17.9': - resolution: {integrity: sha512-iDf05JDQ7I0b7JEA/9IektxN/80a2MZ1ToohfmNS3rfeuQnIKI3IJlIafD0xu4StbtQTghx9T3Maa97ytkXenQ==} - peerDependencies: - '@algolia/client-search': '>= 4.9.1 < 6' - algoliasearch: '>= 4.9.1 < 6' - - '@algolia/client-abtesting@5.29.0': - resolution: {integrity: sha512-AM/6LYMSTnZvAT5IarLEKjYWOdV+Fb+LVs8JRq88jn8HH6bpVUtjWdOZXqX1hJRXuCAY8SdQfb7F8uEiMNXdYQ==} + '@algolia/client-abtesting@5.41.0': + resolution: {integrity: sha512-iRuvbEyuHCAhIMkyzG3tfINLxTS7mSKo7q8mQF+FbQpWenlAlrXnfZTN19LRwnVjx0UtAdZq96ThMWGS6cQ61A==} engines: {node: '>= 14.0.0'} - '@algolia/client-analytics@5.29.0': - resolution: {integrity: sha512-La34HJh90l0waw3wl5zETO8TuukeUyjcXhmjYZL3CAPLggmKv74mobiGRIb+mmBENybiFDXf/BeKFLhuDYWMMQ==} + '@algolia/client-analytics@5.41.0': + resolution: {integrity: sha512-OIPVbGfx/AO8l1V70xYTPSeTt/GCXPEl6vQICLAXLCk9WOUbcLGcy6t8qv0rO7Z7/M/h9afY6Af8JcnI+FBFdQ==} engines: {node: '>= 14.0.0'} - '@algolia/client-common@5.29.0': - resolution: {integrity: sha512-T0lzJH/JiCxQYtCcnWy7Jf1w/qjGDXTi2npyF9B9UsTvXB97GRC6icyfXxe21mhYvhQcaB1EQ/J2575FXxi2rA==} + '@algolia/client-common@5.41.0': + resolution: {integrity: sha512-8Mc9niJvfuO8dudWN5vSUlYkz7U3M3X3m1crDLc9N7FZrIVoNGOUETPk3TTHviJIh9y6eKZKbq1hPGoGY9fqPA==} engines: {node: '>= 14.0.0'} - '@algolia/client-insights@5.29.0': - resolution: {integrity: sha512-A39F1zmHY9aev0z4Rt3fTLcGN5AG1VsVUkVWy6yQG5BRDScktH+U5m3zXwThwniBTDV1HrPgiGHZeWb67GkR2Q==} + '@algolia/client-insights@5.41.0': + resolution: {integrity: sha512-vXzvCGZS6Ixxn+WyzGUVDeR3HO/QO5POeeWy1kjNJbEf6f+tZSI+OiIU9Ha+T3ntV8oXFyBEuweygw4OLmgfiQ==} engines: {node: '>= 14.0.0'} - '@algolia/client-personalization@5.29.0': - resolution: {integrity: sha512-ibxmh2wKKrzu5du02gp8CLpRMeo+b/75e4ORct98CT7mIxuYFXowULwCd6cMMkz/R0LpKXIbTUl15UL5soaiUQ==} + '@algolia/client-personalization@5.41.0': + resolution: {integrity: sha512-tkymXhmlcc7w/HEvLRiHcpHxLFcUB+0PnE9FcG6hfFZ1ZXiWabH+sX+uukCVnluyhfysU9HRU2kUmUWfucx1Dg==} engines: {node: '>= 14.0.0'} - '@algolia/client-query-suggestions@5.29.0': - resolution: {integrity: sha512-VZq4/AukOoJC2WSwF6J5sBtt+kImOoBwQc1nH3tgI+cxJBg7B77UsNC+jT6eP2dQCwGKBBRTmtPLUTDDnHpMgA==} + '@algolia/client-query-suggestions@5.41.0': + resolution: {integrity: sha512-vyXDoz3kEZnosNeVQQwf0PbBt5IZJoHkozKRIsYfEVm+ylwSDFCW08qy2YIVSHdKy69/rWN6Ue/6W29GgVlmKQ==} engines: {node: '>= 14.0.0'} - '@algolia/client-search@5.29.0': - resolution: {integrity: sha512-cZ0Iq3OzFUPpgszzDr1G1aJV5UMIZ4VygJ2Az252q4Rdf5cQMhYEIKArWY/oUjMhQmosM8ygOovNq7gvA9CdCg==} + '@algolia/client-search@5.41.0': + resolution: {integrity: sha512-G9I2atg1ShtFp0t7zwleP6aPS4DcZvsV4uoQOripp16aR6VJzbEnKFPLW4OFXzX7avgZSpYeBAS+Zx4FOgmpPw==} engines: {node: '>= 14.0.0'} '@algolia/events@4.0.1': resolution: {integrity: sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==} - '@algolia/ingestion@1.29.0': - resolution: {integrity: sha512-scBXn0wO5tZCxmO6evfa7A3bGryfyOI3aoXqSQBj5SRvNYXaUlFWQ/iKI70gRe/82ICwE0ICXbHT/wIvxOW7vw==} + '@algolia/ingestion@1.41.0': + resolution: {integrity: sha512-sxU/ggHbZtmrYzTkueTXXNyifn+ozsLP+Wi9S2hOBVhNWPZ8uRiDTDcFyL7cpCs1q72HxPuhzTP5vn4sUl74cQ==} engines: {node: '>= 14.0.0'} - '@algolia/monitoring@1.29.0': - resolution: {integrity: sha512-FGWWG9jLFhsKB7YiDjM2dwQOYnWu//7Oxrb2vT96N7+s+hg1mdHHfHNRyEudWdxd4jkMhBjeqNA21VbTiOIPVg==} + '@algolia/monitoring@1.41.0': + resolution: {integrity: sha512-UQ86R6ixraHUpd0hn4vjgTHbViNO8+wA979gJmSIsRI3yli2v89QSFF/9pPcADR6PbtSio/99PmSNxhZy+CR3Q==} engines: {node: '>= 14.0.0'} - '@algolia/recommend@5.29.0': - resolution: {integrity: sha512-xte5+mpdfEARAu61KXa4ewpjchoZuJlAlvQb8ptK6hgHlBHDnYooy1bmOFpokaAICrq/H9HpoqNUX71n+3249A==} + '@algolia/recommend@5.41.0': + resolution: {integrity: sha512-DxP9P8jJ8whJOnvmyA5mf1wv14jPuI0L25itGfOHSU6d4ZAjduVfPjTS3ROuUN5CJoTdlidYZE+DtfWHxJwyzQ==} engines: {node: '>= 14.0.0'} - '@algolia/requester-browser-xhr@5.29.0': - resolution: {integrity: sha512-og+7Em75aPHhahEUScq2HQ3J7ULN63Levtd87BYMpn6Im5d5cNhaC4QAUsXu6LWqxRPgh4G+i+wIb6tVhDhg2A==} + '@algolia/requester-browser-xhr@5.41.0': + resolution: {integrity: sha512-C21J+LYkE48fDwtLX7YXZd2Fn7Fe0/DOEtvohSfr/ODP8dGDhy9faaYeWB0n1AvmZltugjkjAXT7xk0CYNIXsQ==} engines: {node: '>= 14.0.0'} - '@algolia/requester-fetch@5.29.0': - resolution: {integrity: sha512-JCxapz7neAy8hT/nQpCvOrI5JO8VyQ1kPvBiaXWNC1prVq0UMYHEL52o1BsPvtXfdQ7BVq19OIq6TjOI06mV/w==} + '@algolia/requester-fetch@5.41.0': + resolution: {integrity: sha512-FhJy/+QJhMx1Hajf2LL8og4J7SqOAHiAuUXq27cct4QnPhSIuIGROzeRpfDNH5BUbq22UlMuGd44SeD4HRAqvA==} engines: {node: '>= 14.0.0'} - '@algolia/requester-node-http@5.29.0': - resolution: {integrity: sha512-lVBD81RBW5VTdEYgnzCz7Pf9j2H44aymCP+/eHGJu4vhU+1O8aKf3TVBgbQr5UM6xoe8IkR/B112XY6YIG2vtg==} + '@algolia/requester-node-http@5.41.0': + resolution: {integrity: sha512-tYv3rGbhBS0eZ5D8oCgV88iuWILROiemk+tQ3YsAKZv2J4kKUNvKkrX/If/SreRy4MGP2uJzMlyKcfSfO2mrsQ==} engines: {node: '>= 14.0.0'} '@alloc/quick-lru@5.2.0': @@ -1004,6 +1049,15 @@ packages: chokidar: optional: true + '@angular-devkit/core@19.2.17': + resolution: {integrity: sha512-Ah008x2RJkd0F+NLKqIpA34/vUGwjlprRCkvddjDopAWRzYn6xCkz1Tqwuhn0nR1Dy47wTLKYD999TYl5ONOAQ==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + chokidar: ^4.0.0 + peerDependenciesMeta: + chokidar: + optional: true + '@angular-devkit/schematics-cli@19.2.15': resolution: {integrity: sha512-1ESFmFGMpGQmalDB3t2EtmWDGv6gOFYBMxmHO2f1KI/UDl8UmZnCGL4mD3EWo8Hv0YIsZ9wOH9Q7ZHNYjeSpzg==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} @@ -1013,6 +1067,10 @@ packages: resolution: {integrity: sha512-kNOJ+3vekJJCQKWihNmxBkarJzNW09kP5a9E1SRNiQVNOUEeSwcRR0qYotM65nx821gNzjjhJXnAZ8OazWldrg==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@angular-devkit/schematics@19.2.17': + resolution: {integrity: sha512-ADfbaBsrG8mBF6Mfs+crKA/2ykB8AJI50Cv9tKmZfwcUcyAdmTr+vVvhsBCfvUAEokigSsgqgpYxfkJVxhJYeg==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@asamuzakjp/css-color@3.2.0': resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} @@ -1029,103 +1087,103 @@ packages: '@aws-crypto/util@5.2.0': resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} - '@aws-sdk/client-sesv2@3.890.0': - resolution: {integrity: sha512-AM9Lt4QkNet8xKgCwJxfkyqlorwG9S+tvtpSfHYCVq0j2Z6PbkDaUBnvwjGOMBV7Um5IzZ7yhvQXrBg7omZciQ==} + '@aws-sdk/client-sesv2@3.919.0': + resolution: {integrity: sha512-3OomeIFXid1iUZybdI2wAkxMgKyKNHzP9lVBtQVNFD0iZLon71XShAwpNyzrp+UTf/vgTqIJShazlcLVpvSXCA==} engines: {node: '>=18.0.0'} - '@aws-sdk/client-sso@3.890.0': - resolution: {integrity: sha512-vefYNwh/K5V5YiJpFJfoMPNqsoiRTqD7ZnkvR0cjJdwhOIwFnSKN1vz0OMjySTQmVMcG4JKGVul82ou7ErtOhQ==} + '@aws-sdk/client-sso@3.919.0': + resolution: {integrity: sha512-9DVw/1DCzZ9G7Jofnhpg/XDC3wdJ3NAJdNWY1TrgE5ZcpTM+UTIQMGyaljCv9rgxggutHBgmBI5lP3YMcPk9ZQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/core@3.890.0': - resolution: {integrity: sha512-CT+yjhytHdyKvV3Nh/fqBjnZ8+UiQZVz4NMm4LrPATgVSOdfygXHqrWxrPTVgiBtuJWkotg06DF7+pTd5ekLBw==} + '@aws-sdk/core@3.916.0': + resolution: {integrity: sha512-1JHE5s6MD5PKGovmx/F1e01hUbds/1y3X8rD+Gvi/gWVfdg5noO7ZCerpRsWgfzgvCMZC9VicopBqNHCKLykZA==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-env@3.890.0': - resolution: {integrity: sha512-BtsUa2y0Rs8phmB2ScZ5RuPqZVmxJJXjGfeiXctmLFTxTwoayIK1DdNzOWx6SRMPVc3s2RBGN4vO7T1TwN+ajA==} + '@aws-sdk/credential-provider-env@3.916.0': + resolution: {integrity: sha512-3gDeqOXcBRXGHScc6xb7358Lyf64NRG2P08g6Bu5mv1Vbg9PKDyCAZvhKLkG7hkdfAM8Yc6UJNhbFxr1ud/tCQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-http@3.890.0': - resolution: {integrity: sha512-0sru3LVwsuGYyzbD90EC/d5HnCZ9PL4O9BA2LYT6b9XceC005Oj86uzE47LXb+mDhTAt3T6ZO0+ZcVQe0DDi8w==} + '@aws-sdk/credential-provider-http@3.916.0': + resolution: {integrity: sha512-NmooA5Z4/kPFJdsyoJgDxuqXC1C6oPMmreJjbOPqcwo6E/h2jxaG8utlQFgXe5F9FeJsMx668dtxVxSYnAAqHQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-ini@3.890.0': - resolution: {integrity: sha512-Mxv7ByftHKH7dE6YXu9gQ6ODXwO1iSO32t8tBrZLS3g8K1knWADIqDFv3yErQtJ8hp27IDxbAbVH/1RQdSkmhA==} + '@aws-sdk/credential-provider-ini@3.919.0': + resolution: {integrity: sha512-fAWVfh0P54UFbyAK4tmIPh/X3COFAyXYSp8b2Pc1R6GRwDDMvrAigwGJuyZS4BmpPlXij1gB0nXbhM5Yo4MMMA==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-node@3.890.0': - resolution: {integrity: sha512-zbPz3mUtaBdch0KoH8/LouRDcYSzyT2ecyCOo5OAFVil7AxT1jvsn4vX78FlnSVpZ4mLuHY8pHTVGi235XiyBA==} + '@aws-sdk/credential-provider-node@3.919.0': + resolution: {integrity: sha512-GL5filyxYS+eZq8ZMQnY5hh79Wxor7Rljo0SUJxZVwEj8cf3zY0MMuwoXU1HQrVabvYtkPDOWSreX8GkIBtBCw==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-process@3.890.0': - resolution: {integrity: sha512-dWZ54TI1Q+UerF5YOqGiCzY+x2YfHsSQvkyM3T4QDNTJpb/zjiVv327VbSOULOlI7gHKWY/G3tMz0D9nWI7YbA==} + '@aws-sdk/credential-provider-process@3.916.0': + resolution: {integrity: sha512-SXDyDvpJ1+WbotZDLJW1lqP6gYGaXfZJrgFSXIuZjHb75fKeNRgPkQX/wZDdUvCwdrscvxmtyJorp2sVYkMcvA==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-sso@3.890.0': - resolution: {integrity: sha512-ajYCZ6f2+98w8zG/IXcQ+NhWYoI5qPUDovw+gMqMWX/jL1cmZ9PFAwj2Vyq9cbjum5RNWwPLArWytTCgJex4AQ==} + '@aws-sdk/credential-provider-sso@3.919.0': + resolution: {integrity: sha512-oN1XG/frOc2K2KdVwRQjLTBLM1oSFJLtOhuV/6g9N0ASD+44uVJai1CF9JJv5GjHGV+wsqAt+/Dzde0tZEXirA==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-web-identity@3.890.0': - resolution: {integrity: sha512-qZ2Mx7BeYR1s0F/H6wePI0MAmkFswmBgrpgMCOt2S4b2IpQPnUa2JbxY3GwW2WqX3nV0KjPW08ctSLMmlq/tKA==} + '@aws-sdk/credential-provider-web-identity@3.919.0': + resolution: {integrity: sha512-Wi7RmyWA8kUJ++/8YceC7U5r4LyvOHGCnJLDHliP8rOC8HLdSgxw/Upeq3WmC+RPw1zyGOtEDRS/caop2xLXEA==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-host-header@3.887.0': - resolution: {integrity: sha512-ulzqXv6NNqdu/kr0sgBYupWmahISHY+azpJidtK6ZwQIC+vBUk9NdZeqQpy7KVhIk2xd4+5Oq9rxapPwPI21CA==} + '@aws-sdk/middleware-host-header@3.914.0': + resolution: {integrity: sha512-7r9ToySQ15+iIgXMF/h616PcQStByylVkCshmQqcdeynD/lCn2l667ynckxW4+ql0Q+Bo/URljuhJRxVJzydNA==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-logger@3.887.0': - resolution: {integrity: sha512-YbbgLI6jKp2qSoAcHnXrQ5jcuc5EYAmGLVFgMVdk8dfCfJLfGGSaOLxF4CXC7QYhO50s+mPPkhBYejCik02Kug==} + '@aws-sdk/middleware-logger@3.914.0': + resolution: {integrity: sha512-/gaW2VENS5vKvJbcE1umV4Ag3NuiVzpsANxtrqISxT3ovyro29o1RezW/Avz/6oJqjnmgz8soe9J1t65jJdiNg==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-recursion-detection@3.887.0': - resolution: {integrity: sha512-tjrUXFtQnFLo+qwMveq5faxP5MQakoLArXtqieHphSqZTXm21wDJM73hgT4/PQQGTwgYjDKqnqsE1hvk0hcfDw==} + '@aws-sdk/middleware-recursion-detection@3.919.0': + resolution: {integrity: sha512-q3MAUxLQve4rTfAannUCx2q1kAHkBBsxt6hVUpzi63KC4lBLScc1ltr7TI+hDxlfGRWGo54jRegb2SsY9Jm+Mw==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-sdk-s3@3.890.0': - resolution: {integrity: sha512-58P1lrE606zpp29xH9Keh3j2BWfa2ciGBtygJTpulRMlqPL3U1gFfU2g5nDYJbjKgRtCgNIBqfmtkL4eikCb9w==} + '@aws-sdk/middleware-sdk-s3@3.916.0': + resolution: {integrity: sha512-pjmzzjkEkpJObzmTthqJPq/P13KoNFuEi/x5PISlzJtHofCNcyXeVAQ90yvY2dQ6UXHf511Rh1/ytiKy2A8M0g==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-user-agent@3.890.0': - resolution: {integrity: sha512-x4+gLrOFGN7PnfxCaQbs3QEF8bMQE4CVxcOp066UEJqr2Pn4yB12Q3O+YntOtESK5NcTxIh7JlhGss95EHzNng==} + '@aws-sdk/middleware-user-agent@3.916.0': + resolution: {integrity: sha512-mzF5AdrpQXc2SOmAoaQeHpDFsK2GE6EGcEACeNuoESluPI2uYMpuuNMYrUufdnIAIyqgKlis0NVxiahA5jG42w==} engines: {node: '>=18.0.0'} - '@aws-sdk/nested-clients@3.890.0': - resolution: {integrity: sha512-D5qVNd+qlqdL8duJShzffAqPllGRA4tG7n/GEpL13eNfHChPvGkkUFBMrxSgCAETaTna13G6kq+dMO+SAdbm1A==} + '@aws-sdk/nested-clients@3.919.0': + resolution: {integrity: sha512-5D9OQsMPkbkp4KHM7JZv/RcGCpr3E1L7XX7U9sCxY+sFGeysltoviTmaIBXsJ2IjAJbBULtf0G/J+2cfH5OP+w==} engines: {node: '>=18.0.0'} - '@aws-sdk/region-config-resolver@3.890.0': - resolution: {integrity: sha512-VfdT+tkF9groRYNzKvQCsCGDbOQdeBdzyB1d6hWiq22u13UafMIoskJ1ec0i0H1X29oT6mjTitfnvPq1UiKwzQ==} + '@aws-sdk/region-config-resolver@3.914.0': + resolution: {integrity: sha512-KlmHhRbn1qdwXUdsdrJ7S/MAkkC1jLpQ11n+XvxUUUCGAJd1gjC7AjxPZUM7ieQ2zcb8bfEzIU7al+Q3ZT0u7Q==} engines: {node: '>=18.0.0'} - '@aws-sdk/signature-v4-multi-region@3.890.0': - resolution: {integrity: sha512-il8kb2/wDLXhemN3p7v4MvbvqoMuo7Ug3ihuIUIhPtSVjcnn+BISJU0S+5YTl8TXf6qxML9VrfxL0pmuhO3BsA==} + '@aws-sdk/signature-v4-multi-region@3.916.0': + resolution: {integrity: sha512-fuzUMo6xU7e0NBzBA6TQ4FUf1gqNbg4woBSvYfxRRsIfKmSMn9/elXXn4sAE5UKvlwVQmYnb6p7dpVRPyFvnQA==} engines: {node: '>=18.0.0'} - '@aws-sdk/token-providers@3.890.0': - resolution: {integrity: sha512-+pK/0iQEpPmnztbAw0NNmb+B5pPy8VLu+Ab4SJLgVp41RE9NO13VQtrzUbh61TTAVMrzqWlLQ2qmAl2Fk4VNgw==} + '@aws-sdk/token-providers@3.919.0': + resolution: {integrity: sha512-6aFv4lzXbfbkl0Pv37Us8S/ZkqplOQZIEgQg7bfMru7P96Wv2jVnDGsEc5YyxMnnRyIB90naQ5JgslZ4rkpknw==} engines: {node: '>=18.0.0'} - '@aws-sdk/types@3.887.0': - resolution: {integrity: sha512-fmTEJpUhsPsovQ12vZSpVTEP/IaRoJAMBGQXlQNjtCpkBp6Iq3KQDa/HDaPINE+3xxo6XvTdtibsNOd5zJLV9A==} + '@aws-sdk/types@3.914.0': + resolution: {integrity: sha512-kQWPsRDmom4yvAfyG6L1lMmlwnTzm1XwMHOU+G5IFlsP4YEaMtXidDzW/wiivY0QFrhfCz/4TVmu0a2aPU57ug==} engines: {node: '>=18.0.0'} - '@aws-sdk/util-arn-parser@3.873.0': - resolution: {integrity: sha512-qag+VTqnJWDn8zTAXX4wiVioa0hZDQMtbZcGRERVnLar4/3/VIKBhxX2XibNQXFu1ufgcRn4YntT/XEPecFWcg==} + '@aws-sdk/util-arn-parser@3.893.0': + resolution: {integrity: sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA==} engines: {node: '>=18.0.0'} - '@aws-sdk/util-endpoints@3.890.0': - resolution: {integrity: sha512-nJ8v1x9ZQKzMRK4dS4oefOMIHqb6cguctTcx1RB9iTaFOR5pP7bvq+D4mvNZ6vBxiHg1dQGBUUgl5XJmdR7atQ==} + '@aws-sdk/util-endpoints@3.916.0': + resolution: {integrity: sha512-bAgUQwvixdsiGNcuZSDAOWbyHlnPtg8G8TyHD6DTfTmKTHUW6tAn+af/ZYJPXEzXhhpwgJqi58vWnsiDhmr7NQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/util-locate-window@3.873.0': - resolution: {integrity: sha512-xcVhZF6svjM5Rj89T1WzkjQmrTF6dpR2UvIHPMTnSZoNe6CixejPZ6f0JJ2kAhO8H+dUHwNBlsUgOTIKiK/Syg==} + '@aws-sdk/util-locate-window@3.893.0': + resolution: {integrity: sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==} engines: {node: '>=18.0.0'} - '@aws-sdk/util-user-agent-browser@3.887.0': - resolution: {integrity: sha512-X71UmVsYc6ZTH4KU6hA5urOzYowSXc3qvroagJNLJYU1ilgZ529lP4J9XOYfEvTXkLR1hPFSRxa43SrwgelMjA==} + '@aws-sdk/util-user-agent-browser@3.914.0': + resolution: {integrity: sha512-rMQUrM1ECH4kmIwlGl9UB0BtbHy6ZuKdWFrIknu8yGTRI/saAucqNTh5EI1vWBxZ0ElhK5+g7zOnUuhSmVQYUA==} - '@aws-sdk/util-user-agent-node@3.890.0': - resolution: {integrity: sha512-s85NkCxKoAlUvx7UP7OelxLqwTi27Tps9/Q+4N+9rEUjThxEnDsqJSStJ1XiYhddz1xc/vxMvPjYN0qX6EKPtA==} + '@aws-sdk/util-user-agent-node@3.916.0': + resolution: {integrity: sha512-CwfWV2ch6UdjuSV75ZU99N03seEUb31FIUrXBnwa6oONqj/xqXwrxtlUMLx6WH3OJEE4zI3zt5PjlTdGcVwf4g==} engines: {node: '>=18.0.0'} peerDependencies: aws-crt: '>=1.0.0' @@ -1133,28 +1191,28 @@ packages: aws-crt: optional: true - '@aws-sdk/xml-builder@3.887.0': - resolution: {integrity: sha512-lMwgWK1kNgUhHGfBvO/5uLe7TKhycwOn3eRCqsKPT9aPCx/HWuTlpcQp8oW2pCRGLS7qzcxqpQulcD+bbUL7XQ==} + '@aws-sdk/xml-builder@3.914.0': + resolution: {integrity: sha512-k75evsBD5TcIjedycYS7QXQ98AmOtbnxRJOPtCo0IwYRmy7UvqgS/gBL5SmrIqeV6FDSYRQMgdBxSMp6MLmdew==} engines: {node: '>=18.0.0'} - '@aws/lambda-invoke-store@0.0.1': - resolution: {integrity: sha512-ORHRQ2tmvnBXc8t/X9Z8IcSbBA4xTLKuN873FopzklHMeqBst7YG0d+AX97inkvDX+NChYtSr+qGfcqGFaI8Zw==} + '@aws/lambda-invoke-store@0.1.1': + resolution: {integrity: sha512-RcLam17LdlbSOSp9VxmUu1eI6Mwxp+OwhD2QhiSNmNCzoDb0EeUXTD2n/WbcnrAYMGlmf05th6QYq23VqvJqpA==} engines: {node: '>=18.0.0'} '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.27.7': - resolution: {integrity: sha512-xgu/ySj2mTiUFmdE9yCMfBxLp4DHd5DwmbbD05YAuICfodYT3VvRxbrh81LGQ/8UpSdtMdfKMn3KouYDX59DGQ==} + '@babel/compat-data@7.28.5': + resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} engines: {node: '>=6.9.0'} - '@babel/core@7.27.7': - resolution: {integrity: sha512-BU2f9tlKQ5CAthiMIgpzAh4eDTLWo1mqi9jqE2OxMG0E/OM199VJt2q8BztTxpnSW0i1ymdwLXRJnYzvDM5r2w==} + '@babel/core@7.28.5': + resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} engines: {node: '>=6.9.0'} - '@babel/generator@7.28.3': - resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} + '@babel/generator@7.28.5': + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} engines: {node: '>=6.9.0'} '@babel/helper-annotate-as-pure@7.27.3': @@ -1165,14 +1223,14 @@ packages: resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@7.27.1': - resolution: {integrity: sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==} + '@babel/helper-create-class-features-plugin@7.28.5': + resolution: {integrity: sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-create-regexp-features-plugin@7.27.1': - resolution: {integrity: sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==} + '@babel/helper-create-regexp-features-plugin@7.28.5': + resolution: {integrity: sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1186,16 +1244,16 @@ packages: resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} - '@babel/helper-member-expression-to-functions@7.27.1': - resolution: {integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==} + '@babel/helper-member-expression-to-functions@7.28.5': + resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} engines: {node: '>=6.9.0'} '@babel/helper-module-imports@7.27.1': resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.27.3': - resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==} + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1228,29 +1286,29 @@ packages: resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.27.1': - resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} '@babel/helper-validator-option@7.27.1': resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helper-wrap-function@7.27.1': - resolution: {integrity: sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==} + '@babel/helper-wrap-function@7.28.3': + resolution: {integrity: sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.27.6': - resolution: {integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==} + '@babel/helpers@7.28.4': + resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} engines: {node: '>=6.9.0'} - '@babel/parser@7.28.4': - resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1': - resolution: {integrity: sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==} + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5': + resolution: {integrity: sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1273,8 +1331,8 @@ packages: peerDependencies: '@babel/core': ^7.13.0 - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.27.1': - resolution: {integrity: sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==} + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.3': + resolution: {integrity: sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1326,8 +1384,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-async-generator-functions@7.27.1': - resolution: {integrity: sha512-eST9RrwlpaoJBDHShc+DS2SG4ATTi2MYNb4OxYkf3n+7eb49LWpnS+HSpVfW4x927qQwgk8A2hGNVaajAEw0EA==} + '@babel/plugin-transform-async-generator-functions@7.28.0': + resolution: {integrity: sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1344,8 +1402,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-block-scoping@7.27.5': - resolution: {integrity: sha512-JF6uE2s67f0y2RZcm2kpAUEbD50vH62TyWVebxwHAlbSdM49VqPz8t4a1uIjp4NIOIZ4xzLfjY5emt/RCyC7TQ==} + '@babel/plugin-transform-block-scoping@7.28.5': + resolution: {integrity: sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1356,14 +1414,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-class-static-block@7.27.1': - resolution: {integrity: sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==} + '@babel/plugin-transform-class-static-block@7.28.3': + resolution: {integrity: sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.12.0 - '@babel/plugin-transform-classes@7.27.7': - resolution: {integrity: sha512-CuLkokN1PEZ0Fsjtq+001aog/C2drDK9nTfK/NRK0n6rBin6cBrvM+zfQjDE+UllhR6/J4a6w8Xq9i4yi3mQrw==} + '@babel/plugin-transform-classes@7.28.4': + resolution: {integrity: sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1374,8 +1432,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-destructuring@7.27.7': - resolution: {integrity: sha512-pg3ZLdIKWCP0CrJm0O4jYjVthyBeioVfvz9nwt6o5paUxsgJ/8GucSMAIaj6M7xA4WY+SrvtGu2LijzkdyecWQ==} + '@babel/plugin-transform-destructuring@7.28.5': + resolution: {integrity: sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1404,8 +1462,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-exponentiation-operator@7.27.1': - resolution: {integrity: sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==} + '@babel/plugin-transform-explicit-resource-management@7.28.0': + resolution: {integrity: sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-exponentiation-operator@7.28.5': + resolution: {integrity: sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1440,8 +1504,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-logical-assignment-operators@7.27.1': - resolution: {integrity: sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==} + '@babel/plugin-transform-logical-assignment-operators@7.28.5': + resolution: {integrity: sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1464,8 +1528,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-systemjs@7.27.1': - resolution: {integrity: sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==} + '@babel/plugin-transform-modules-systemjs@7.28.5': + resolution: {integrity: sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1500,8 +1564,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-object-rest-spread@7.27.7': - resolution: {integrity: sha512-201B1kFTWhckclcXpWHc8uUpYziDX/Pl4rxl0ZX0DiCZ3jknwfSUALL3QCYeeXXB37yWxJbo+g+Vfq8pAaHi3w==} + '@babel/plugin-transform-object-rest-spread@7.28.4': + resolution: {integrity: sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1518,8 +1582,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-optional-chaining@7.27.1': - resolution: {integrity: sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==} + '@babel/plugin-transform-optional-chaining@7.28.5': + resolution: {integrity: sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1554,8 +1618,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-display-name@7.27.1': - resolution: {integrity: sha512-p9+Vl3yuHPmkirRrg021XiP+EETmPMQTLr6Ayjj85RLNEbb3Eya/4VI0vAdzQG9SEAl2Lnt7fy5lZyMzjYoZQQ==} + '@babel/plugin-transform-react-display-name@7.28.0': + resolution: {integrity: sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1578,8 +1642,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-regenerator@7.27.5': - resolution: {integrity: sha512-uhB8yHerfe3MWnuLAhEbeQ4afVoqv8BQsPqrTv7e/jZ9y00kJL6l9a/f4OWaKxotmjzewfEyXE1vgDJenkQ2/Q==} + '@babel/plugin-transform-regenerator@7.28.4': + resolution: {integrity: sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1596,8 +1660,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-runtime@7.27.4': - resolution: {integrity: sha512-D68nR5zxU64EUzV8i7T3R5XP0Xhrou/amNnddsRQssx6GrTLdZl1rLxyjtVZBd+v/NVX4AbTPOB5aU8thAZV1A==} + '@babel/plugin-transform-runtime@7.28.5': + resolution: {integrity: sha512-20NUVgOrinudkIBzQ2bNxP08YpKprUkRTiRSd2/Z5GOdPImJGkoN4Z7IQe1T5AdyKI1i5L6RBmluqdSzvaq9/w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1632,8 +1696,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-typescript@7.27.1': - resolution: {integrity: sha512-Q5sT5+O4QUebHdbwKedFBEwRLb02zJ7r4A5Gg2hUoLuU3FjdMcyqcywqUrLCaDsFCxzokf7u9kuy7qz51YUuAg==} + '@babel/plugin-transform-typescript@7.28.5': + resolution: {integrity: sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1662,8 +1726,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/preset-env@7.27.2': - resolution: {integrity: sha512-Ma4zSuYSlGNRlCLO+EAzLnCmJK2vdstgv+n7aUP+/IKZrOfWHOJVdSJtuub8RzHTj3ahD37k5OKJWvzf16TQyQ==} + '@babel/preset-env@7.28.5': + resolution: {integrity: sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1673,20 +1737,20 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 - '@babel/preset-react@7.27.1': - resolution: {integrity: sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA==} + '@babel/preset-react@7.28.5': + resolution: {integrity: sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/preset-typescript@7.27.1': - resolution: {integrity: sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==} + '@babel/preset-typescript@7.28.5': + resolution: {integrity: sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/runtime-corejs3@7.27.6': - resolution: {integrity: sha512-vDVrlmRAY8z9Ul/HxT+8ceAru95LQgkSKiXkSYZvqtbkPSfhZJgpRp45Cldbh1GJ1kxzQkI70AqyrTI58KpaWQ==} + '@babel/runtime-corejs3@7.28.4': + resolution: {integrity: sha512-h7iEYiW4HebClDEhtvFObtPmIvrd1SSfpI9EhOeKk4CtIK/ngBWFpuhCzhdmRKtg71ylcue+9I6dv54XYO1epQ==} engines: {node: '>=6.9.0'} '@babel/runtime@7.28.4': @@ -1697,12 +1761,12 @@ packages: resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.4': - resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==} + '@babel/traverse@7.28.5': + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.4': - resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} '@balena/dockerignore@1.0.2': @@ -1761,32 +1825,50 @@ packages: '@csstools/css-parser-algorithms': ^3.0.5 '@csstools/css-tokenizer': ^3.0.4 + '@csstools/postcss-alpha-function@1.0.1': + resolution: {integrity: sha512-isfLLwksH3yHkFXfCI2Gcaqg7wGGHZZwunoJzEZk0yKYIokgre6hYVFibKL3SYAoR1kBXova8LB+JoO5vZzi9w==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + '@csstools/postcss-cascade-layers@5.0.2': resolution: {integrity: sha512-nWBE08nhO8uWl6kSAeCx4im7QfVko3zLrtgWZY4/bP87zrSPpSyN/3W3TDqz1jJuH+kbKOHXg5rJnK+ZVYcFFg==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 - '@csstools/postcss-color-function@4.0.10': - resolution: {integrity: sha512-4dY0NBu7NVIpzxZRgh/Q/0GPSz/jLSw0i/u3LTUor0BkQcz/fNhN10mSWBDsL0p9nDb0Ky1PD6/dcGbhACuFTQ==} + '@csstools/postcss-color-function-display-p3-linear@1.0.1': + resolution: {integrity: sha512-E5qusdzhlmO1TztYzDIi8XPdPoYOjoTY6HBYBCYSj+Gn4gQRBlvjgPQXzfzuPQqt8EhkC/SzPKObg4Mbn8/xMg==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 - '@csstools/postcss-color-mix-function@3.0.10': - resolution: {integrity: sha512-P0lIbQW9I4ShE7uBgZRib/lMTf9XMjJkFl/d6w4EMNHu2qvQ6zljJGEcBkw/NsBtq/6q3WrmgxSS8kHtPMkK4Q==} + '@csstools/postcss-color-function@4.0.12': + resolution: {integrity: sha512-yx3cljQKRaSBc2hfh8rMZFZzChaFgwmO2JfFgFr1vMcF3C/uyy5I4RFIBOIWGq1D+XbKCG789CGkG6zzkLpagA==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 - '@csstools/postcss-color-mix-variadic-function-arguments@1.0.0': - resolution: {integrity: sha512-Z5WhouTyD74dPFPrVE7KydgNS9VvnjB8qcdes9ARpCOItb4jTnm7cHp4FhxCRUoyhabD0WVv43wbkJ4p8hLAlQ==} + '@csstools/postcss-color-mix-function@3.0.12': + resolution: {integrity: sha512-4STERZfCP5Jcs13P1U5pTvI9SkgLgfMUMhdXW8IlJWkzOOOqhZIjcNhWtNJZes2nkBDsIKJ0CJtFtuaZ00moag==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 - '@csstools/postcss-content-alt-text@2.0.6': - resolution: {integrity: sha512-eRjLbOjblXq+byyaedQRSrAejKGNAFued+LcbzT+LCL78fabxHkxYjBbxkroONxHHYu2qxhFK2dBStTLPG3jpQ==} + '@csstools/postcss-color-mix-variadic-function-arguments@1.0.2': + resolution: {integrity: sha512-rM67Gp9lRAkTo+X31DUqMEq+iK+EFqsidfecmhrteErxJZb6tUoJBVQca1Vn1GpDql1s1rD1pKcuYzMsg7Z1KQ==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-content-alt-text@2.0.8': + resolution: {integrity: sha512-9SfEW9QCxEpTlNMnpSqFaHyzsiRpZ5J5+KqCu1u5/eEJAWsMhzT40qf0FIbeeglEvrGRMdDzAxMIz3wqoGSb+Q==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-contrast-color-function@2.0.12': + resolution: {integrity: sha512-YbwWckjK3qwKjeYz/CijgcS7WDUCtKTd8ShLztm3/i5dhh4NaqzsbYnhm4bjrpFpnLZ31jVcbK8YL77z3GBPzA==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -1803,26 +1885,26 @@ packages: peerDependencies: postcss: ^8.4 - '@csstools/postcss-gamut-mapping@2.0.10': - resolution: {integrity: sha512-QDGqhJlvFnDlaPAfCYPsnwVA6ze+8hhrwevYWlnUeSjkkZfBpcCO42SaUD8jiLlq7niouyLgvup5lh+f1qessg==} + '@csstools/postcss-gamut-mapping@2.0.11': + resolution: {integrity: sha512-fCpCUgZNE2piVJKC76zFsgVW1apF6dpYsqGyH8SIeCcM4pTEsRTWTLCaJIMKFEundsCKwY1rwfhtrio04RJ4Dw==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 - '@csstools/postcss-gradients-interpolation-method@5.0.10': - resolution: {integrity: sha512-HHPauB2k7Oits02tKFUeVFEU2ox/H3OQVrP3fSOKDxvloOikSal+3dzlyTZmYsb9FlY9p5EUpBtz0//XBmy+aw==} + '@csstools/postcss-gradients-interpolation-method@5.0.12': + resolution: {integrity: sha512-jugzjwkUY0wtNrZlFeyXzimUL3hN4xMvoPnIXxoZqxDvjZRiSh+itgHcVUWzJ2VwD/VAMEgCLvtaJHX+4Vj3Ow==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 - '@csstools/postcss-hwb-function@4.0.10': - resolution: {integrity: sha512-nOKKfp14SWcdEQ++S9/4TgRKchooLZL0TUFdun3nI4KPwCjETmhjta1QT4ICQcGVWQTvrsgMM/aLB5We+kMHhQ==} + '@csstools/postcss-hwb-function@4.0.12': + resolution: {integrity: sha512-mL/+88Z53KrE4JdePYFJAQWFrcADEqsLprExCM04GDNgHIztwFzj0Mbhd/yxMBngq0NIlz58VVxjt5abNs1VhA==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 - '@csstools/postcss-ic-unit@4.0.2': - resolution: {integrity: sha512-lrK2jjyZwh7DbxaNnIUjkeDmU8Y6KyzRBk91ZkI5h8nb1ykEfZrtIVArdIjX4DHMIBGpdHrgP0n4qXDr7OHaKA==} + '@csstools/postcss-ic-unit@4.0.4': + resolution: {integrity: sha512-yQ4VmossuOAql65sCPppVO1yfb7hDscf4GseF0VCA/DTDaBc0Wtf8MTqVPfjGYlT5+2buokG0Gp7y0atYZpwjg==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -1839,8 +1921,8 @@ packages: peerDependencies: postcss: ^8.4 - '@csstools/postcss-light-dark-function@2.0.9': - resolution: {integrity: sha512-1tCZH5bla0EAkFAI2r0H33CDnIBeLUaJh1p+hvvsylJ4svsv2wOmJjJn+OXwUZLXef37GYbRIVKX+X+g6m+3CQ==} + '@csstools/postcss-light-dark-function@2.0.11': + resolution: {integrity: sha512-fNJcKXJdPM3Lyrbmgw2OBbaioU7yuKZtiXClf4sGdQttitijYlZMD5K7HrC/eF83VRWRrYq6OZ0Lx92leV2LFA==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -1899,14 +1981,14 @@ packages: peerDependencies: postcss: ^8.4 - '@csstools/postcss-oklab-function@4.0.10': - resolution: {integrity: sha512-ZzZUTDd0fgNdhv8UUjGCtObPD8LYxMH+MJsW9xlZaWTV8Ppr4PtxlHYNMmF4vVWGl0T6f8tyWAKjoI6vePSgAg==} + '@csstools/postcss-oklab-function@4.0.12': + resolution: {integrity: sha512-HhlSmnE1NKBhXsTnNGjxvhryKtO7tJd1w42DKOGFD6jSHtYOrsJTQDKPMwvOfrzUAk8t7GcpIfRyM7ssqHpFjg==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 - '@csstools/postcss-progressive-custom-properties@4.1.0': - resolution: {integrity: sha512-YrkI9dx8U4R8Sz2EJaoeD9fI7s7kmeEBfmO+UURNeL6lQI7VxF6sBE+rSqdCBn4onwqmxFdBU3lTwyYb/lCmxA==} + '@csstools/postcss-progressive-custom-properties@4.2.1': + resolution: {integrity: sha512-uPiiXf7IEKtUQXsxu6uWtOlRMXd2QWWy5fhxHDnPdXKCQckPP3E34ZgDoZ62r2iT+UOgWsSbM4NvHE5m3mAEdw==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -1917,8 +1999,8 @@ packages: peerDependencies: postcss: ^8.4 - '@csstools/postcss-relative-color-syntax@3.0.10': - resolution: {integrity: sha512-8+0kQbQGg9yYG8hv0dtEpOMLwB9M+P7PhacgIzVzJpixxV4Eq9AUQtQw8adMmAJU1RBBmIlpmtmm3XTRd/T00g==} + '@csstools/postcss-relative-color-syntax@3.0.12': + resolution: {integrity: sha512-0RLIeONxu/mtxRtf3o41Lq2ghLimw0w9ByLWnnEVuy89exmEEq8bynveBxNW3nyHqLAFEeNtVEmC1QK9MZ8Huw==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -1941,8 +2023,8 @@ packages: peerDependencies: postcss: ^8.4 - '@csstools/postcss-text-decoration-shorthand@4.0.2': - resolution: {integrity: sha512-8XvCRrFNseBSAGxeaVTaNijAu+FzUvjwFXtcrynmazGb/9WUdsPCpBX+mHEHShVRq47Gy4peYAoxYs8ltUnmzA==} + '@csstools/postcss-text-decoration-shorthand@4.0.3': + resolution: {integrity: sha512-KSkGgZfx0kQjRIYnpsD7X2Om9BUXX/Kii77VBifQW9Ih929hK0KNjVngHDH0bFB9GmfWcR9vJYJJRvw/NQjkrA==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -1981,11 +2063,11 @@ packages: resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} engines: {node: '>=10.0.0'} - '@docsearch/css@3.9.0': - resolution: {integrity: sha512-cQbnVbq0rrBwNAKegIac/t6a8nWoUAn8frnkLFW6YARaRmAQr5/Eoe6Ln2fqkUCZ40KpdrKbpSAmgrkviOxuWA==} + '@docsearch/css@4.2.0': + resolution: {integrity: sha512-65KU9Fw5fGsPPPlgIghonMcndyx1bszzrDQYLfierN+Ha29yotMHzVS94bPkZS6On9LS8dE4qmW4P/fGjtCf/g==} - '@docsearch/react@3.9.0': - resolution: {integrity: sha512-mb5FOZYZIkRQ6s/NWnM98k879vu5pscWqTLubLFBO87igYYT4VzVazh4h5o/zCvTIZgEt3PvsCOMOswOUo9yHQ==} + '@docsearch/react@4.2.0': + resolution: {integrity: sha512-zSN/KblmtBcerf7Z87yuKIHZQmxuXvYc6/m0+qnjyNu+Ir67AVOagTa1zBqcxkVUVkmBqUExdcyrdo9hbGbqTw==} peerDependencies: '@types/react': '>= 16.8.0 < 20.0.0' react: '>= 16.8.0 < 20.0.0' @@ -2001,120 +2083,120 @@ packages: search-insights: optional: true - '@docusaurus/babel@3.8.1': - resolution: {integrity: sha512-3brkJrml8vUbn9aeoZUlJfsI/GqyFcDgQJwQkmBtclJgWDEQBKKeagZfOgx0WfUQhagL1sQLNW0iBdxnI863Uw==} - engines: {node: '>=18.0'} + '@docusaurus/babel@3.9.2': + resolution: {integrity: sha512-GEANdi/SgER+L7Japs25YiGil/AUDnFFHaCGPBbundxoWtCkA2lmy7/tFmgED4y1htAy6Oi4wkJEQdGssnw9MA==} + engines: {node: '>=20.0'} - '@docusaurus/bundler@3.8.1': - resolution: {integrity: sha512-/z4V0FRoQ0GuSLToNjOSGsk6m2lQUG4FRn8goOVoZSRsTrU8YR2aJacX5K3RG18EaX9b+52pN4m1sL3MQZVsQA==} - engines: {node: '>=18.0'} + '@docusaurus/bundler@3.9.2': + resolution: {integrity: sha512-ZOVi6GYgTcsZcUzjblpzk3wH1Fya2VNpd5jtHoCCFcJlMQ1EYXZetfAnRHLcyiFeBABaI1ltTYbOBtH/gahGVA==} + engines: {node: '>=20.0'} peerDependencies: '@docusaurus/faster': '*' peerDependenciesMeta: '@docusaurus/faster': optional: true - '@docusaurus/core@3.8.1': - resolution: {integrity: sha512-ENB01IyQSqI2FLtOzqSI3qxG2B/jP4gQPahl2C3XReiLebcVh5B5cB9KYFvdoOqOWPyr5gXK4sjgTKv7peXCrA==} - engines: {node: '>=18.0'} + '@docusaurus/core@3.9.2': + resolution: {integrity: sha512-HbjwKeC+pHUFBfLMNzuSjqFE/58+rLVKmOU3lxQrpsxLBOGosYco/Q0GduBb0/jEMRiyEqjNT/01rRdOMWq5pw==} + engines: {node: '>=20.0'} hasBin: true peerDependencies: '@mdx-js/react': ^3.0.0 react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/cssnano-preset@3.8.1': - resolution: {integrity: sha512-G7WyR2N6SpyUotqhGznERBK+x84uyhfMQM2MmDLs88bw4Flom6TY46HzkRkSEzaP9j80MbTN8naiL1fR17WQug==} - engines: {node: '>=18.0'} + '@docusaurus/cssnano-preset@3.9.2': + resolution: {integrity: sha512-8gBKup94aGttRduABsj7bpPFTX7kbwu+xh3K9NMCF5K4bWBqTFYW+REKHF6iBVDHRJ4grZdIPbvkiHd/XNKRMQ==} + engines: {node: '>=20.0'} - '@docusaurus/logger@3.8.1': - resolution: {integrity: sha512-2wjeGDhKcExEmjX8k1N/MRDiPKXGF2Pg+df/bDDPnnJWHXnVEZxXj80d6jcxp1Gpnksl0hF8t/ZQw9elqj2+ww==} - engines: {node: '>=18.0'} + '@docusaurus/logger@3.9.2': + resolution: {integrity: sha512-/SVCc57ByARzGSU60c50rMyQlBuMIJCjcsJlkphxY6B0GV4UH3tcA1994N8fFfbJ9kX3jIBe/xg3XP5qBtGDbA==} + engines: {node: '>=20.0'} - '@docusaurus/mdx-loader@3.8.1': - resolution: {integrity: sha512-DZRhagSFRcEq1cUtBMo4TKxSNo/W6/s44yhr8X+eoXqCLycFQUylebOMPseHi5tc4fkGJqwqpWJLz6JStU9L4w==} - engines: {node: '>=18.0'} + '@docusaurus/mdx-loader@3.9.2': + resolution: {integrity: sha512-wiYoGwF9gdd6rev62xDU8AAM8JuLI/hlwOtCzMmYcspEkzecKrP8J8X+KpYnTlACBUUtXNJpSoCwFWJhLRevzQ==} + engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/module-type-aliases@3.8.1': - resolution: {integrity: sha512-6xhvAJiXzsaq3JdosS7wbRt/PwEPWHr9eM4YNYqVlbgG1hSK3uQDXTVvQktasp3VO6BmfYWPozueLWuj4gB+vg==} + '@docusaurus/module-type-aliases@3.9.2': + resolution: {integrity: sha512-8qVe2QA9hVLzvnxP46ysuofJUIc/yYQ82tvA/rBTrnpXtCjNSFLxEZfd5U8cYZuJIVlkPxamsIgwd5tGZXfvew==} peerDependencies: react: '*' react-dom: '*' - '@docusaurus/plugin-content-blog@3.8.1': - resolution: {integrity: sha512-vNTpMmlvNP9n3hGEcgPaXyvTljanAKIUkuG9URQ1DeuDup0OR7Ltvoc8yrmH+iMZJbcQGhUJF+WjHLwuk8HSdw==} - engines: {node: '>=18.0'} + '@docusaurus/plugin-content-blog@3.9.2': + resolution: {integrity: sha512-3I2HXy3L1QcjLJLGAoTvoBnpOwa6DPUa3Q0dMK19UTY9mhPkKQg/DYhAGTiBUKcTR0f08iw7kLPqOhIgdV3eVQ==} + engines: {node: '>=20.0'} peerDependencies: '@docusaurus/plugin-content-docs': '*' react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-content-docs@3.8.1': - resolution: {integrity: sha512-oByRkSZzeGNQByCMaX+kif5Nl2vmtj2IHQI2fWjCfCootsdKZDPFLonhIp5s3IGJO7PLUfe0POyw0Xh/RrGXJA==} - engines: {node: '>=18.0'} + '@docusaurus/plugin-content-docs@3.9.2': + resolution: {integrity: sha512-C5wZsGuKTY8jEYsqdxhhFOe1ZDjH0uIYJ9T/jebHwkyxqnr4wW0jTkB72OMqNjsoQRcb0JN3PcSeTwFlVgzCZg==} + engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-content-pages@3.8.1': - resolution: {integrity: sha512-a+V6MS2cIu37E/m7nDJn3dcxpvXb6TvgdNI22vJX8iUTp8eoMoPa0VArEbWvCxMY/xdC26WzNv4wZ6y0iIni/w==} - engines: {node: '>=18.0'} + '@docusaurus/plugin-content-pages@3.9.2': + resolution: {integrity: sha512-s4849w/p4noXUrGpPUF0BPqIAfdAe76BLaRGAGKZ1gTDNiGxGcpsLcwJ9OTi1/V8A+AzvsmI9pkjie2zjIQZKA==} + engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-css-cascade-layers@3.8.1': - resolution: {integrity: sha512-VQ47xRxfNKjHS5ItzaVXpxeTm7/wJLFMOPo1BkmoMG4Cuz4nuI+Hs62+RMk1OqVog68Swz66xVPK8g9XTrBKRw==} - engines: {node: '>=18.0'} + '@docusaurus/plugin-css-cascade-layers@3.9.2': + resolution: {integrity: sha512-w1s3+Ss+eOQbscGM4cfIFBlVg/QKxyYgj26k5AnakuHkKxH6004ZtuLe5awMBotIYF2bbGDoDhpgQ4r/kcj4rQ==} + engines: {node: '>=20.0'} - '@docusaurus/plugin-debug@3.8.1': - resolution: {integrity: sha512-nT3lN7TV5bi5hKMB7FK8gCffFTBSsBsAfV84/v293qAmnHOyg1nr9okEw8AiwcO3bl9vije5nsUvP0aRl2lpaw==} - engines: {node: '>=18.0'} + '@docusaurus/plugin-debug@3.9.2': + resolution: {integrity: sha512-j7a5hWuAFxyQAkilZwhsQ/b3T7FfHZ+0dub6j/GxKNFJp2h9qk/P1Bp7vrGASnvA9KNQBBL1ZXTe7jlh4VdPdA==} + engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-google-analytics@3.8.1': - resolution: {integrity: sha512-Hrb/PurOJsmwHAsfMDH6oVpahkEGsx7F8CWMjyP/dw1qjqmdS9rcV1nYCGlM8nOtD3Wk/eaThzUB5TSZsGz+7Q==} - engines: {node: '>=18.0'} + '@docusaurus/plugin-google-analytics@3.9.2': + resolution: {integrity: sha512-mAwwQJ1Us9jL/lVjXtErXto4p4/iaLlweC54yDUK1a97WfkC6Z2k5/769JsFgwOwOP+n5mUQGACXOEQ0XDuVUw==} + engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-google-gtag@3.8.1': - resolution: {integrity: sha512-tKE8j1cEZCh8KZa4aa80zpSTxsC2/ZYqjx6AAfd8uA8VHZVw79+7OTEP2PoWi0uL5/1Is0LF5Vwxd+1fz5HlKg==} - engines: {node: '>=18.0'} + '@docusaurus/plugin-google-gtag@3.9.2': + resolution: {integrity: sha512-YJ4lDCphabBtw19ooSlc1MnxtYGpjFV9rEdzjLsUnBCeis2djUyCozZaFhCg6NGEwOn7HDDyMh0yzcdRpnuIvA==} + engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-google-tag-manager@3.8.1': - resolution: {integrity: sha512-iqe3XKITBquZq+6UAXdb1vI0fPY5iIOitVjPQ581R1ZKpHr0qe+V6gVOrrcOHixPDD/BUKdYwkxFjpNiEN+vBw==} - engines: {node: '>=18.0'} + '@docusaurus/plugin-google-tag-manager@3.9.2': + resolution: {integrity: sha512-LJtIrkZN/tuHD8NqDAW1Tnw0ekOwRTfobWPsdO15YxcicBo2ykKF0/D6n0vVBfd3srwr9Z6rzrIWYrMzBGrvNw==} + engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-sitemap@3.8.1': - resolution: {integrity: sha512-+9YV/7VLbGTq8qNkjiugIelmfUEVkTyLe6X8bWq7K5qPvGXAjno27QAfFq63mYfFFbJc7z+pudL63acprbqGzw==} - engines: {node: '>=18.0'} + '@docusaurus/plugin-sitemap@3.9.2': + resolution: {integrity: sha512-WLh7ymgDXjG8oPoM/T4/zUP7KcSuFYRZAUTl8vR6VzYkfc18GBM4xLhcT+AKOwun6kBivYKUJf+vlqYJkm+RHw==} + engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-svgr@3.8.1': - resolution: {integrity: sha512-rW0LWMDsdlsgowVwqiMb/7tANDodpy1wWPwCcamvhY7OECReN3feoFwLjd/U4tKjNY3encj0AJSTxJA+Fpe+Gw==} - engines: {node: '>=18.0'} + '@docusaurus/plugin-svgr@3.9.2': + resolution: {integrity: sha512-n+1DE+5b3Lnf27TgVU5jM1d4x5tUh2oW5LTsBxJX4PsAPV0JGcmI6p3yLYtEY0LRVEIJh+8RsdQmRE66wSV8mw==} + engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/preset-classic@3.8.1': - resolution: {integrity: sha512-yJSjYNHXD8POMGc2mKQuj3ApPrN+eG0rO1UPgSx7jySpYU+n4WjBikbrA2ue5ad9A7aouEtMWUoiSRXTH/g7KQ==} - engines: {node: '>=18.0'} + '@docusaurus/preset-classic@3.9.2': + resolution: {integrity: sha512-IgyYO2Gvaigi21LuDIe+nvmN/dfGXAiMcV/murFqcpjnZc7jxFAxW+9LEjdPt61uZLxG4ByW/oUmX/DDK9t/8w==} + engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 @@ -2124,52 +2206,52 @@ packages: peerDependencies: react: '*' - '@docusaurus/theme-classic@3.8.1': - resolution: {integrity: sha512-bqDUCNqXeYypMCsE1VcTXSI1QuO4KXfx8Cvl6rYfY0bhhqN6d2WZlRkyLg/p6pm+DzvanqHOyYlqdPyP0iz+iw==} - engines: {node: '>=18.0'} + '@docusaurus/theme-classic@3.9.2': + resolution: {integrity: sha512-IGUsArG5hhekXd7RDb11v94ycpJpFdJPkLnt10fFQWOVxAtq5/D7hT6lzc2fhyQKaaCE62qVajOMKL7OiAFAIA==} + engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/theme-common@3.8.1': - resolution: {integrity: sha512-UswMOyTnPEVRvN5Qzbo+l8k4xrd5fTFu2VPPfD6FcW/6qUtVLmJTQCktbAL3KJ0BVXGm5aJXz/ZrzqFuZERGPw==} - engines: {node: '>=18.0'} + '@docusaurus/theme-common@3.9.2': + resolution: {integrity: sha512-6c4DAbR6n6nPbnZhY2V3tzpnKnGL+6aOsLvFL26VRqhlczli9eWG0VDUNoCQEPnGwDMhPS42UhSAnz5pThm5Ag==} + engines: {node: '>=20.0'} peerDependencies: '@docusaurus/plugin-content-docs': '*' react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/theme-search-algolia@3.8.1': - resolution: {integrity: sha512-NBFH5rZVQRAQM087aYSRKQ9yGEK9eHd+xOxQjqNpxMiV85OhJDD4ZGz6YJIod26Fbooy54UWVdzNU0TFeUUUzQ==} - engines: {node: '>=18.0'} + '@docusaurus/theme-search-algolia@3.9.2': + resolution: {integrity: sha512-GBDSFNwjnh5/LdkxCKQHkgO2pIMX1447BxYUBG2wBiajS21uj64a+gH/qlbQjDLxmGrbrllBrtJkUHxIsiwRnw==} + engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/theme-translations@3.8.1': - resolution: {integrity: sha512-OTp6eebuMcf2rJt4bqnvuwmm3NVXfzfYejL+u/Y1qwKhZPrjPoKWfk1CbOP5xH5ZOPkiAsx4dHdQBRJszK3z2g==} - engines: {node: '>=18.0'} + '@docusaurus/theme-translations@3.9.2': + resolution: {integrity: sha512-vIryvpP18ON9T9rjgMRFLr2xJVDpw1rtagEGf8Ccce4CkTrvM/fRB8N2nyWYOW5u3DdjkwKw5fBa+3tbn9P4PA==} + engines: {node: '>=20.0'} - '@docusaurus/tsconfig@3.8.1': - resolution: {integrity: sha512-XBWCcqhRHhkhfolnSolNL+N7gj3HVE3CoZVqnVjfsMzCoOsuQw2iCLxVVHtO+rePUUfouVZHURDgmqIySsF66A==} + '@docusaurus/tsconfig@3.9.2': + resolution: {integrity: sha512-j6/Fp4Rlpxsc632cnRnl5HpOWeb6ZKssDj6/XzzAzVGXXfm9Eptx3rxCC+fDzySn9fHTS+CWJjPineCR1bB5WQ==} - '@docusaurus/types@3.8.1': - resolution: {integrity: sha512-ZPdW5AB+pBjiVrcLuw3dOS6BFlrG0XkS2lDGsj8TizcnREQg3J8cjsgfDviszOk4CweNfwo1AEELJkYaMUuOPg==} + '@docusaurus/types@3.9.2': + resolution: {integrity: sha512-Ux1JUNswg+EfUEmajJjyhIohKceitY/yzjRUpu04WXgvVz+fbhVC0p+R0JhvEu4ytw8zIAys2hrdpQPBHRIa8Q==} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/utils-common@3.8.1': - resolution: {integrity: sha512-zTZiDlvpvoJIrQEEd71c154DkcriBecm4z94OzEE9kz7ikS3J+iSlABhFXM45mZ0eN5pVqqr7cs60+ZlYLewtg==} - engines: {node: '>=18.0'} + '@docusaurus/utils-common@3.9.2': + resolution: {integrity: sha512-I53UC1QctruA6SWLvbjbhCpAw7+X7PePoe5pYcwTOEXD/PxeP8LnECAhTHHwWCblyUX5bMi4QLRkxvyZ+IT8Aw==} + engines: {node: '>=20.0'} - '@docusaurus/utils-validation@3.8.1': - resolution: {integrity: sha512-gs5bXIccxzEbyVecvxg6upTwaUbfa0KMmTj7HhHzc016AGyxH2o73k1/aOD0IFrdCsfJNt37MqNI47s2MgRZMA==} - engines: {node: '>=18.0'} + '@docusaurus/utils-validation@3.9.2': + resolution: {integrity: sha512-l7yk3X5VnNmATbwijJkexdhulNsQaNDwoagiwujXoxFbWLcxHQqNQ+c/IAlzrfMMOfa/8xSBZ7KEKDesE/2J7A==} + engines: {node: '>=20.0'} - '@docusaurus/utils@3.8.1': - resolution: {integrity: sha512-P1ml0nvOmEFdmu0smSXOqTS1sxU5tqvnc0dA4MTKV39kye+bhQnjkIKEE18fNOvxjyB86k8esoCIFM3x4RykOQ==} - engines: {node: '>=18.0'} + '@docusaurus/utils@3.9.2': + resolution: {integrity: sha512-lBSBiRruFurFKXr5Hbsl2thmGweAPmddhF3jb99U4EMDA5L+e5Y1rAkOS07Nvrup7HUMBDrCV45meaxZnt28nQ==} + engines: {node: '>=20.0'} '@emnapi/runtime@1.5.0': resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==} @@ -2180,8 +2262,8 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.25.9': - resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] @@ -2192,8 +2274,8 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.25.9': - resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==} + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} engines: {node: '>=18'} cpu: [arm64] os: [android] @@ -2204,8 +2286,8 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.25.9': - resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==} + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} engines: {node: '>=18'} cpu: [arm] os: [android] @@ -2216,8 +2298,8 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.25.9': - resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==} + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} engines: {node: '>=18'} cpu: [x64] os: [android] @@ -2228,8 +2310,8 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.25.9': - resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==} + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] @@ -2240,8 +2322,8 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.25.9': - resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==} + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] @@ -2252,8 +2334,8 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.25.9': - resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==} + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] @@ -2264,8 +2346,8 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.9': - resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==} + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] @@ -2276,8 +2358,8 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.25.9': - resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==} + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] @@ -2288,8 +2370,8 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.25.9': - resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==} + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} engines: {node: '>=18'} cpu: [arm] os: [linux] @@ -2300,8 +2382,8 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.25.9': - resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==} + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] @@ -2312,8 +2394,8 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.25.9': - resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==} + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} engines: {node: '>=18'} cpu: [loong64] os: [linux] @@ -2324,8 +2406,8 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.25.9': - resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==} + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] @@ -2336,8 +2418,8 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.25.9': - resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==} + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] @@ -2348,8 +2430,8 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.25.9': - resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==} + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] @@ -2360,8 +2442,8 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.25.9': - resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==} + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} engines: {node: '>=18'} cpu: [s390x] os: [linux] @@ -2372,14 +2454,14 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.25.9': - resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==} + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.9': - resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==} + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] @@ -2390,14 +2472,14 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.9': - resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==} + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.9': - resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==} + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] @@ -2408,14 +2490,14 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.9': - resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==} + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.25.9': - resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==} + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] @@ -2426,8 +2508,8 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.25.9': - resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==} + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} engines: {node: '>=18'} cpu: [x64] os: [sunos] @@ -2438,8 +2520,8 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.25.9': - resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==} + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] @@ -2450,8 +2532,8 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.25.9': - resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==} + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] @@ -2462,8 +2544,8 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.25.9': - resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==} + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -2474,49 +2556,56 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/regexpp@4.12.1': - resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/config-array@0.21.0': - resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/config-helpers@0.3.1': - resolution: {integrity: sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==} + '@eslint/config-helpers@0.4.1': + resolution: {integrity: sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/core@0.15.2': resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@0.16.0': + resolution: {integrity: sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/eslintrc@3.3.1': resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.35.0': - resolution: {integrity: sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw==} + '@eslint/js@9.38.0': + resolution: {integrity: sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/object-schema@2.1.6': - resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/plugin-kit@0.3.5': resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@exodus/schemasafe@1.3.0': - resolution: {integrity: sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==} + '@eslint/plugin-kit@0.4.0': + resolution: {integrity: sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@faker-js/faker@10.0.0': - resolution: {integrity: sha512-UollFEUkVXutsaP+Vndjxar40Gs5JL2HeLcl8xO1QAjJgOdhc3OmBFWyEylS+RddWaaBiAzH+5/17PLQJwDiLw==} + '@extism/extism@2.0.0-rc13': + resolution: {integrity: sha512-iQ3mrPKOC0WMZ94fuJrKbJmMyz4LQ9Abf8gd4F5ShxKWa+cRKcVzk0EqRQsp5xXsQ2dO3zJTiA6eTc4Ihf7k+A==} + + '@extism/js-pdk@1.1.1': + resolution: {integrity: sha512-VZLn/dX0ttA1uKk2PZeR/FL3N+nA1S5Vc7E5gdjkR60LuUIwCZT9cYON245V4HowHlBA7YOegh0TLjkx+wNbrA==} + + '@faker-js/faker@10.1.0': + resolution: {integrity: sha512-C3mrr3b5dRVlKPJdfrAXS8+dq+rq8Qm5SNRazca0JKgw1HQERFmrVb0towvMmw5uu8hHKNiQasMaR/tydf3Zsg==} engines: {node: ^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0, npm: '>=10'} - '@faker-js/faker@5.5.3': - resolution: {integrity: sha512-R11tGE6yIFwqpaIqcfkcg7AICXzFg14+5h5v0TfF/9+RMDL6jhzCy/pxHVOfbALGdtVYdt6JdR21tuxEgl34dw==} - deprecated: Please update to a newer version. - '@fig/complete-commander@3.2.0': resolution: {integrity: sha512-1Holl3XtRiANVKURZwgpjCnPuV4RsHp+XC0MhgvyAX/avQwj7F2HUItYOvGi/bXjJCkEzgBZmVfCr0HBA+q+Bw==} peerDependencies: @@ -2531,29 +2620,29 @@ packages: '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} - '@formatjs/ecma402-abstract@2.3.4': - resolution: {integrity: sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA==} + '@formatjs/ecma402-abstract@2.3.6': + resolution: {integrity: sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==} '@formatjs/fast-memoize@2.2.7': resolution: {integrity: sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==} - '@formatjs/icu-messageformat-parser@2.11.2': - resolution: {integrity: sha512-AfiMi5NOSo2TQImsYAg8UYddsNJ/vUEv/HaNqiFjnI3ZFfWihUtD5QtuX6kHl8+H+d3qvnE/3HZrfzgdWpsLNA==} + '@formatjs/icu-messageformat-parser@2.11.4': + resolution: {integrity: sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==} - '@formatjs/icu-skeleton-parser@1.8.14': - resolution: {integrity: sha512-i4q4V4qslThK4Ig8SxyD76cp3+QJ3sAqr7f6q9VVfeGtxG9OhiAk3y9XF6Q41OymsKzsGQ6OQQoJNY4/lI8TcQ==} + '@formatjs/icu-skeleton-parser@1.8.16': + resolution: {integrity: sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==} - '@formatjs/intl-localematcher@0.6.1': - resolution: {integrity: sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==} + '@formatjs/intl-localematcher@0.6.2': + resolution: {integrity: sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==} - '@golevelup/nestjs-discovery@4.0.3': - resolution: {integrity: sha512-8w3CsXHN7+7Sn2i419Eal1Iw/kOjAd6Kb55M/ZqKBBwACCMn4WiEuzssC71LpBMI1090CiDxuelfPRwwIrQK+A==} + '@golevelup/nestjs-discovery@5.0.0': + resolution: {integrity: sha512-NaIWLCLI+XvneUK05LH2idHLmLNITYT88YnpOuUQmllKtiJNIS3woSt7QXrMZ5k3qUWuZpehEVz1JtlX4I1KyA==} peerDependencies: - '@nestjs/common': ^10.x || ^11.0.0 - '@nestjs/core': ^10.x || ^11.0.0 + '@nestjs/common': ^11.0.20 + '@nestjs/core': ^11.0.20 - '@grpc/grpc-js@1.13.4': - resolution: {integrity: sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==} + '@grpc/grpc-js@1.14.0': + resolution: {integrity: sha512-N8Jx6PaYzcTRNzirReJCtADVoq4z7+1KQ4E70jTg/koQiMoUSN1kbNjPOqpPbhMFhfU1/l7ixspPl8dNY+FoUg==} engines: {node: '>=12.10.0'} '@grpc/proto-loader@0.7.15': @@ -2561,6 +2650,11 @@ packages: engines: {node: '>=6'} hasBin: true + '@grpc/proto-loader@0.8.0': + resolution: {integrity: sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==} + engines: {node: '>=6'} + hasBin: true + '@hapi/hoek@9.3.0': resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} @@ -2583,130 +2677,142 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@img/sharp-darwin-arm64@0.34.3': - resolution: {integrity: sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==} + '@img/colour@1.0.0': + resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.4': + resolution: {integrity: sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [darwin] - '@img/sharp-darwin-x64@0.34.3': - resolution: {integrity: sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==} + '@img/sharp-darwin-x64@0.34.4': + resolution: {integrity: sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.2.0': - resolution: {integrity: sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==} + '@img/sharp-libvips-darwin-arm64@1.2.3': + resolution: {integrity: sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.2.0': - resolution: {integrity: sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==} + '@img/sharp-libvips-darwin-x64@1.2.3': + resolution: {integrity: sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-linux-arm64@1.2.0': - resolution: {integrity: sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==} + '@img/sharp-libvips-linux-arm64@1.2.3': + resolution: {integrity: sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linux-arm@1.2.0': - resolution: {integrity: sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==} + '@img/sharp-libvips-linux-arm@1.2.3': + resolution: {integrity: sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA==} cpu: [arm] os: [linux] - '@img/sharp-libvips-linux-ppc64@1.2.0': - resolution: {integrity: sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==} + '@img/sharp-libvips-linux-ppc64@1.2.3': + resolution: {integrity: sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg==} cpu: [ppc64] os: [linux] - '@img/sharp-libvips-linux-s390x@1.2.0': - resolution: {integrity: sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==} + '@img/sharp-libvips-linux-s390x@1.2.3': + resolution: {integrity: sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w==} cpu: [s390x] os: [linux] - '@img/sharp-libvips-linux-x64@1.2.0': - resolution: {integrity: sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==} + '@img/sharp-libvips-linux-x64@1.2.3': + resolution: {integrity: sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg==} cpu: [x64] os: [linux] - '@img/sharp-libvips-linuxmusl-arm64@1.2.0': - resolution: {integrity: sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==} + '@img/sharp-libvips-linuxmusl-arm64@1.2.3': + resolution: {integrity: sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linuxmusl-x64@1.2.0': - resolution: {integrity: sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==} + '@img/sharp-libvips-linuxmusl-x64@1.2.3': + resolution: {integrity: sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g==} cpu: [x64] os: [linux] - '@img/sharp-linux-arm64@0.34.3': - resolution: {integrity: sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==} + '@img/sharp-linux-arm64@0.34.4': + resolution: {integrity: sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linux-arm@0.34.3': - resolution: {integrity: sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==} + '@img/sharp-linux-arm@0.34.4': + resolution: {integrity: sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] - '@img/sharp-linux-ppc64@0.34.3': - resolution: {integrity: sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==} + '@img/sharp-linux-ppc64@0.34.4': + resolution: {integrity: sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] - '@img/sharp-linux-s390x@0.34.3': - resolution: {integrity: sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==} + '@img/sharp-linux-s390x@0.34.4': + resolution: {integrity: sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - '@img/sharp-linux-x64@0.34.3': - resolution: {integrity: sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==} + '@img/sharp-linux-x64@0.34.4': + resolution: {integrity: sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-linuxmusl-arm64@0.34.3': - resolution: {integrity: sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==} + '@img/sharp-linuxmusl-arm64@0.34.4': + resolution: {integrity: sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linuxmusl-x64@0.34.3': - resolution: {integrity: sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==} + '@img/sharp-linuxmusl-x64@0.34.4': + resolution: {integrity: sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-wasm32@0.34.3': - resolution: {integrity: sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==} + '@img/sharp-wasm32@0.34.4': + resolution: {integrity: sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] - '@img/sharp-win32-arm64@0.34.3': - resolution: {integrity: sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==} + '@img/sharp-win32-arm64@0.34.4': + resolution: {integrity: sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [win32] - '@img/sharp-win32-ia32@0.34.3': - resolution: {integrity: sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==} + '@img/sharp-win32-ia32@0.34.4': + resolution: {integrity: sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-x64@0.34.3': - resolution: {integrity: sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==} + '@img/sharp-win32-x64@0.34.4': + resolution: {integrity: sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [win32] - '@immich/ui@0.29.0': - resolution: {integrity: sha512-An9cf1L4nMO6+C1Tkktd+qjGmZvyGz/Un33cGsKQa2I7IdZHd67KbbC2v3wN3bQMiTjxtFJ8YR9EONohJ8jDtQ==} + '@immich/justified-layout-wasm@0.4.3': + resolution: {integrity: sha512-fpcQ7zPhP3Cp1bEXhONVYSUeIANa2uzaQFGKufUZQo5FO7aFT77szTVChhlCy4XaVy5R4ZvgSkA/1TJmeORz7Q==} + + '@immich/svelte-markdown-preprocess@0.1.0': + resolution: {integrity: sha512-jgSOJEGLPKEXQCNRI4r4YUayeM2b0ZYLdzgKGl891jZBhOQIetlY7rU44kPpV1AA3/8wGDwNFKduIQZZ/qJYzg==} + peerDependencies: + svelte: ^5.0.0 + + '@immich/ui@0.43.0': + resolution: {integrity: sha512-dwWIURsGghsbeFnqxCqUyWslyRU2vQjih7uewNr0nsW68bJ5/esl+V/Kiw2opiNiwI4Q3HEcuTRY57k4Hq+X3Q==} peerDependencies: svelte: ^5.0.0 @@ -2852,8 +2958,8 @@ packages: '@internationalized/date@3.8.2': resolution: {integrity: sha512-/wENk7CbvLbkUvX1tu0mwq49CVkkWpkXubGel6birjRPyo6uQ4nQpnq5xZu823zRCwwn82zgHrvgF1vZyvmVgA==} - '@ioredis/commands@1.3.0': - resolution: {integrity: sha512-M/T6Zewn7sDaBQEqIZ8Rb+i9y8qfGmq+5SDFSf9sA2lUZTmdDLVdOiQaeDp+Q4wElZ9HG1GAX5KhDaidp6LQsQ==} + '@ioredis/commands@1.4.0': + resolution: {integrity: sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ==} '@isaacs/balanced-match@4.0.1': resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} @@ -2893,18 +2999,54 @@ packages: resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - '@jridgewell/source-map@0.3.6': - resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - '@jridgewell/trace-mapping@0.3.30': - resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} '@js-sdsl/ordered-map@4.4.2': resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + '@jsonjoy.com/base64@1.1.2': + resolution: {integrity: sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + '@jsonjoy.com/buffers@1.2.1': + resolution: {integrity: sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + '@jsonjoy.com/codegen@1.0.0': + resolution: {integrity: sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + '@jsonjoy.com/json-pack@1.21.0': + resolution: {integrity: sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + '@jsonjoy.com/json-pointer@1.0.2': + resolution: {integrity: sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + '@jsonjoy.com/util@1.9.0': + resolution: {integrity: sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + '@koa/cors@5.0.0': resolution: {integrity: sha512-x/iUDjcS90W69PryLDIMgFyV21YLTnG9zOpPXS7Bkt2b8AsY3zZsIpOLBkYr9fBcF3HbkKaER5hOBZLfpLgYNw==} engines: {node: '>= 14.0.0'} @@ -2976,8 +3118,8 @@ packages: resolution: {integrity: sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==} engines: {node: '>=6.0.0'} - '@maplibre/maplibre-gl-style-spec@23.3.0': - resolution: {integrity: sha512-IGJtuBbaGzOUgODdBRg66p8stnwj9iDXkgbYKoYcNiiQmaez5WVRfXm4b03MCDwmZyX93csbfHFWEJJYHnn5oA==} + '@maplibre/maplibre-gl-style-spec@24.3.1': + resolution: {integrity: sha512-TUM5JD40H2mgtVXl5IwWz03BuQabw8oZQLJTmPpJA0YTYF+B+oZppy5lNMO6bMvHzB+/5mxqW9VLG3wFdeqtOw==} hasBin: true '@maplibre/vt-pbf@4.0.3': @@ -2995,8 +3137,8 @@ packages: '@mdn/browser-compat-data@6.0.27': resolution: {integrity: sha512-s5kTuDih5Ysb7DS2T2MhvneFLvDS0NwnLY5Jv6dL+zBXbcNVcyFcGdjwn3rttiHvcbb/qF02HP9ywufdwHZbIw==} - '@mdx-js/mdx@3.1.0': - resolution: {integrity: sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==} + '@mdx-js/mdx@3.1.1': + resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} '@mdx-js/react@3.1.1': resolution: {integrity: sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==} @@ -3007,16 +3149,6 @@ packages: '@microsoft/tsdoc@0.15.1': resolution: {integrity: sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==} - '@monaco-editor/loader@1.5.0': - resolution: {integrity: sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==} - - '@monaco-editor/react@4.7.0': - resolution: {integrity: sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==} - peerDependencies: - monaco-editor: '>= 0.25.0 < 1' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} cpu: [arm64] @@ -3050,14 +3182,14 @@ packages: '@namnode/store@0.1.0': resolution: {integrity: sha512-4NGTldxKcmY0UuZ7OEkvCjs8ZEoeYB6M2UwMu74pdLiFMKxXbj9HdNk1Qn213bxX1O7bY5h+PLh5DZsTURZkYA==} - '@nestjs/bull-shared@11.0.3': - resolution: {integrity: sha512-CaHniPkLAxis6fAB1DB8WZELQv8VPCLedbj7iP0VQ1pz74i6NSzG9mBg6tOomXq/WW4la4P4OMGEQ48UAJh20A==} + '@nestjs/bull-shared@11.0.4': + resolution: {integrity: sha512-VBJcDHSAzxQnpcDfA0kt9MTGUD1XZzfByV70su0W0eDCQ9aqIEBlzWRW21tv9FG9dIut22ysgDidshdjlnczLw==} peerDependencies: '@nestjs/common': ^10.0.0 || ^11.0.0 '@nestjs/core': ^10.0.0 || ^11.0.0 - '@nestjs/bullmq@11.0.3': - resolution: {integrity: sha512-0Qr7Fk3Ir3V2OBIKJk+ArEM0AesGjKaNZA8QQ4fH3qGogudYADSjaNe910/OAfmX8q+cjCRorvwTLdcShwWEMw==} + '@nestjs/bullmq@11.0.4': + resolution: {integrity: sha512-wBzK9raAVG0/6NTMdvLGM4/FQ1lsB35/pYS8L6a0SDgkTiLpd7mAjQ8R692oMx5s7IjvgntaZOuTUrKYLNfIkA==} peerDependencies: '@nestjs/common': ^10.0.0 || ^11.0.0 '@nestjs/core': ^10.0.0 || ^11.0.0 @@ -3076,8 +3208,8 @@ packages: '@swc/core': optional: true - '@nestjs/common@11.1.6': - resolution: {integrity: sha512-krKwLLcFmeuKDqngG2N/RuZHCs2ycsKcxWIDgcm7i1lf3sQ0iG03ci+DsP/r3FcT/eJDFsIHnKtNta2LIi7PzQ==} + '@nestjs/common@11.1.8': + resolution: {integrity: sha512-bbsOqwld/GdBfiRNc4nnjyWWENDEicq4SH+R5AuYatvf++vf1x5JIsHB1i1KtfZMD3eRte0D4K9WXuAYil6XAg==} peerDependencies: class-transformer: '>=0.4.1' class-validator: '>=0.13.2' @@ -3089,8 +3221,8 @@ packages: class-validator: optional: true - '@nestjs/core@11.1.6': - resolution: {integrity: sha512-siWX7UDgErisW18VTeJA+x+/tpNZrJewjTBsRPF3JVxuWRuAB1kRoiJcxHgln8Lb5UY9NdvklITR84DUEXD0Cg==} + '@nestjs/core@11.1.8': + resolution: {integrity: sha512-7riWfmTmMhCJHZ5ZiaG+crj4t85IPCq/wLRuOUSigBYyFT2JZj0lVHtAdf4Davp9ouNI8GINBDt9h9b5Gz9nTw==} engines: {node: '>= 20'} peerDependencies: '@nestjs/common': ^11.0.0 @@ -3120,32 +3252,32 @@ packages: class-validator: optional: true - '@nestjs/platform-express@11.1.6': - resolution: {integrity: sha512-HErwPmKnk+loTq8qzu1up+k7FC6Kqa8x6lJ4cDw77KnTxLzsCaPt+jBvOq6UfICmfqcqCCf3dKXg+aObQp+kIQ==} + '@nestjs/platform-express@11.1.8': + resolution: {integrity: sha512-rL6pZH9BW7BnL5X2eWbJMtt86uloAKjFgyY5+L2UkizgfEp7rgAs0+Z1z0BcW2Pgu5+q8O7RKPNyHJ/9ZNz/ZQ==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/core': ^11.0.0 - '@nestjs/platform-socket.io@11.1.6': - resolution: {integrity: sha512-ozm+OKiRiFLNQdFLA3ULDuazgdVaPrdRdgtG/+404T7tcROXpbUuFL0eEmWJpG64CxMkBNwamclUSH6J0AeU7A==} + '@nestjs/platform-socket.io@11.1.8': + resolution: {integrity: sha512-nMUvwcdztso8BjN9czRl4sm0Ewc5xrCcgLvy+QPt6VAnTdu06KcZqtA6Cl3MKxViSQsQ8NBN5foKvZehNt/tug==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/websockets': ^11.0.0 rxjs: ^7.1.0 - '@nestjs/schedule@6.0.0': - resolution: {integrity: sha512-aQySMw6tw2nhitELXd3EiRacQRgzUKD9mFcUZVOJ7jPLqIBvXOyvRWLsK9SdurGA+jjziAlMef7iB5ZEFFoQpw==} + '@nestjs/schedule@6.0.1': + resolution: {integrity: sha512-v3yO6cSPAoBSSyH67HWnXHzuhPhSNZhRmLY38JvCt2sqY8sPMOODpcU1D79iUMFf7k16DaMEbL4Mgx61ZhiC8Q==} peerDependencies: '@nestjs/common': ^10.0.0 || ^11.0.0 '@nestjs/core': ^10.0.0 || ^11.0.0 - '@nestjs/schematics@11.0.7': - resolution: {integrity: sha512-t8dNYYMwEeEsrlwc2jbkfwCfXczq4AeNEgx1KVQuJ6wYibXk0ZbXbPdfp8scnEAaQv1grpncNV5gWgzi7ZwbvQ==} + '@nestjs/schematics@11.0.9': + resolution: {integrity: sha512-0NfPbPlEaGwIT8/TCThxLzrlz3yzDNkfRNpbL7FiplKq3w4qXpJg0JYwqgMEJnLQZm3L/L/5XjoyfJHUO3qX9g==} peerDependencies: typescript: '>=4.8.2' - '@nestjs/swagger@11.2.0': - resolution: {integrity: sha512-5wolt8GmpNcrQv34tIPUtPoV1EeFbCetm40Ij3+M0FNNnf2RJ3FyWfuQvI8SBlcJyfaounYVTKzKHreFXsUyOg==} + '@nestjs/swagger@11.2.1': + resolution: {integrity: sha512-1MS7xf0pzc1mofG53xrrtrurnziafPUHkqzRm4YUVPA/egeiMaSerQBD/feiAeQ2BnX0WiLsTX4HQFO0icvOjQ==} peerDependencies: '@fastify/static': ^8.0.0 '@nestjs/common': ^11.0.1 @@ -3161,8 +3293,8 @@ packages: class-validator: optional: true - '@nestjs/testing@11.1.6': - resolution: {integrity: sha512-srYzzDNxGvVCe1j0SpTS9/ix75PKt6Sn6iMaH1rpJ6nj2g8vwNrhK0CoJJXvpCYgrnI+2WES2pprYnq8rAMYHA==} + '@nestjs/testing@11.1.8': + resolution: {integrity: sha512-E6K+0UTKztcPxJzLnQa7S34lFjZbrj3Z1r7c5y5WDrL1m5HD1H4AeyBhicHgdaFmxjLAva2bq0sYKy/S7cdeYA==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/core': ^11.0.0 @@ -3174,8 +3306,8 @@ packages: '@nestjs/platform-express': optional: true - '@nestjs/websockets@11.1.6': - resolution: {integrity: sha512-jlBX5QpqhfEVfxkwxTesIjgl0bdhgFMoORQYzjRg1i+Z+Qouf4KmjNPv5DZE3DZRDg91E+3Bpn0VgW0Yfl94ng==} + '@nestjs/websockets@11.1.8': + resolution: {integrity: sha512-RXo2336p/vyAwJ0qPInglzNSQ//qz+JTLr2LE1vlbmN5WcyB7zV6+gY06YgNdsr3oy/cXRh7fnC3Ph/VAu1EVg==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/core': ^11.0.0 @@ -3218,88 +3350,88 @@ packages: '@oazapfts/runtime@1.0.4': resolution: {integrity: sha512-7t6C2shug/6tZhQgkCa532oTYBLEnbASV/i1SG1rH2GB4h3aQQujYciYSPT92hvN4IwTe8S2hPkN/6iiOyTlCg==} - '@opentelemetry/api-logs@0.205.0': - resolution: {integrity: sha512-wBlPk1nFB37Hsm+3Qy73yQSobVn28F4isnWIBvKpd5IUH/eat8bwcL02H9yzmHyyPmukeccSl2mbN5sDQZYnPg==} + '@opentelemetry/api-logs@0.207.0': + resolution: {integrity: sha512-lAb0jQRVyleQQGiuuvCOTDVspc14nx6XJjP4FspJ1sNARo3Regq4ZZbrc3rN4b1TYSuUCvgH+UXUPug4SLOqEQ==} engines: {node: '>=8.0.0'} '@opentelemetry/api@1.9.0': resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} - '@opentelemetry/context-async-hooks@2.1.0': - resolution: {integrity: sha512-zOyetmZppnwTyPrt4S7jMfXiSX9yyfF0hxlA8B5oo2TtKl+/RGCy7fi4DrBfIf3lCPrkKsRBWZZD7RFojK7FDg==} + '@opentelemetry/context-async-hooks@2.2.0': + resolution: {integrity: sha512-qRkLWiUEZNAmYapZ7KGS5C4OmBLcP/H2foXeOEaowYCR0wi89fHejrfYfbuLVCMLp/dWZXKvQusdbUEZjERfwQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/core@2.1.0': - resolution: {integrity: sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==} + '@opentelemetry/core@2.2.0': + resolution: {integrity: sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/exporter-logs-otlp-grpc@0.205.0': - resolution: {integrity: sha512-jQlw7OHbqZ8zPt+pOrW2KGN7T55P50e3NXBMr4ckPOF+DWDwSy4W7mkG09GpYWlQAQ5C9BXg5gfUlv5ldTgWsw==} + '@opentelemetry/exporter-logs-otlp-grpc@0.207.0': + resolution: {integrity: sha512-K92RN+kQGTMzFDsCzsYNGqOsXRUnko/Ckk+t/yPJao72MewOLgBUTWVHhebgkNfRCYqDz1v3K0aPT9OJkemvgg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-logs-otlp-http@0.205.0': - resolution: {integrity: sha512-5JteMyVWiro4ghF0tHQjfE6OJcF7UBUcoEqX3UIQ5jutKP1H+fxFdyhqjjpmeHMFxzOHaYuLlNR1Bn7FOjGyJg==} + '@opentelemetry/exporter-logs-otlp-http@0.207.0': + resolution: {integrity: sha512-JpOh7MguEUls8eRfkVVW3yRhClo5b9LqwWTOg8+i4gjr/+8eiCtquJnC7whvpTIGyff06cLZ2NsEj+CVP3Mjeg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-logs-otlp-proto@0.205.0': - resolution: {integrity: sha512-q3VS9wS+lpZ01txKxiDGBtBpTNge3YhbVEFDgem9ZQR9eI3EZ68+9tVZH9zJcSxI37nZPJ6lEEZO58yEjYZsVA==} + '@opentelemetry/exporter-logs-otlp-proto@0.207.0': + resolution: {integrity: sha512-RQJEV/K6KPbQrIUbsrRkEe0ufks1o5OGLHy6jbDD8tRjeCsbFHWfg99lYBRqBV33PYZJXsigqMaAbjWGTFYzLw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-metrics-otlp-grpc@0.205.0': - resolution: {integrity: sha512-1Vxlo4lUwqSKYX+phFkXHKYR3DolFHxCku6lVMP1H8sVE3oj4wwmwxMzDsJ7zF+sXd8M0FCr+ckK4SnNNKkV+w==} + '@opentelemetry/exporter-metrics-otlp-grpc@0.207.0': + resolution: {integrity: sha512-6flX89W54gkwmqYShdcTBR1AEF5C1Ob0O8pDgmLPikTKyEv27lByr9yBmO5WrP0+5qJuNPHrLfgFQFYi6npDGA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-metrics-otlp-http@0.205.0': - resolution: {integrity: sha512-fFxNQ/HbbpLmh1pgU6HUVbFD1kNIjrkoluoKJkh88+gnmpFD92kMQ8WFNjPnSbjg2mNVnEkeKXgCYEowNW+p1w==} + '@opentelemetry/exporter-metrics-otlp-http@0.207.0': + resolution: {integrity: sha512-fG8FAJmvXOrKXGIRN8+y41U41IfVXxPRVwyB05LoMqYSjugx/FSBkMZUZXUT/wclTdmBKtS5MKoi0bEKkmRhSw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-metrics-otlp-proto@0.205.0': - resolution: {integrity: sha512-qIbNnedw9QfFjwpx4NQvdgjK3j3R2kWH/2T+7WXAm1IfMFe9fwatYxE61i7li4CIJKf8HgUC3GS8Du0C3D+AuQ==} + '@opentelemetry/exporter-metrics-otlp-proto@0.207.0': + resolution: {integrity: sha512-kDBxiTeQjaRlUQzS1COT9ic+et174toZH6jxaVuVAvGqmxOkgjpLOjrI5ff8SMMQE69r03L3Ll3nPKekLopLwg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-prometheus@0.205.0': - resolution: {integrity: sha512-xsot/Qm9VLDTag4GEwAunD1XR1U8eBHTLAgO7IZNo2JuD/c/vL7xmDP7mQIUr6Lk3gtj/yGGIR2h3vhTeVzv4w==} + '@opentelemetry/exporter-prometheus@0.207.0': + resolution: {integrity: sha512-Y5p1s39FvIRmU+F1++j7ly8/KSqhMmn6cMfpQqiDCqDjdDHwUtSq0XI0WwL3HYGnZeaR/VV4BNmsYQJ7GAPrhw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-trace-otlp-grpc@0.205.0': - resolution: {integrity: sha512-ZBksUk84CcQOuDJB65yu5A4PORkC4qEsskNwCrPZxDLeWjPOFZNSWt0E0jQxKCY8PskLhjNXJYo12YaqsYvGFA==} + '@opentelemetry/exporter-trace-otlp-grpc@0.207.0': + resolution: {integrity: sha512-7u2ZmcIx6D4KG/+5np4X2qA0o+O0K8cnUDhR4WI/vr5ZZ0la9J9RG+tkSjC7Yz+2XgL6760gSIM7/nyd3yaBLA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-trace-otlp-http@0.205.0': - resolution: {integrity: sha512-vr2bwwPCSc9u7rbKc74jR+DXFvyMFQo9o5zs+H/fgbK672Whw/1izUKVf+xfWOdJOvuwTnfWxy+VAY+4TSo74Q==} + '@opentelemetry/exporter-trace-otlp-http@0.207.0': + resolution: {integrity: sha512-HSRBzXHIC7C8UfPQdu15zEEoBGv0yWkhEwxqgPCHVUKUQ9NLHVGXkVrf65Uaj7UwmAkC1gQfkuVYvLlD//AnUQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-trace-otlp-proto@0.205.0': - resolution: {integrity: sha512-bGtFzqiENO2GpJk988mOBMe0MfeNpTQjbLm/LBijas6VRyEDQarUzdBHpFlu89A25k1+BCntdWGsWTa9Ai4FyA==} + '@opentelemetry/exporter-trace-otlp-proto@0.207.0': + resolution: {integrity: sha512-ruUQB4FkWtxHjNmSXjrhmJZFvyMm+tBzHyMm7YPQshApy4wvZUTcrpPyP/A/rCl/8M4BwoVIZdiwijMdbZaq4w==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-zipkin@2.1.0': - resolution: {integrity: sha512-0mEI0VDZrrX9t5RE1FhAyGz+jAGt96HSuXu73leswtY3L5YZD11gtcpARY2KAx/s6Z2+rj5Mhj566JsI2C7mfA==} + '@opentelemetry/exporter-zipkin@2.2.0': + resolution: {integrity: sha512-VV4QzhGCT7cWrGasBWxelBjqbNBbyHicWWS/66KoZoe9BzYwFB72SH2/kkc4uAviQlO8iwv2okIJy+/jqqEHTg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.0.0 @@ -3310,102 +3442,102 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-http@0.205.0': - resolution: {integrity: sha512-6fOgRlV7ypBuEzCQP7vXkLQxz3UL1FhE24rAlMRbwGvPAnZLvutcG/fq9FI/n+VU23dOpYexocYsXCf5oy/AXw==} + '@opentelemetry/instrumentation-http@0.207.0': + resolution: {integrity: sha512-FC4i5hVixTzuhg4SV2ycTEAYx+0E2hm+GwbdoVPSA6kna0pPVI4etzaA9UkpJ9ussumQheFXP6rkGIaFJjMxsw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-ioredis@0.53.0': - resolution: {integrity: sha512-Ah2wU347vOJYbE563Tgm3UX2J3DAXoI8gsr8qH0OOO4uDuEv3kVS/eDCfXApt11bvvDDPlOoc60/TGn6m9IoPw==} + '@opentelemetry/instrumentation-ioredis@0.55.0': + resolution: {integrity: sha512-ASuBMzh0ImmfOnWj9vCPtBMqSjr54/r/HluUIylwZB7xzTU6gL2SfybxySJMzEL9+386gJJVApwQktVznAtrWA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-nestjs-core@0.51.0': - resolution: {integrity: sha512-Se/m4887W94OO12pjKMjI3398L7HCoWeCjcbwoPvNOWpSpMkljBOHA9vE/fyo63CaVG1XAM5xA4ad60wmJKl9A==} + '@opentelemetry/instrumentation-nestjs-core@0.54.0': + resolution: {integrity: sha512-kqJcOVcniazueWTXt9czK6gd9xlHw5IM5JQM4wfH0ZkjZjNkKtQNzlhjdJpvqVhU9bGHet1yfrHOKXxlP4YeOA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-pg@0.58.0': - resolution: {integrity: sha512-WHntZAorf6CZ0n5a3oHlwGkSeu5Xa4AiCmXkNTKg24TbYSFWzJUtWvPQSkxePvQ3ku71lhAY/M20WgwHlvpZpQ==} + '@opentelemetry/instrumentation-pg@0.60.0': + resolution: {integrity: sha512-qZKeQojYJMoo7kWbHw/+SHopSdhfTxNISsBS+ZBbkr44sepmk/PmyU2AbOsSK7VOKvFt3oZde2n2nly7rk0SsA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation@0.205.0': - resolution: {integrity: sha512-cgvm7tvQdu9Qo7VurJP84wJ7ZV9F6WqDDGZpUc6rUEXwjV7/bXWs0kaYp9v+1Vh1+3TZCD3i6j/lUBcPhu8NhA==} + '@opentelemetry/instrumentation@0.207.0': + resolution: {integrity: sha512-y6eeli9+TLKnznrR8AZlQMSJT7wILpXH+6EYq5Vf/4Ao+huI7EedxQHwRgVUOMLFbe7VFDvHJrX9/f4lcwnJsA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-exporter-base@0.205.0': - resolution: {integrity: sha512-2MN0C1IiKyo34M6NZzD6P9Nv9Dfuz3OJ3rkZwzFmF6xzjDfqqCTatc9v1EpNfaP55iDOCLHFyYNCgs61FFgtUQ==} + '@opentelemetry/otlp-exporter-base@0.207.0': + resolution: {integrity: sha512-4RQluMVVGMrHok/3SVeSJ6EnRNkA2MINcX88sh+d/7DjGUrewW/WT88IsMEci0wUM+5ykTpPPNbEOoW+jwHnbw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-grpc-exporter-base@0.205.0': - resolution: {integrity: sha512-AeuLfrciGYffqsp4EUTdYYc6Ee2BQS+hr08mHZk1C524SFWx0WnfcTnV0NFXbVURUNU6DZu1DhS89zRRrcx/hg==} + '@opentelemetry/otlp-grpc-exporter-base@0.207.0': + resolution: {integrity: sha512-eKFjKNdsPed4q9yYqeI5gBTLjXxDM/8jwhiC0icw3zKxHVGBySoDsed5J5q/PGY/3quzenTr3FiTxA3NiNT+nw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-transformer@0.205.0': - resolution: {integrity: sha512-KmObgqPtk9k/XTlWPJHdMbGCylRAmMJNXIRh6VYJmvlRDMfe+DonH41G7eenG8t4FXn3fxOGh14o/WiMRR6vPg==} + '@opentelemetry/otlp-transformer@0.207.0': + resolution: {integrity: sha512-+6DRZLqM02uTIY5GASMZWUwr52sLfNiEe20+OEaZKhztCs3+2LxoTjb6JxFRd9q1qNqckXKYlUKjbH/AhG8/ZA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/propagator-b3@2.1.0': - resolution: {integrity: sha512-yOdHmFseIChYanddMMz0mJIFQHyjwbNhoxc65fEAA8yanxcBPwoFDoh1+WBUWAO/Z0NRgk+k87d+aFIzAZhcBw==} + '@opentelemetry/propagator-b3@2.2.0': + resolution: {integrity: sha512-9CrbTLFi5Ee4uepxg2qlpQIozoJuoAZU5sKMx0Mn7Oh+p7UrgCiEV6C02FOxxdYVRRFQVCinYR8Kf6eMSQsIsw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/propagator-jaeger@2.1.0': - resolution: {integrity: sha512-QYo7vLyMjrBCUTpwQBF/e+rvP7oGskrSELGxhSvLj5gpM0az9oJnu/0O4l2Nm7LEhAff80ntRYKkAcSwVgvSVQ==} + '@opentelemetry/propagator-jaeger@2.2.0': + resolution: {integrity: sha512-FfeOHOrdhiNzecoB1jZKp2fybqmqMPJUXe2ZOydP7QzmTPYcfPeuaclTLYVhK3HyJf71kt8sTl92nV4YIaLaKA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/redis-common@0.38.0': - resolution: {integrity: sha512-4Wc0AWURII2cfXVVoZ6vDqK+s5n4K5IssdrlVrvGsx6OEOKdghKtJZqXAHWFiZv4nTDLH2/2fldjIHY8clMOjQ==} + '@opentelemetry/redis-common@0.38.2': + resolution: {integrity: sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA==} engines: {node: ^18.19.0 || >=20.6.0} - '@opentelemetry/resources@2.1.0': - resolution: {integrity: sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==} + '@opentelemetry/resources@2.2.0': + resolution: {integrity: sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/sdk-logs@0.205.0': - resolution: {integrity: sha512-nyqhNQ6eEzPWQU60Nc7+A5LIq8fz3UeIzdEVBQYefB4+msJZ2vuVtRuk9KxPMw1uHoHDtYEwkr2Ct0iG29jU8w==} + '@opentelemetry/sdk-logs@0.207.0': + resolution: {integrity: sha512-4MEQmn04y+WFe6cyzdrXf58hZxilvY59lzZj2AccuHW/+BxLn/rGVN/Irsi/F0qfBOpMOrrCLKTExoSL2zoQmg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.4.0 <1.10.0' - '@opentelemetry/sdk-metrics@2.1.0': - resolution: {integrity: sha512-J9QX459mzqHLL9Y6FZ4wQPRZG4TOpMCyPOh6mkr/humxE1W2S3Bvf4i75yiMW9uyed2Kf5rxmLhTm/UK8vNkAw==} + '@opentelemetry/sdk-metrics@2.2.0': + resolution: {integrity: sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.9.0 <1.10.0' - '@opentelemetry/sdk-node@0.205.0': - resolution: {integrity: sha512-Y4Wcs8scj/Wy1u61pX1ggqPXPtCsGaqx/UnFu7BtRQE1zCQR+b0h56K7I0jz7U2bRlPUZIFdnNLtoaJSMNzz2g==} + '@opentelemetry/sdk-node@0.207.0': + resolution: {integrity: sha512-hnRsX/M8uj0WaXOBvFenQ8XsE8FLVh2uSnn1rkWu4mx+qu7EKGUZvZng6y/95cyzsqOfiaDDr08Ek4jppkIDNg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/sdk-trace-base@2.1.0': - resolution: {integrity: sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ==} + '@opentelemetry/sdk-trace-base@2.2.0': + resolution: {integrity: sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/sdk-trace-node@2.1.0': - resolution: {integrity: sha512-SvVlBFc/jI96u/mmlKm86n9BbTCbQ35nsPoOohqJX6DXH92K0kTe73zGY5r8xoI1QkjR9PizszVJLzMC966y9Q==} + '@opentelemetry/sdk-trace-node@2.2.0': + resolution: {integrity: sha512-+OaRja3f0IqGG2kptVeYsrZQK9nKRSpfFrKtRBq4uh6nIB8bTBgaGvYQrQoRrQWQMA5dK5yLhDMDc0dvYvCOIQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' @@ -3414,8 +3546,8 @@ packages: resolution: {integrity: sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==} engines: {node: '>=14'} - '@opentelemetry/sql-common@0.41.0': - resolution: {integrity: sha512-pmzXctVbEERbqSfiAgdes9Y63xjoOyXcD7B6IXBkVb+vbM7M9U98mn33nGXxPf4dfYR0M+vhcKRZmbSJ7HfqFA==} + '@opentelemetry/sql-common@0.41.2': + resolution: {integrity: sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.1.0 @@ -3448,8 +3580,8 @@ packages: peerDependencies: '@photo-sphere-viewer/core': 5.14.0 - '@photostructure/tz-lookup@11.2.0': - resolution: {integrity: sha512-DwrvodcXHNSdGdeSF7SBL5o8aBlsaeuCuG7633F04nYsL3hn5Hxe3z/5kCqxv61J1q7ggKZ27GPylR3x0cPNXQ==} + '@photostructure/tz-lookup@11.3.0': + resolution: {integrity: sha512-rYGy7ETBHTnXrwbzm47e3LJPKJmzpY7zXnbZhdosNU0lTGWVqzxptSjK4qZkJ1G+Kwy4F6XStNR9ZqMsXAoASQ==} '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} @@ -3459,8 +3591,8 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@playwright/test@1.55.0': - resolution: {integrity: sha512-04IXzPwHrW69XusN/SIdDdKZBzMfOT9UNT/YiJit/xpy2VuAoB8NHc8Aplb96zsWDddLnbkPL3TsmrS04ZU2xQ==} + '@playwright/test@1.56.1': + resolution: {integrity: sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==} engines: {node: '>=18'} hasBin: true @@ -3538,8 +3670,8 @@ packages: peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/components@0.5.3': - resolution: {integrity: sha512-8G5vsoMehuGOT4cDqaYLdpagtqCYPl4vThXNylClxO6SrN2w9Mh1+i2RNGj/rdqh/woamHORjlXMYCA/kzDMew==} + '@react-email/components@0.5.7': + resolution: {integrity: sha512-ECyVoyDcev2FSQ7C0buXaIJ0+6MRDXNUbCOZwBRrlLdCCRjap2b4+MHrYSTXFzo5kqfjjRoyo/2PbJXFQni67g==} engines: {node: '>=18.0.0'} peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc @@ -3591,8 +3723,8 @@ packages: peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/markdown@0.0.15': - resolution: {integrity: sha512-UQA9pVm5sbflgtg3EX3FquUP4aMBzmLReLbGJ6DZQZnAskBF36aI56cRykDq1o+1jT+CKIK1CducPYziaXliag==} + '@react-email/markdown@0.0.16': + resolution: {integrity: sha512-KSUHmoBMYhvc6iGwlIDkm0DRGbGQ824iNjLMCJsBVUoKHGQYs7F/N3b1tnS1YzRUX+GwHIexSsHuIUEi1m+8OQ==} engines: {node: '>=18.0.0'} peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc @@ -3603,8 +3735,8 @@ packages: peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-email/render@1.2.3': - resolution: {integrity: sha512-qu3XYNkHGao3teJexVD5CrcgFkNLrzbZvpZN17a7EyQYUN3kHkTkE9saqY4VbvGx6QoNU3p8rsk/Xm++D/+pTw==} + '@react-email/render@1.4.0': + resolution: {integrity: sha512-ZtJ3noggIvW1ZAryoui95KJENKdCzLmN5F7hyZY1F/17B1vwzuxHB7YkuCg0QqHjDivc5axqYEYdIOw4JIQdUw==} engines: {node: '>=18.0.0'} peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc @@ -3634,17 +3766,6 @@ packages: peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc - '@reduxjs/toolkit@1.9.7': - resolution: {integrity: sha512-t7v8ZPxhhKgOKtU+uyJT13lu4vL7az5aFi4IdoDs/eS548edn2M8Ik9h8fxgvMjGoAUVFSt6ZC1P5cWmQ014QQ==} - peerDependencies: - react: ^16.9.0 || ^17.0.0 || ^18 - react-redux: ^7.2.1 || ^8.0.2 - peerDependenciesMeta: - react: - optional: true - react-redux: - optional: true - '@rollup/pluginutils@5.3.0': resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} engines: {node: '>=14.0.0'} @@ -3654,108 +3775,113 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.50.1': - resolution: {integrity: sha512-HJXwzoZN4eYTdD8bVV22DN8gsPCAj3V20NHKOs8ezfXanGpmVPR7kalUHd+Y31IJp9stdB87VKPFbsGY3H/2ag==} + '@rollup/rollup-android-arm-eabi@4.52.5': + resolution: {integrity: sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.50.1': - resolution: {integrity: sha512-PZlsJVcjHfcH53mOImyt3bc97Ep3FJDXRpk9sMdGX0qgLmY0EIWxCag6EigerGhLVuL8lDVYNnSo8qnTElO4xw==} + '@rollup/rollup-android-arm64@4.52.5': + resolution: {integrity: sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.50.1': - resolution: {integrity: sha512-xc6i2AuWh++oGi4ylOFPmzJOEeAa2lJeGUGb4MudOtgfyyjr4UPNK+eEWTPLvmPJIY/pgw6ssFIox23SyrkkJw==} + '@rollup/rollup-darwin-arm64@4.52.5': + resolution: {integrity: sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.50.1': - resolution: {integrity: sha512-2ofU89lEpDYhdLAbRdeyz/kX3Y2lpYc6ShRnDjY35bZhd2ipuDMDi6ZTQ9NIag94K28nFMofdnKeHR7BT0CATw==} + '@rollup/rollup-darwin-x64@4.52.5': + resolution: {integrity: sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.50.1': - resolution: {integrity: sha512-wOsE6H2u6PxsHY/BeFHA4VGQN3KUJFZp7QJBmDYI983fgxq5Th8FDkVuERb2l9vDMs1D5XhOrhBrnqcEY6l8ZA==} + '@rollup/rollup-freebsd-arm64@4.52.5': + resolution: {integrity: sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.50.1': - resolution: {integrity: sha512-A/xeqaHTlKbQggxCqispFAcNjycpUEHP52mwMQZUNqDUJFFYtPHCXS1VAG29uMlDzIVr+i00tSFWFLivMcoIBQ==} + '@rollup/rollup-freebsd-x64@4.52.5': + resolution: {integrity: sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.50.1': - resolution: {integrity: sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg==} + '@rollup/rollup-linux-arm-gnueabihf@4.52.5': + resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.50.1': - resolution: {integrity: sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw==} + '@rollup/rollup-linux-arm-musleabihf@4.52.5': + resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.50.1': - resolution: {integrity: sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw==} + '@rollup/rollup-linux-arm64-gnu@4.52.5': + resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.50.1': - resolution: {integrity: sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w==} + '@rollup/rollup-linux-arm64-musl@4.52.5': + resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.50.1': - resolution: {integrity: sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q==} + '@rollup/rollup-linux-loong64-gnu@4.52.5': + resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.50.1': - resolution: {integrity: sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q==} + '@rollup/rollup-linux-ppc64-gnu@4.52.5': + resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.50.1': - resolution: {integrity: sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ==} + '@rollup/rollup-linux-riscv64-gnu@4.52.5': + resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.50.1': - resolution: {integrity: sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg==} + '@rollup/rollup-linux-riscv64-musl@4.52.5': + resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.50.1': - resolution: {integrity: sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg==} + '@rollup/rollup-linux-s390x-gnu@4.52.5': + resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.50.1': - resolution: {integrity: sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA==} + '@rollup/rollup-linux-x64-gnu@4.52.5': + resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.50.1': - resolution: {integrity: sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg==} + '@rollup/rollup-linux-x64-musl@4.52.5': + resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==} cpu: [x64] os: [linux] - '@rollup/rollup-openharmony-arm64@4.50.1': - resolution: {integrity: sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA==} + '@rollup/rollup-openharmony-arm64@4.52.5': + resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.50.1': - resolution: {integrity: sha512-hpZB/TImk2FlAFAIsoElM3tLzq57uxnGYwplg6WDyAxbYczSi8O2eQ+H2Lx74504rwKtZ3N2g4bCUkiamzS6TQ==} + '@rollup/rollup-win32-arm64-msvc@4.52.5': + resolution: {integrity: sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.50.1': - resolution: {integrity: sha512-SXjv8JlbzKM0fTJidX4eVsH+Wmnp0/WcD8gJxIZyR6Gay5Qcsmdbi9zVtnbkGPG8v2vMR1AD06lGWy5FLMcG7A==} + '@rollup/rollup-win32-ia32-msvc@4.52.5': + resolution: {integrity: sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.50.1': - resolution: {integrity: sha512-StxAO/8ts62KZVRAm4JZYq9+NqNsV7RvimNK+YM7ry//zebEH6meuugqW/P5OFUCjyQgui+9fUxT6d5NShvMvA==} + '@rollup/rollup-win32-x64-gnu@4.52.5': + resolution: {integrity: sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.52.5': + resolution: {integrity: sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==} cpu: [x64] os: [win32] @@ -3794,172 +3920,176 @@ packages: '@slorber/remark-comment@1.0.0': resolution: {integrity: sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA==} - '@smithy/abort-controller@4.1.1': - resolution: {integrity: sha512-vkzula+IwRvPR6oKQhMYioM3A/oX/lFCZiwuxkQbRhqJS2S4YRY2k7k/SyR2jMf3607HLtbEwlRxi0ndXHMjRg==} + '@smithy/abort-controller@4.2.3': + resolution: {integrity: sha512-xWL9Mf8b7tIFuAlpjKtRPnHrR8XVrwTj5NPYO/QwZPtc0SDLsPxb56V5tzi5yspSMytISHybifez+4jlrx0vkQ==} engines: {node: '>=18.0.0'} - '@smithy/config-resolver@4.2.2': - resolution: {integrity: sha512-IT6MatgBWagLybZl1xQcURXRICvqz1z3APSCAI9IqdvfCkrA7RaQIEfgC6G/KvfxnDfQUDqFV+ZlixcuFznGBQ==} + '@smithy/config-resolver@4.4.0': + resolution: {integrity: sha512-Kkmz3Mup2PGp/HNJxhCWkLNdlajJORLSjwkcfrj0E7nu6STAEdcMR1ir5P9/xOmncx8xXfru0fbUYLlZog/cFg==} engines: {node: '>=18.0.0'} - '@smithy/core@3.11.0': - resolution: {integrity: sha512-Abs5rdP1o8/OINtE49wwNeWuynCu0kme1r4RI3VXVrHr4odVDG7h7mTnw1WXXfN5Il+c25QOnrdL2y56USfxkA==} + '@smithy/core@3.17.1': + resolution: {integrity: sha512-V4Qc2CIb5McABYfaGiIYLTmo/vwNIK7WXI5aGveBd9UcdhbOMwcvIMxIw/DJj1S9QgOMa/7FBkarMdIC0EOTEQ==} engines: {node: '>=18.0.0'} - '@smithy/credential-provider-imds@4.1.2': - resolution: {integrity: sha512-JlYNq8TShnqCLg0h+afqe2wLAwZpuoSgOyzhYvTgbiKBWRov+uUve+vrZEQO6lkdLOWPh7gK5dtb9dS+KGendg==} + '@smithy/credential-provider-imds@4.2.3': + resolution: {integrity: sha512-hA1MQ/WAHly4SYltJKitEsIDVsNmXcQfYBRv2e+q04fnqtAX5qXaybxy/fhUeAMCnQIdAjaGDb04fMHQefWRhw==} engines: {node: '>=18.0.0'} - '@smithy/fetch-http-handler@5.2.1': - resolution: {integrity: sha512-5/3wxKNtV3wO/hk1is+CZUhL8a1yy/U+9u9LKQ9kZTkMsHaQjJhc3stFfiujtMnkITjzWfndGA2f7g9Uh9vKng==} + '@smithy/fetch-http-handler@5.3.4': + resolution: {integrity: sha512-bwigPylvivpRLCm+YK9I5wRIYjFESSVwl8JQ1vVx/XhCw0PtCi558NwTnT2DaVCl5pYlImGuQTSwMsZ+pIavRw==} engines: {node: '>=18.0.0'} - '@smithy/hash-node@4.1.1': - resolution: {integrity: sha512-H9DIU9WBLhYrvPs9v4sYvnZ1PiAI0oc8CgNQUJ1rpN3pP7QADbTOUjchI2FB764Ub0DstH5xbTqcMJu1pnVqxA==} + '@smithy/hash-node@4.2.3': + resolution: {integrity: sha512-6+NOdZDbfuU6s1ISp3UOk5Rg953RJ2aBLNLLBEcamLjHAg1Po9Ha7QIB5ZWhdRUVuOUrT8BVFR+O2KIPmw027g==} engines: {node: '>=18.0.0'} - '@smithy/invalid-dependency@4.1.1': - resolution: {integrity: sha512-1AqLyFlfrrDkyES8uhINRlJXmHA2FkG+3DY8X+rmLSqmFwk3DJnvhyGzyByPyewh2jbmV+TYQBEfngQax8IFGg==} + '@smithy/invalid-dependency@4.2.3': + resolution: {integrity: sha512-Cc9W5DwDuebXEDMpOpl4iERo8I0KFjTnomK2RMdhhR87GwrSmUmwMxS4P5JdRf+LsjOdIqumcerwRgYMr/tZ9Q==} engines: {node: '>=18.0.0'} '@smithy/is-array-buffer@2.2.0': resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} engines: {node: '>=14.0.0'} - '@smithy/is-array-buffer@4.1.0': - resolution: {integrity: sha512-ePTYUOV54wMogio+he4pBybe8fwg4sDvEVDBU8ZlHOZXbXK3/C0XfJgUCu6qAZcawv05ZhZzODGUerFBPsPUDQ==} + '@smithy/is-array-buffer@4.2.0': + resolution: {integrity: sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==} engines: {node: '>=18.0.0'} - '@smithy/middleware-content-length@4.1.1': - resolution: {integrity: sha512-9wlfBBgTsRvC2JxLJxv4xDGNBrZuio3AgSl0lSFX7fneW2cGskXTYpFxCdRYD2+5yzmsiTuaAJD1Wp7gWt9y9w==} + '@smithy/middleware-content-length@4.2.3': + resolution: {integrity: sha512-/atXLsT88GwKtfp5Jr0Ks1CSa4+lB+IgRnkNrrYP0h1wL4swHNb0YONEvTceNKNdZGJsye+W2HH8W7olbcPUeA==} engines: {node: '>=18.0.0'} - '@smithy/middleware-endpoint@4.2.2': - resolution: {integrity: sha512-M51KcwD+UeSOFtpALGf5OijWt915aQT5eJhqnMKJt7ZTfDfNcvg2UZgIgTZUoiORawb6o5lk4n3rv7vnzQXgsA==} + '@smithy/middleware-endpoint@4.3.5': + resolution: {integrity: sha512-SIzKVTvEudFWJbxAaq7f2GvP3jh2FHDpIFI6/VAf4FOWGFZy0vnYMPSRj8PGYI8Hjt29mvmwSRgKuO3bK4ixDw==} engines: {node: '>=18.0.0'} - '@smithy/middleware-retry@4.2.2': - resolution: {integrity: sha512-KZJueEOO+PWqflv2oGx9jICpHdBYXwCI19j7e2V3IMwKgFcXc9D9q/dsTf4B+uCnYxjNoS1jpyv6pGNGRsKOXA==} + '@smithy/middleware-retry@4.4.5': + resolution: {integrity: sha512-DCaXbQqcZ4tONMvvdz+zccDE21sLcbwWoNqzPLFlZaxt1lDtOE2tlVpRSwcTOJrjJSUThdgEYn7HrX5oLGlK9A==} engines: {node: '>=18.0.0'} - '@smithy/middleware-serde@4.1.1': - resolution: {integrity: sha512-lh48uQdbCoj619kRouev5XbWhCwRKLmphAif16c4J6JgJ4uXjub1PI6RL38d3BLliUvSso6klyB/LTNpWSNIyg==} + '@smithy/middleware-serde@4.2.3': + resolution: {integrity: sha512-8g4NuUINpYccxiCXM5s1/V+uLtts8NcX4+sPEbvYQDZk4XoJfDpq5y2FQxfmUL89syoldpzNzA0R9nhzdtdKnQ==} engines: {node: '>=18.0.0'} - '@smithy/middleware-stack@4.1.1': - resolution: {integrity: sha512-ygRnniqNcDhHzs6QAPIdia26M7e7z9gpkIMUe/pK0RsrQ7i5MblwxY8078/QCnGq6AmlUUWgljK2HlelsKIb/A==} + '@smithy/middleware-stack@4.2.3': + resolution: {integrity: sha512-iGuOJkH71faPNgOj/gWuEGS6xvQashpLwWB1HjHq1lNNiVfbiJLpZVbhddPuDbx9l4Cgl0vPLq5ltRfSaHfspA==} engines: {node: '>=18.0.0'} - '@smithy/node-config-provider@4.2.2': - resolution: {integrity: sha512-SYGTKyPvyCfEzIN5rD8q/bYaOPZprYUPD2f5g9M7OjaYupWOoQFYJ5ho+0wvxIRf471i2SR4GoiZ2r94Jq9h6A==} + '@smithy/node-config-provider@4.3.3': + resolution: {integrity: sha512-NzI1eBpBSViOav8NVy1fqOlSfkLgkUjUTlohUSgAEhHaFWA3XJiLditvavIP7OpvTjDp5u2LhtlBhkBlEisMwA==} engines: {node: '>=18.0.0'} - '@smithy/node-http-handler@4.2.1': - resolution: {integrity: sha512-REyybygHlxo3TJICPF89N2pMQSf+p+tBJqpVe1+77Cfi9HBPReNjTgtZ1Vg73exq24vkqJskKDpfF74reXjxfw==} + '@smithy/node-http-handler@4.4.3': + resolution: {integrity: sha512-MAwltrDB0lZB/H6/2M5PIsISSwdI5yIh6DaBB9r0Flo9nx3y0dzl/qTMJPd7tJvPdsx6Ks/cwVzheGNYzXyNbQ==} engines: {node: '>=18.0.0'} - '@smithy/property-provider@4.1.1': - resolution: {integrity: sha512-gm3ZS7DHxUbzC2wr8MUCsAabyiXY0gaj3ROWnhSx/9sPMc6eYLMM4rX81w1zsMaObj2Lq3PZtNCC1J6lpEY7zg==} + '@smithy/property-provider@4.2.3': + resolution: {integrity: sha512-+1EZ+Y+njiefCohjlhyOcy1UNYjT+1PwGFHCxA/gYctjg3DQWAU19WigOXAco/Ql8hZokNehpzLd0/+3uCreqQ==} engines: {node: '>=18.0.0'} - '@smithy/protocol-http@5.2.1': - resolution: {integrity: sha512-T8SlkLYCwfT/6m33SIU/JOVGNwoelkrvGjFKDSDtVvAXj/9gOT78JVJEas5a+ETjOu4SVvpCstKgd0PxSu/aHw==} + '@smithy/protocol-http@5.3.3': + resolution: {integrity: sha512-Mn7f/1aN2/jecywDcRDvWWWJF4uwg/A0XjFMJtj72DsgHTByfjRltSqcT9NyE9RTdBSN6X1RSXrhn/YWQl8xlw==} engines: {node: '>=18.0.0'} - '@smithy/querystring-builder@4.1.1': - resolution: {integrity: sha512-J9b55bfimP4z/Jg1gNo+AT84hr90p716/nvxDkPGCD4W70MPms0h8KF50RDRgBGZeL83/u59DWNqJv6tEP/DHA==} + '@smithy/querystring-builder@4.2.3': + resolution: {integrity: sha512-LOVCGCmwMahYUM/P0YnU/AlDQFjcu+gWbFJooC417QRB/lDJlWSn8qmPSDp+s4YVAHOgtgbNG4sR+SxF/VOcJQ==} engines: {node: '>=18.0.0'} - '@smithy/querystring-parser@4.1.1': - resolution: {integrity: sha512-63TEp92YFz0oQ7Pj9IuI3IgnprP92LrZtRAkE3c6wLWJxfy/yOPRt39IOKerVr0JS770olzl0kGafXlAXZ1vng==} + '@smithy/querystring-parser@4.2.3': + resolution: {integrity: sha512-cYlSNHcTAX/wc1rpblli3aUlLMGgKZ/Oqn8hhjFASXMCXjIqeuQBei0cnq2JR8t4RtU9FpG6uyl6PxyArTiwKA==} engines: {node: '>=18.0.0'} - '@smithy/service-error-classification@4.1.1': - resolution: {integrity: sha512-Iam75b/JNXyDE41UvrlM6n8DNOa/r1ylFyvgruTUx7h2Uk7vDNV9AAwP1vfL1fOL8ls0xArwEGVcGZVd7IO/Cw==} + '@smithy/service-error-classification@4.2.3': + resolution: {integrity: sha512-NkxsAxFWwsPsQiwFG2MzJ/T7uIR6AQNh1SzcxSUnmmIqIQMlLRQDKhc17M7IYjiuBXhrQRjQTo3CxX+DobS93g==} engines: {node: '>=18.0.0'} - '@smithy/shared-ini-file-loader@4.2.0': - resolution: {integrity: sha512-OQTfmIEp2LLuWdxa8nEEPhZmiOREO6bcB6pjs0AySf4yiZhl6kMOfqmcwcY8BaBPX+0Tb+tG7/Ia/6mwpoZ7Pw==} + '@smithy/shared-ini-file-loader@4.3.3': + resolution: {integrity: sha512-9f9Ixej0hFhroOK2TxZfUUDR13WVa8tQzhSzPDgXe5jGL3KmaM9s8XN7RQwqtEypI82q9KHnKS71CJ+q/1xLtQ==} engines: {node: '>=18.0.0'} - '@smithy/signature-v4@5.2.1': - resolution: {integrity: sha512-M9rZhWQLjlQVCCR37cSjHfhriGRN+FQ8UfgrYNufv66TJgk+acaggShl3KS5U/ssxivvZLlnj7QH2CUOKlxPyA==} + '@smithy/signature-v4@5.3.3': + resolution: {integrity: sha512-CmSlUy+eEYbIEYN5N3vvQTRfqt0lJlQkaQUIf+oizu7BbDut0pozfDjBGecfcfWf7c62Yis4JIEgqQ/TCfodaA==} engines: {node: '>=18.0.0'} - '@smithy/smithy-client@4.6.2': - resolution: {integrity: sha512-u82cjh/x7MlMat76Z38TRmEcG6JtrrxN4N2CSNG5o2v2S3hfLAxRgSgFqf0FKM3dglH41Evknt/HOX+7nfzZ3g==} + '@smithy/smithy-client@4.9.1': + resolution: {integrity: sha512-Ngb95ryR5A9xqvQFT5mAmYkCwbXvoLavLFwmi7zVg/IowFPCfiqRfkOKnbc/ZRL8ZKJ4f+Tp6kSu6wjDQb8L/g==} engines: {node: '>=18.0.0'} - '@smithy/types@4.5.0': - resolution: {integrity: sha512-RkUpIOsVlAwUIZXO1dsz8Zm+N72LClFfsNqf173catVlvRZiwPy0x2u0JLEA4byreOPKDZPGjmPDylMoP8ZJRg==} + '@smithy/types@4.8.0': + resolution: {integrity: sha512-QpELEHLO8SsQVtqP+MkEgCYTFW0pleGozfs3cZ183ZBj9z3VC1CX1/wtFMK64p+5bhtZo41SeLK1rBRtd25nHQ==} engines: {node: '>=18.0.0'} - '@smithy/url-parser@4.1.1': - resolution: {integrity: sha512-bx32FUpkhcaKlEoOMbScvc93isaSiRM75pQ5IgIBaMkT7qMlIibpPRONyx/0CvrXHzJLpOn/u6YiDX2hcvs7Dg==} + '@smithy/url-parser@4.2.3': + resolution: {integrity: sha512-I066AigYvY3d9VlU3zG9XzZg1yT10aNqvCaBTw9EPgu5GrsEl1aUkcMvhkIXascYH1A8W0LQo3B1Kr1cJNcQEw==} engines: {node: '>=18.0.0'} - '@smithy/util-base64@4.1.0': - resolution: {integrity: sha512-RUGd4wNb8GeW7xk+AY5ghGnIwM96V0l2uzvs/uVHf+tIuVX2WSvynk5CxNoBCsM2rQRSZElAo9rt3G5mJ/gktQ==} + '@smithy/util-base64@4.3.0': + resolution: {integrity: sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==} engines: {node: '>=18.0.0'} - '@smithy/util-body-length-browser@4.1.0': - resolution: {integrity: sha512-V2E2Iez+bo6bUMOTENPr6eEmepdY8Hbs+Uc1vkDKgKNA/brTJqOW/ai3JO1BGj9GbCeLqw90pbbH7HFQyFotGQ==} + '@smithy/util-body-length-browser@4.2.0': + resolution: {integrity: sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==} engines: {node: '>=18.0.0'} - '@smithy/util-body-length-node@4.1.0': - resolution: {integrity: sha512-BOI5dYjheZdgR9XiEM3HJcEMCXSoqbzu7CzIgYrx0UtmvtC3tC2iDGpJLsSRFffUpy8ymsg2ARMP5fR8mtuUQQ==} + '@smithy/util-body-length-node@4.2.1': + resolution: {integrity: sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==} engines: {node: '>=18.0.0'} '@smithy/util-buffer-from@2.2.0': resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} engines: {node: '>=14.0.0'} - '@smithy/util-buffer-from@4.1.0': - resolution: {integrity: sha512-N6yXcjfe/E+xKEccWEKzK6M+crMrlwaCepKja0pNnlSkm6SjAeLKKA++er5Ba0I17gvKfN/ThV+ZOx/CntKTVw==} + '@smithy/util-buffer-from@4.2.0': + resolution: {integrity: sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==} engines: {node: '>=18.0.0'} - '@smithy/util-config-provider@4.1.0': - resolution: {integrity: sha512-swXz2vMjrP1ZusZWVTB/ai5gK+J8U0BWvP10v9fpcFvg+Xi/87LHvHfst2IgCs1i0v4qFZfGwCmeD/KNCdJZbQ==} + '@smithy/util-config-provider@4.2.0': + resolution: {integrity: sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==} engines: {node: '>=18.0.0'} - '@smithy/util-defaults-mode-browser@4.1.2': - resolution: {integrity: sha512-QKrOw01DvNHKgY+3p4r9Ut4u6EHLVZ01u6SkOMe6V6v5C+nRPXJeWh72qCT1HgwU3O7sxAIu23nNh+FOpYVZKA==} + '@smithy/util-defaults-mode-browser@4.3.4': + resolution: {integrity: sha512-qI5PJSW52rnutos8Bln8nwQZRpyoSRN6k2ajyoUHNMUzmWqHnOJCnDELJuV6m5PML0VkHI+XcXzdB+6awiqYUw==} engines: {node: '>=18.0.0'} - '@smithy/util-defaults-mode-node@4.1.2': - resolution: {integrity: sha512-l2yRmSfx5haYHswPxMmCR6jGwgPs5LjHLuBwlj9U7nNBMS43YV/eevj+Xq1869UYdiynnMrCKtoOYQcwtb6lKg==} + '@smithy/util-defaults-mode-node@4.2.6': + resolution: {integrity: sha512-c6M/ceBTm31YdcFpgfgQAJaw3KbaLuRKnAz91iMWFLSrgxRpYm03c3bu5cpYojNMfkV9arCUelelKA7XQT36SQ==} engines: {node: '>=18.0.0'} - '@smithy/util-endpoints@3.1.2': - resolution: {integrity: sha512-+AJsaaEGb5ySvf1SKMRrPZdYHRYSzMkCoK16jWnIMpREAnflVspMIDeCVSZJuj+5muZfgGpNpijE3mUNtjv01Q==} + '@smithy/util-endpoints@3.2.3': + resolution: {integrity: sha512-aCfxUOVv0CzBIkU10TubdgKSx5uRvzH064kaiPEWfNIvKOtNpu642P4FP1hgOFkjQIkDObrfIDnKMKkeyrejvQ==} engines: {node: '>=18.0.0'} - '@smithy/util-hex-encoding@4.1.0': - resolution: {integrity: sha512-1LcueNN5GYC4tr8mo14yVYbh/Ur8jHhWOxniZXii+1+ePiIbsLZ5fEI0QQGtbRRP5mOhmooos+rLmVASGGoq5w==} + '@smithy/util-hex-encoding@4.2.0': + resolution: {integrity: sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==} engines: {node: '>=18.0.0'} - '@smithy/util-middleware@4.1.1': - resolution: {integrity: sha512-CGmZ72mL29VMfESz7S6dekqzCh8ZISj3B+w0g1hZFXaOjGTVaSqfAEFAq8EGp8fUL+Q2l8aqNmt8U1tglTikeg==} + '@smithy/util-middleware@4.2.3': + resolution: {integrity: sha512-v5ObKlSe8PWUHCqEiX2fy1gNv6goiw6E5I/PN2aXg3Fb/hse0xeaAnSpXDiWl7x6LamVKq7senB+m5LOYHUAHw==} engines: {node: '>=18.0.0'} - '@smithy/util-retry@4.1.1': - resolution: {integrity: sha512-jGeybqEZ/LIordPLMh5bnmnoIgsqnp4IEimmUp5c5voZ8yx+5kAlN5+juyr7p+f7AtZTgvhmInQk4Q0UVbrZ0Q==} + '@smithy/util-retry@4.2.3': + resolution: {integrity: sha512-lLPWnakjC0q9z+OtiXk+9RPQiYPNAovt2IXD3CP4LkOnd9NpUsxOjMx1SnoUVB7Orb7fZp67cQMtTBKMFDvOGg==} engines: {node: '>=18.0.0'} - '@smithy/util-stream@4.3.1': - resolution: {integrity: sha512-khKkW/Jqkgh6caxMWbMuox9+YfGlsk9OnHOYCGVEdYQb/XVzcORXHLYUubHmmda0pubEDncofUrPNniS9d+uAA==} + '@smithy/util-stream@4.5.4': + resolution: {integrity: sha512-+qDxSkiErejw1BAIXUFBSfM5xh3arbz1MmxlbMCKanDDZtVEQ7PSKW9FQS0Vud1eI/kYn0oCTVKyNzRlq+9MUw==} engines: {node: '>=18.0.0'} - '@smithy/util-uri-escape@4.1.0': - resolution: {integrity: sha512-b0EFQkq35K5NHUYxU72JuoheM6+pytEVUGlTwiFxWFpmddA+Bpz3LgsPRIpBk8lnPE47yT7AF2Egc3jVnKLuPg==} + '@smithy/util-uri-escape@4.2.0': + resolution: {integrity: sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==} engines: {node: '>=18.0.0'} '@smithy/util-utf8@2.3.0': resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} engines: {node: '>=14.0.0'} - '@smithy/util-utf8@4.1.0': - resolution: {integrity: sha512-mEu1/UIXAdNYuBcyEPbjScKi/+MQVXNIuY/7Cm5XLIWe319kDrT5SizBE95jqtmEXoDbGoZxKLCMttdZdqTZKQ==} + '@smithy/util-utf8@4.2.0': + resolution: {integrity: sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==} + engines: {node: '>=18.0.0'} + + '@smithy/uuid@1.1.0': + resolution: {integrity: sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==} engines: {node: '>=18.0.0'} '@socket.io/component-emitter@3.1.2': @@ -3974,25 +4104,25 @@ packages: '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} - '@sveltejs/acorn-typescript@1.0.5': - resolution: {integrity: sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==} + '@sveltejs/acorn-typescript@1.0.6': + resolution: {integrity: sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==} peerDependencies: acorn: ^8.9.0 - '@sveltejs/adapter-static@3.0.9': - resolution: {integrity: sha512-aytHXcMi7lb9ljsWUzXYQ0p5X1z9oWud2olu/EpmH7aCu4m84h7QLvb5Wp+CFirKcwoNnYvYWhyP/L8Vh1ztdw==} + '@sveltejs/adapter-static@3.0.10': + resolution: {integrity: sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew==} peerDependencies: '@sveltejs/kit': ^2.0.0 - '@sveltejs/enhanced-img@0.8.1': - resolution: {integrity: sha512-Ibom8j6F9vdmOeR+ljQ4q7TEJV0FWN1kB5wJZ2S/GuDVgCrKYrsXBw5gpSKcHwGYpKA3o9EoijPhep/GHgRUWQ==} + '@sveltejs/enhanced-img@0.8.4': + resolution: {integrity: sha512-/L12VUQj+ANIhskHT620jtxAs9wUPUIutgo37rwx5NgLnxOpVsP27fMiSpdIMgERldbgr6uRJ1WZGGOkzm7Vcg==} peerDependencies: '@sveltejs/vite-plugin-svelte': ^6.0.0 svelte: ^5.0.0 vite: ^6.3.0 || >=7.0.0 - '@sveltejs/kit@2.38.1': - resolution: {integrity: sha512-5JJBPu3U2KXpRwc+e/D2Pl+DJM9oBcCl6XtWenrb6xc6H4lFa0XIJaSch4wMiADrhX512sVIUf13VnEp7aWO1w==} + '@sveltejs/kit@2.48.3': + resolution: {integrity: sha512-jf8mx3yctRXE9hvixgcqqK94YI2hDnbxI/12Upkz99XFMvxnJKCMzvz0j7lmbXSyBSNEycWO5xHvi7b73y9qkQ==} engines: {node: '>=18.13'} hasBin: true peerDependencies: @@ -4012,8 +4142,8 @@ packages: svelte: ^5.0.0 vite: ^6.3.0 || ^7.0.0 - '@sveltejs/vite-plugin-svelte@6.2.0': - resolution: {integrity: sha512-nJsV36+o7rZUDlrnSduMNl11+RoDE1cKqOI0yUEBCcqFoAZOk47TwD3dPKS2WmRutke9StXnzsPBslY7prDM9w==} + '@sveltejs/vite-plugin-svelte@6.2.1': + resolution: {integrity: sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==} engines: {node: ^20.19 || ^22.12 || >=24} peerDependencies: svelte: ^5.0.0 @@ -4097,68 +4227,68 @@ packages: resolution: {integrity: sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==} engines: {node: '>=14'} - '@swc/core-darwin-arm64@1.13.5': - resolution: {integrity: sha512-lKNv7SujeXvKn16gvQqUQI5DdyY8v7xcoO3k06/FJbHJS90zEwZdQiMNRiqpYw/orU543tPaWgz7cIYWhbopiQ==} + '@swc/core-darwin-arm64@1.14.0': + resolution: {integrity: sha512-uHPC8rlCt04nvYNczWzKVdgnRhxCa3ndKTBBbBpResOZsRmiwRAvByIGh599j+Oo6Z5eyTPrgY+XfJzVmXnN7Q==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] - '@swc/core-darwin-x64@1.13.5': - resolution: {integrity: sha512-ILd38Fg/w23vHb0yVjlWvQBoE37ZJTdlLHa8LRCFDdX4WKfnVBiblsCU9ar4QTMNdeTBEX9iUF4IrbNWhaF1Ng==} + '@swc/core-darwin-x64@1.14.0': + resolution: {integrity: sha512-2SHrlpl68vtePRknv9shvM9YKKg7B9T13tcTg9aFCwR318QTYo+FzsKGmQSv9ox/Ua0Q2/5y2BNjieffJoo4nA==} engines: {node: '>=10'} cpu: [x64] os: [darwin] - '@swc/core-linux-arm-gnueabihf@1.13.5': - resolution: {integrity: sha512-Q6eS3Pt8GLkXxqz9TAw+AUk9HpVJt8Uzm54MvPsqp2yuGmY0/sNaPPNVqctCX9fu/Nu8eaWUen0si6iEiCsazQ==} + '@swc/core-linux-arm-gnueabihf@1.14.0': + resolution: {integrity: sha512-SMH8zn01dxt809svetnxpeg/jWdpi6dqHKO3Eb11u4OzU2PK7I5uKS6gf2hx5LlTbcJMFKULZiVwjlQLe8eqtg==} engines: {node: '>=10'} cpu: [arm] os: [linux] - '@swc/core-linux-arm64-gnu@1.13.5': - resolution: {integrity: sha512-aNDfeN+9af+y+M2MYfxCzCy/VDq7Z5YIbMqRI739o8Ganz6ST+27kjQFd8Y/57JN/hcnUEa9xqdS3XY7WaVtSw==} + '@swc/core-linux-arm64-gnu@1.14.0': + resolution: {integrity: sha512-q2JRu2D8LVqGeHkmpVCljVNltG0tB4o4eYg+dElFwCS8l2Mnt9qurMCxIeo9mgoqz0ax+k7jWtIRHktnVCbjvQ==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-arm64-musl@1.13.5': - resolution: {integrity: sha512-9+ZxFN5GJag4CnYnq6apKTnnezpfJhCumyz0504/JbHLo+Ue+ZtJnf3RhyA9W9TINtLE0bC4hKpWi8ZKoETyOQ==} + '@swc/core-linux-arm64-musl@1.14.0': + resolution: {integrity: sha512-uofpVoPCEUjYIv454ZEZ3sLgMD17nIwlz2z7bsn7rl301Kt/01umFA7MscUovFfAK2IRGck6XB+uulMu6aFhKQ==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-x64-gnu@1.13.5': - resolution: {integrity: sha512-WD530qvHrki8Ywt/PloKUjaRKgstQqNGvmZl54g06kA+hqtSE2FTG9gngXr3UJxYu/cNAjJYiBifm7+w4nbHbA==} + '@swc/core-linux-x64-gnu@1.14.0': + resolution: {integrity: sha512-quTTx1Olm05fBfv66DEBuOsOgqdypnZ/1Bh3yGXWY7ANLFeeRpCDZpljD9BSjdsNdPOlwJmEUZXMHtGm3v1TZQ==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-linux-x64-musl@1.13.5': - resolution: {integrity: sha512-Luj8y4OFYx4DHNQTWjdIuKTq2f5k6uSXICqx+FSabnXptaOBAbJHNbHT/06JZh6NRUouaf0mYXN0mcsqvkhd7Q==} + '@swc/core-linux-x64-musl@1.14.0': + resolution: {integrity: sha512-caaNAu+aIqT8seLtCf08i8C3/UC5ttQujUjejhMcuS1/LoCKtNiUs4VekJd2UGt+pyuuSrQ6dKl8CbCfWvWeXw==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-win32-arm64-msvc@1.13.5': - resolution: {integrity: sha512-cZ6UpumhF9SDJvv4DA2fo9WIzlNFuKSkZpZmPG1c+4PFSEMy5DFOjBSllCvnqihCabzXzpn6ykCwBmHpy31vQw==} + '@swc/core-win32-arm64-msvc@1.14.0': + resolution: {integrity: sha512-EeW3jFlT3YNckJ6V/JnTfGcX7UHGyh6/AiCPopZ1HNaGiXVCKHPpVQZicmtyr/UpqxCXLrTgjHOvyMke7YN26A==} engines: {node: '>=10'} cpu: [arm64] os: [win32] - '@swc/core-win32-ia32-msvc@1.13.5': - resolution: {integrity: sha512-C5Yi/xIikrFUzZcyGj9L3RpKljFvKiDMtyDzPKzlsDrKIw2EYY+bF88gB6oGY5RGmv4DAX8dbnpRAqgFD0FMEw==} + '@swc/core-win32-ia32-msvc@1.14.0': + resolution: {integrity: sha512-dPai3KUIcihV5hfoO4QNQF5HAaw8+2bT7dvi8E5zLtecW2SfL3mUZipzampXq5FHll0RSCLzlrXnSx+dBRZIIQ==} engines: {node: '>=10'} cpu: [ia32] os: [win32] - '@swc/core-win32-x64-msvc@1.13.5': - resolution: {integrity: sha512-YrKdMVxbYmlfybCSbRtrilc6UA8GF5aPmGKBdPvjrarvsmf4i7ZHGCEnLtfOMd3Lwbs2WUZq3WdMbozYeLU93Q==} + '@swc/core-win32-x64-msvc@1.14.0': + resolution: {integrity: sha512-nm+JajGrTqUA6sEHdghDlHMNfH1WKSiuvljhdmBACW4ta4LC3gKurX2qZuiBARvPkephW9V/i5S8QPY1PzFEqg==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@swc/core@1.13.5': - resolution: {integrity: sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ==} + '@swc/core@1.14.0': + resolution: {integrity: sha512-oExhY90bes5pDTVrei0xlMVosTxwd/NMafIpqsC4dMbRYZ5KB981l/CX8tMnGsagTplj/RcG9BeRYmV6/J5m3w==} engines: {node: '>=10'} peerDependencies: '@swc/helpers': '>=0.5.17' @@ -4179,65 +4309,65 @@ packages: resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} engines: {node: '>=14.16'} - '@tailwindcss/node@4.1.13': - resolution: {integrity: sha512-eq3ouolC1oEFOAvOMOBAmfCIqZBJuvWvvYWh5h5iOYfe1HFC6+GZ6EIL0JdM3/niGRJmnrOc+8gl9/HGUaaptw==} + '@tailwindcss/node@4.1.16': + resolution: {integrity: sha512-BX5iaSsloNuvKNHRN3k2RcCuTEgASTo77mofW0vmeHkfrDWaoFAFvNHpEgtu0eqyypcyiBkDWzSMxJhp3AUVcw==} - '@tailwindcss/oxide-android-arm64@4.1.13': - resolution: {integrity: sha512-BrpTrVYyejbgGo57yc8ieE+D6VT9GOgnNdmh5Sac6+t0m+v+sKQevpFVpwX3pBrM2qKrQwJ0c5eDbtjouY/+ew==} + '@tailwindcss/oxide-android-arm64@4.1.16': + resolution: {integrity: sha512-8+ctzkjHgwDJ5caq9IqRSgsP70xhdhJvm+oueS/yhD5ixLhqTw9fSL1OurzMUhBwE5zK26FXLCz2f/RtkISqHA==} engines: {node: '>= 10'} cpu: [arm64] os: [android] - '@tailwindcss/oxide-darwin-arm64@4.1.13': - resolution: {integrity: sha512-YP+Jksc4U0KHcu76UhRDHq9bx4qtBftp9ShK/7UGfq0wpaP96YVnnjFnj3ZFrUAjc5iECzODl/Ts0AN7ZPOANQ==} + '@tailwindcss/oxide-darwin-arm64@4.1.16': + resolution: {integrity: sha512-C3oZy5042v2FOALBZtY0JTDnGNdS6w7DxL/odvSny17ORUnaRKhyTse8xYi3yKGyfnTUOdavRCdmc8QqJYwFKA==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@tailwindcss/oxide-darwin-x64@4.1.13': - resolution: {integrity: sha512-aAJ3bbwrn/PQHDxCto9sxwQfT30PzyYJFG0u/BWZGeVXi5Hx6uuUOQEI2Fa43qvmUjTRQNZnGqe9t0Zntexeuw==} + '@tailwindcss/oxide-darwin-x64@4.1.16': + resolution: {integrity: sha512-vjrl/1Ub9+JwU6BP0emgipGjowzYZMjbWCDqwA2Z4vCa+HBSpP4v6U2ddejcHsolsYxwL5r4bPNoamlV0xDdLg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@tailwindcss/oxide-freebsd-x64@4.1.13': - resolution: {integrity: sha512-Wt8KvASHwSXhKE/dJLCCWcTSVmBj3xhVhp/aF3RpAhGeZ3sVo7+NTfgiN8Vey/Fi8prRClDs6/f0KXPDTZE6nQ==} + '@tailwindcss/oxide-freebsd-x64@4.1.16': + resolution: {integrity: sha512-TSMpPYpQLm+aR1wW5rKuUuEruc/oOX3C7H0BTnPDn7W/eMw8W+MRMpiypKMkXZfwH8wqPIRKppuZoedTtNj2tg==} engines: {node: '>= 10'} cpu: [x64] os: [freebsd] - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.13': - resolution: {integrity: sha512-mbVbcAsW3Gkm2MGwA93eLtWrwajz91aXZCNSkGTx/R5eb6KpKD5q8Ueckkh9YNboU8RH7jiv+ol/I7ZyQ9H7Bw==} + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.16': + resolution: {integrity: sha512-p0GGfRg/w0sdsFKBjMYvvKIiKy/LNWLWgV/plR4lUgrsxFAoQBFrXkZ4C0w8IOXfslB9vHK/JGASWD2IefIpvw==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@tailwindcss/oxide-linux-arm64-gnu@4.1.13': - resolution: {integrity: sha512-wdtfkmpXiwej/yoAkrCP2DNzRXCALq9NVLgLELgLim1QpSfhQM5+ZxQQF8fkOiEpuNoKLp4nKZ6RC4kmeFH0HQ==} + '@tailwindcss/oxide-linux-arm64-gnu@4.1.16': + resolution: {integrity: sha512-DoixyMmTNO19rwRPdqviTrG1rYzpxgyYJl8RgQvdAQUzxC1ToLRqtNJpU/ATURSKgIg6uerPw2feW0aS8SNr/w==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-arm64-musl@4.1.13': - resolution: {integrity: sha512-hZQrmtLdhyqzXHB7mkXfq0IYbxegaqTmfa1p9MBj72WPoDD3oNOh1Lnxf6xZLY9C3OV6qiCYkO1i/LrzEdW2mg==} + '@tailwindcss/oxide-linux-arm64-musl@4.1.16': + resolution: {integrity: sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-x64-gnu@4.1.13': - resolution: {integrity: sha512-uaZTYWxSXyMWDJZNY1Ul7XkJTCBRFZ5Fo6wtjrgBKzZLoJNrG+WderJwAjPzuNZOnmdrVg260DKwXCFtJ/hWRQ==} + '@tailwindcss/oxide-linux-x64-gnu@4.1.16': + resolution: {integrity: sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-linux-x64-musl@4.1.13': - resolution: {integrity: sha512-oXiPj5mi4Hdn50v5RdnuuIms0PVPI/EG4fxAfFiIKQh5TgQgX7oSuDWntHW7WNIi/yVLAiS+CRGW4RkoGSSgVQ==} + '@tailwindcss/oxide-linux-x64-musl@4.1.16': + resolution: {integrity: sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-wasm32-wasi@4.1.13': - resolution: {integrity: sha512-+LC2nNtPovtrDwBc/nqnIKYh/W2+R69FA0hgoeOn64BdCX522u19ryLh3Vf3F8W49XBcMIxSe665kwy21FkhvA==} + '@tailwindcss/oxide-wasm32-wasi@4.1.16': + resolution: {integrity: sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q==} engines: {node: '>=14.0.0'} cpu: [wasm32] bundledDependencies: @@ -4248,24 +4378,24 @@ packages: - '@emnapi/wasi-threads' - tslib - '@tailwindcss/oxide-win32-arm64-msvc@4.1.13': - resolution: {integrity: sha512-dziTNeQXtoQ2KBXmrjCxsuPk3F3CQ/yb7ZNZNA+UkNTeiTGgfeh+gH5Pi7mRncVgcPD2xgHvkFCh/MhZWSgyQg==} + '@tailwindcss/oxide-win32-arm64-msvc@4.1.16': + resolution: {integrity: sha512-zX+Q8sSkGj6HKRTMJXuPvOcP8XfYON24zJBRPlszcH1Np7xuHXhWn8qfFjIujVzvH3BHU+16jBXwgpl20i+v9A==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@tailwindcss/oxide-win32-x64-msvc@4.1.13': - resolution: {integrity: sha512-3+LKesjXydTkHk5zXX01b5KMzLV1xl2mcktBJkje7rhFUpUlYJy7IMOLqjIRQncLTa1WZZiFY/foAeB5nmaiTw==} + '@tailwindcss/oxide-win32-x64-msvc@4.1.16': + resolution: {integrity: sha512-m5dDFJUEejbFqP+UXVstd4W/wnxA4F61q8SoL+mqTypId2T2ZpuxosNSgowiCnLp2+Z+rivdU0AqpfgiD7yCBg==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@tailwindcss/oxide@4.1.13': - resolution: {integrity: sha512-CPgsM1IpGRa880sMbYmG1s4xhAy3xEt1QULgTJGQmZUeNgXFR7s1YxYygmJyBGtou4SyEosGAGEeYqY7R53bIA==} + '@tailwindcss/oxide@4.1.16': + resolution: {integrity: sha512-2OSv52FRuhdlgyOQqgtQHuCgXnS8nFSYRp2tJ+4WZXKgTxqPy7SMSls8c3mPT5pkZ17SBToGM5LHEJBO7miEdg==} engines: {node: '>= 10'} - '@tailwindcss/vite@4.1.13': - resolution: {integrity: sha512-0PmqLQ010N58SbMTJ7BVJ4I2xopiQn/5i6nlb4JmxzQf8zcS5+m2Cv6tqh+sfDwtIdjoEnOvwsGQ1hkUi8QEHQ==} + '@tailwindcss/vite@4.1.16': + resolution: {integrity: sha512-bbguNBcDxsRmi9nnlWJxhfDWamY3lmcyACHcdO1crxfzuLpOhHLLtEIN/nCbbAtj5rchUgQD17QVAKi1f7IsKg==} peerDependencies: vite: ^5.2.0 || ^6 || ^7 @@ -4273,8 +4403,8 @@ packages: resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} engines: {node: '>=18'} - '@testing-library/jest-dom@6.8.0': - resolution: {integrity: sha512-WgXcWzVM6idy5JaftTVC8Vs83NKRmGJz4Hqs4oyOuO2J4r/y79vvKZsb+CaGyCSEbUPI6OsewfPd0G1A0/TUZQ==} + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} '@testing-library/svelte@5.2.8': @@ -4323,8 +4453,8 @@ packages: '@types/accepts@1.3.7': resolution: {integrity: sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==} - '@types/archiver@6.0.3': - resolution: {integrity: sha512-a6wUll6k3zX6qs5KlxIggs1P1JcYJaTCx2gnlr+f0S1yd2DoaEwoIK10HmBaLnZwWneBz+JBm0dwcZu0zECBcQ==} + '@types/archiver@6.0.4': + resolution: {integrity: sha512-ULdQpARQ3sz9WH4nb98mJDYA0ft2A8C4f4fovvUcFwINa1cgGjY36JCAYuP5YypRq4mco1lJp1/7jEMS2oR0Hg==} '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} @@ -4371,8 +4501,8 @@ packages: '@types/content-disposition@0.5.9': resolution: {integrity: sha512-8uYXI3Gw35MhiVYhG3s295oihrxRyytcRHjSjqnqZVDDy/xcGBRny7+Xj1Wgfhv5QzRtN2hB2dVRBUX9XW3UcQ==} - '@types/cookie-parser@1.4.9': - resolution: {integrity: sha512-tGZiZ2Gtc4m3wIdLkZ8mkj1T6CEHb35+VApbL2T14Dew8HA7c+04dmKqsKRNC+8RJPm16JEK0tFSwdZqubfc4g==} + '@types/cookie-parser@1.4.10': + resolution: {integrity: sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg==} peerDependencies: '@types/express': '*' @@ -4397,8 +4527,8 @@ packages: '@types/docker-modem@3.0.6': resolution: {integrity: sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==} - '@types/dockerode@3.3.42': - resolution: {integrity: sha512-U1jqHMShibMEWHdxYhj3rCMNCiLx5f35i4e3CEUuW+JSSszc/tVqc6WCAPdhwBymG5R/vgbcceagK0St7Cq6Eg==} + '@types/dockerode@3.3.45': + resolution: {integrity: sha512-iYpZF+xr5QLpIICejLdUF2r5gh8IXY1Gw3WLmt41dUbS3Vn/3hVgL+6lJBVbmrhYBWfbWPPstdr6+A0s95DTWA==} '@types/dom-to-image@2.6.7': resolution: {integrity: sha512-me5VbCv+fcXozblWwG13krNBvuEOm6kA5xoa4RrjDJCNFOZSWR3/QLtOXimBHk1Fisq69Gx3JtOoXtg1N1tijg==} @@ -4415,17 +4545,17 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@types/express-serve-static-core@4.19.6': - resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} + '@types/express-serve-static-core@4.19.7': + resolution: {integrity: sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==} - '@types/express-serve-static-core@5.0.6': - resolution: {integrity: sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==} + '@types/express-serve-static-core@5.1.0': + resolution: {integrity: sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==} - '@types/express@4.17.23': - resolution: {integrity: sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==} + '@types/express@4.17.25': + resolution: {integrity: sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==} - '@types/express@5.0.3': - resolution: {integrity: sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==} + '@types/express@5.0.5': + resolution: {integrity: sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==} '@types/filesystem@0.0.36': resolution: {integrity: sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==} @@ -4433,8 +4563,8 @@ packages: '@types/filewriter@0.0.33': resolution: {integrity: sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==} - '@types/fluent-ffmpeg@2.1.27': - resolution: {integrity: sha512-QiDWjihpUhriISNoBi2hJBRUUmoj/BMTYcfz+F+ZM9hHWBYABFAE6hjP/TbCZC0GWwlpa3FzvHH9RzFeRusZ7A==} + '@types/fluent-ffmpeg@2.1.28': + resolution: {integrity: sha512-5ovxsDwBcPfJ+eYs1I/ZpcYCnkce7pvH9AHSvrZllAp1ZPpTRDZAFjF3TRFbukxSgIYTTNYePbS0rKUmaxVbXw==} '@types/geojson-vt@3.2.5': resolution: {integrity: sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==} @@ -4457,9 +4587,6 @@ packages: '@types/history@4.7.11': resolution: {integrity: sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==} - '@types/hoist-non-react-statics@3.3.6': - resolution: {integrity: sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==} - '@types/html-minifier-terser@6.1.0': resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==} @@ -4472,8 +4599,8 @@ packages: '@types/http-errors@2.0.5': resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} - '@types/http-proxy@1.17.16': - resolution: {integrity: sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==} + '@types/http-proxy@1.17.17': + resolution: {integrity: sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==} '@types/inquirer@8.2.11': resolution: {integrity: sha512-15UboTvxb9SOaPG7CcXZ9dkv8lNqfiAwuh/5WxJDLjmElBt9tbx1/FDsEnJddUBKvN4mlPKvr8FyO1rAmBanzg==} @@ -4493,6 +4620,9 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/jsonwebtoken@9.0.10': + resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} + '@types/justified-layout@4.1.4': resolution: {integrity: sha512-q2ybP0u0NVj87oMnGZOGxY2iUN8ddr48zPOBHBdbOLpsMTA/keGj+93ou+OMCnJk0xewzlNIaVEkxM6VBD3E2w==} @@ -4505,8 +4635,8 @@ packages: '@types/koa@3.0.0': resolution: {integrity: sha512-MOcVYdVYmkSutVHZZPh8j3+dAjLyR5Tl59CN0eKgpkE1h/LBSmPAsQQuWs+bKu7WtGNn+hKfJH9Gzml+PulmDg==} - '@types/leaflet@1.9.20': - resolution: {integrity: sha512-rooalPMlk61LCaLOvBF2VIf9M47HgMQqi5xQ9QRi7c8PkdIe0WrIi5IxXUXQjAdL0c+vcQ01mYWbthzmp9GHWw==} + '@types/leaflet@1.9.21': + resolution: {integrity: sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==} '@types/lodash-es@4.17.12': resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} @@ -4514,9 +4644,6 @@ packages: '@types/lodash@4.17.20': resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==} - '@types/luxon@3.6.2': - resolution: {integrity: sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw==} - '@types/luxon@3.7.1': resolution: {integrity: sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg==} @@ -4529,8 +4656,8 @@ packages: '@types/methods@1.1.4': resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} - '@types/micromatch@4.0.9': - resolution: {integrity: sha512-7V+8ncr22h4UoYRLnLXSpTxjQrNUXtWHGeMPRJt1nULXI57G9bIcpyrHlmrQ7QK24EyyuXvYcSSWAM8GA9nqCg==} + '@types/micromatch@4.0.10': + resolution: {integrity: sha512-5jOhFDElqr4DKTrTEbnW8DZ4Hz5LRUEmyrGpCMrD/NphYv3nUnaF08xmSLx1rGGnyEs/kFnhiw6dCgcDqMr5PQ==} '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} @@ -4544,26 +4671,26 @@ packages: '@types/multer@2.0.0': resolution: {integrity: sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw==} - '@types/node-forge@1.3.11': - resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} + '@types/node-forge@1.3.14': + resolution: {integrity: sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==} '@types/node@17.0.45': resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} - '@types/node@18.19.126': - resolution: {integrity: sha512-8AXQlBfrGmtYJEJUPs63F/uZQqVeFiN9o6NUjbDJYfxNxFnArlZufANPw4h6dGhYGKxcyw+TapXFvEsguzIQow==} + '@types/node@18.19.130': + resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} - '@types/node@20.19.2': - resolution: {integrity: sha512-9pLGGwdzOUBDYi0GNjM97FIA+f92fqSke6joWeBjWXllfNxZBs7qeMF7tvtOIsbY45xkWkxrdwUfUf3MnQa9gA==} + '@types/node@20.19.24': + resolution: {integrity: sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==} - '@types/node@22.18.5': - resolution: {integrity: sha512-g9BpPfJvxYBXUWI9bV37j6d6LTMNQ88hPwdWWUeYZnMhlo66FIg9gCc1/DZb15QylJSKwOZjwrckvOTWpOiChg==} + '@types/node@22.19.0': + resolution: {integrity: sha512-xpr/lmLPQEj+TUnHmR+Ab91/glhJvsqcjB+yY0Ix9GO70H6Lb4FHH5GeqdOE5btAx7eIMwuHkp4H2MSkLcqWbA==} - '@types/node@24.5.1': - resolution: {integrity: sha512-/SQdmUP2xa+1rdx7VwB9yPq8PaKej8TD5cQ+XfKDPWWC+VDJU4rvVVagXqKUzhKjtFoNA8rXDJAkCxQPAe00+Q==} + '@types/node@24.10.0': + resolution: {integrity: sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==} - '@types/nodemailer@7.0.1': - resolution: {integrity: sha512-UfHAghPmGZVzaL8x9y+mKZMWyHC399+iq0MOmya5tIyenWX3lcdSb60vOmp0DocR6gCDTYTozv/ULQnREyyjkg==} + '@types/nodemailer@7.0.3': + resolution: {integrity: sha512-fC8w49YQ868IuPWRXqPfLf+MuTRex5Z1qxMoG8rr70riqqbOp2F5xgOKE9fODEBPzpnvjkJXFgK6IL2xgMSTnA==} '@types/oidc-provider@9.5.0': resolution: {integrity: sha512-eEzCRVTSqIHD9Bo/qRJ4XQWQ5Z/zBcG+Z2cGJluRsSuWx1RJihqRyPxhIEpMXTwPzHYRTQkVp7hwisQOwzzSAg==} @@ -4577,6 +4704,9 @@ packages: '@types/pg@8.15.5': resolution: {integrity: sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ==} + '@types/pg@8.15.6': + resolution: {integrity: sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==} + '@types/picomatch@4.0.2': resolution: {integrity: sha512-qHHxQ+P9PysNEGbALT8f8YOSHW0KJu6l2xU8DYY0fu/EmGxXdVnuTLvFUvBgPJMSqXq29SYHveejeAha+4AYgA==} @@ -4586,8 +4716,8 @@ packages: '@types/prismjs@1.26.5': resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==} - '@types/qrcode@1.5.5': - resolution: {integrity: sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==} + '@types/qrcode@1.5.6': + resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==} '@types/qs@6.14.0': resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} @@ -4595,9 +4725,6 @@ packages: '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} - '@types/react-redux@7.1.34': - resolution: {integrity: sha512-GdFaVjEbYv4Fthm2ZLvj1VSCedV7TqE5y1kNwnjSdBOTXuRSgowux6J8TAct15T3CKBr63UMk+2CO7ilRhyrAQ==} - '@types/react-router-config@5.0.11': resolution: {integrity: sha512-WmSAg7WgqW7m4x8Mt4N6ZyKz0BubSj/2tVUMsAHp+Yd2AMwcSbeFq9WympT19p5heCFmF97R9eD5uUR/t4HEqw==} @@ -4607,14 +4734,14 @@ packages: '@types/react-router@5.1.20': resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==} - '@types/react@19.1.13': - resolution: {integrity: sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==} + '@types/react@19.2.2': + resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==} '@types/readdir-glob@1.1.5': resolution: {integrity: sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==} - '@types/retry@0.12.0': - resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + '@types/retry@0.12.2': + resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==} '@types/sanitize-html@2.16.0': resolution: {integrity: sha512-l6rX1MUXje5ztPT0cAFtUayXF06DqPhRyfVXareEN5gGCFaP/iwsxIyKODr9XDhfxPpN6vXUFNfo5kZMXCxBtw==} @@ -4625,20 +4752,23 @@ packages: '@types/semver@7.7.1': resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} - '@types/send@0.17.5': - resolution: {integrity: sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==} + '@types/send@0.17.6': + resolution: {integrity: sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==} + + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} '@types/serve-index@1.9.4': resolution: {integrity: sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==} - '@types/serve-static@1.15.8': - resolution: {integrity: sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==} + '@types/serve-static@1.15.10': + resolution: {integrity: sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==} '@types/sockjs@0.3.36': resolution: {integrity: sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==} - '@types/ssh2-streams@0.1.12': - resolution: {integrity: sha512-Sy8tpEmCce4Tq0oSOYdfqaBpA3hDM8SoxoFh5vzFsu2oL+znzGz8oVWW7xb4K920yYMUY+PIG31qZnFMfPWNCg==} + '@types/ssh2-streams@0.1.13': + resolution: {integrity: sha512-faHyY3brO9oLEA0QlcO8N2wT7R0+1sHWZvQ+y3rMLwdY1ZyS1z0W3t65j9PqT4HmQ6ALzNe7RZlNuCNE0wBSWA==} '@types/ssh2@0.5.52': resolution: {integrity: sha512-lbLLlXxdCZOSJMCInKH2+9V/77ET2J6NPQHpFI0kda61Dd1KglJs+fPQBchizmzYSOJBgdTajhPqBO1xxLywvg==} @@ -4667,11 +4797,8 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@types/uuid@9.0.8': - resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} - - '@types/validator@13.15.3': - resolution: {integrity: sha512-7bcUmDyS6PN3EuD9SlGGOxM77F8WLVsrwkxyWxKnxzmXoequ6c7741QBrANq6htVRGOITJ7z72mTP6Z4XyuG+Q==} + '@types/validator@13.15.4': + resolution: {integrity: sha512-LSFfpSnJJY9wbC0LQxgvfb+ynbHftFo0tMsFOl/J4wexLnYMmDSPaj2ZyDv3TkfL1UePxPrxOWJfbiRS8mQv7A==} '@types/whatwg-mimetype@3.0.2': resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==} @@ -4682,71 +4809,75 @@ packages: '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} - '@types/yargs@17.0.33': - resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} + '@types/yargs@17.0.34': + resolution: {integrity: sha512-KExbHVa92aJpw9WDQvzBaGVE2/Pz+pLZQloT2hjL8IqsZnV62rlPOYvNnLmf/L2dyllfVUOVBj64M0z/46eR2A==} - '@typescript-eslint/eslint-plugin@8.43.0': - resolution: {integrity: sha512-8tg+gt7ENL7KewsKMKDHXR1vm8tt9eMxjJBYINf6swonlWgkYn5NwyIgXpbbDxTNU5DgpDFfj95prcTq2clIQQ==} + '@typescript-eslint/eslint-plugin@8.46.2': + resolution: {integrity: sha512-ZGBMToy857/NIPaaCucIUQgqueOiq7HeAKkhlvqVV4lm089zUFW6ikRySx2v+cAhKeUCPuWVHeimyk6Dw1iY3w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.43.0 + '@typescript-eslint/parser': ^8.46.2 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.43.0': - resolution: {integrity: sha512-B7RIQiTsCBBmY+yW4+ILd6mF5h1FUwJsVvpqkrgpszYifetQ2Ke+Z4u6aZh0CblkUGIdR59iYVyXqqZGkZ3aBw==} + '@typescript-eslint/parser@8.46.2': + resolution: {integrity: sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.43.0': - resolution: {integrity: sha512-htB/+D/BIGoNTQYffZw4uM4NzzuolCoaA/BusuSIcC8YjmBYQioew5VUZAYdAETPjeed0hqCaW7EHg+Robq8uw==} + '@typescript-eslint/project-service@8.46.2': + resolution: {integrity: sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.43.0': - resolution: {integrity: sha512-daSWlQ87ZhsjrbMLvpuuMAt3y4ba57AuvadcR7f3nl8eS3BjRc8L9VLxFLk92RL5xdXOg6IQ+qKjjqNEimGuAg==} + '@typescript-eslint/scope-manager@8.46.2': + resolution: {integrity: sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.43.0': - resolution: {integrity: sha512-ALC2prjZcj2YqqL5X/bwWQmHA2em6/94GcbB/KKu5SX3EBDOsqztmmX1kMkvAJHzxk7TazKzJfFiEIagNV3qEA==} + '@typescript-eslint/tsconfig-utils@8.46.2': + resolution: {integrity: sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.43.0': - resolution: {integrity: sha512-qaH1uLBpBuBBuRf8c1mLJ6swOfzCXryhKND04Igr4pckzSEW9JX5Aw9AgW00kwfjWJF0kk0ps9ExKTfvXfw4Qg==} + '@typescript-eslint/type-utils@8.46.2': + resolution: {integrity: sha512-HbPM4LbaAAt/DjxXaG9yiS9brOOz6fabal4uvUmaUYe6l3K1phQDMQKBRUrr06BQkxkvIZVVHttqiybM9nJsLA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.43.0': - resolution: {integrity: sha512-vQ2FZaxJpydjSZJKiSW/LJsabFFvV7KgLC5DiLhkBcykhQj8iK9BOaDmQt74nnKdLvceM5xmhaTF+pLekrxEkw==} + '@typescript-eslint/types@8.46.2': + resolution: {integrity: sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.43.0': - resolution: {integrity: sha512-7Vv6zlAhPb+cvEpP06WXXy/ZByph9iL6BQRBDj4kmBsW98AqEeQHlj/13X+sZOrKSo9/rNKH4Ul4f6EICREFdw==} + '@typescript-eslint/typescript-estree@8.46.2': + resolution: {integrity: sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.43.0': - resolution: {integrity: sha512-S1/tEmkUeeswxd0GGcnwuVQPFWo8NzZTOMxCvw8BX7OMxnNae+i8Tm7REQen/SwUIPoPqfKn7EaZ+YLpiB3k9g==} + '@typescript-eslint/utils@8.46.2': + resolution: {integrity: sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.43.0': - resolution: {integrity: sha512-T+S1KqRD4sg/bHfLwrpF/K3gQLBM1n7Rp7OjjikjTEssI2YJzQpi5WXoynOaQ93ERIuq3O8RBTOUYDKszUCEHw==} + '@typescript-eslint/visitor-keys@8.46.2': + resolution: {integrity: sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@vercel/oidc@3.0.3': + resolution: {integrity: sha512-yNEQvPcVrK9sIe637+I0jD6leluPxzwJKx/Haw6F4H77CdDsszUn5V3o96LPziXkSNE2B83+Z3mjqGKBK/R6Gg==} + engines: {node: '>= 20'} + '@vitest/coverage-v8@3.2.4': resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==} peerDependencies: @@ -4836,11 +4967,11 @@ packages: '@xtuc/long@4.2.2': resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} - '@zoom-image/core@0.41.0': - resolution: {integrity: sha512-LAxGru91286gFmyiQB4RjM277YOWxJX+OZcwtIH/N0dyo73y4NfaAE1eGVdnhjxEYv7yVV3xToMyYnm+uQboTw==} + '@zoom-image/core@0.41.3': + resolution: {integrity: sha512-yW2sZRP6FnZZ7vREpYucDpSCsVFgMSWq22Sp1kabsMPz5+6LSs+sBPPDCVs/ahVYSHa9YOX8rlGHM5iiTFPZLA==} - '@zoom-image/svelte@0.3.4': - resolution: {integrity: sha512-8cPkFUjh+t3/eYkoT2krvz8hoFiXoiYZKpcHOnYCHLhEwaHr1yjgXg/ttWehotVH9V3Z51JQgIcGF3uhYWKB/Q==} + '@zoom-image/svelte@0.3.7': + resolution: {integrity: sha512-vCgA2ClH8J2JBTzpJEgzy03yTi5uJE0963cUjBwc5fi7q8g1wxbbj4/W7YHwQfDmdh9uqHxlX0VcmUQqV44d+w==} peerDependencies: svelte: ^3.0.0 || ^4.0.0 || ^5.0.0 @@ -4911,13 +5042,11 @@ packages: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} - ajv-draft-04@1.0.0: - resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} + ai@5.0.82: + resolution: {integrity: sha512-wmZZfsU40qB77umrcj3YzMSk6cUP5gxLXZDPfiSQLBLegTVXPUdSJC603tR7JB5JkhBDzN5VLaliuRKQGKpUXg==} + engines: {node: '>=18'} peerDependencies: - ajv: ^8.5.0 - peerDependenciesMeta: - ajv: - optional: true + zod: ^3.25.76 || ^4.1.8 ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} @@ -4948,9 +5077,6 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - ajv@8.11.0: - resolution: {integrity: sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==} - ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} @@ -4959,8 +5085,8 @@ packages: peerDependencies: algoliasearch: '>= 3.1 < 6' - algoliasearch@5.29.0: - resolution: {integrity: sha512-E2l6AlTWGznM2e7vEE6T6hzObvEyXukxMOlBmVlMyixZyK1umuO/CiVc6sDBbzVH0oEviCE5IfVY1oZBmccYPQ==} + algoliasearch@5.41.0: + resolution: {integrity: sha512-9E4b3rJmYbBkn7e3aAPt1as+VVnRhsR4qwRRgOzpeyz4PAOuwKh0HI4AN6mTrqK0S0M9fCCSTOUnuJ8gPY/tvA==} engines: {node: '>= 14.0.0'} ansi-align@3.0.1: @@ -5087,12 +5213,6 @@ packages: async@0.2.10: resolution: {integrity: sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==} - async@3.2.2: - resolution: {integrity: sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g==} - - async@3.2.4: - resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} - async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} @@ -5131,8 +5251,8 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-plugin-polyfill-corejs3@0.11.1: - resolution: {integrity: sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==} + babel-plugin-polyfill-corejs3@0.13.0: + resolution: {integrity: sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -5150,11 +5270,16 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - bare-events@2.6.1: - resolution: {integrity: sha512-AuTJkq9XmE6Vk0FJVNq5QxETrSA/vKHarWVBG5l/JbdCL1prJemiyJqUS0jrlXO0MftuPq4m3YVYhoNc5+aE/g==} + bare-events@2.8.1: + resolution: {integrity: sha512-oxSAxTS1hRfnyit2CL5QpAOS5ixfBjj6ex3yTNvXyY/kE719jQ/IjuESJBK2w5v4wwQRAHGseVJXx9QBYOtFGQ==} + peerDependencies: + bare-abort-controller: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true - bare-fs@4.2.0: - resolution: {integrity: sha512-oRfrw7gwwBVAWx9S5zPMo2iiOjxyiZE12DmblmMQREgcogbNO0AFaZ+QBxxkEXiPspcpvO/Qtqn8LabUx4uYXg==} + bare-fs@4.5.0: + resolution: {integrity: sha512-GljgCjeupKZJNetTqxKaQArLK10vpmK28or0+RwWjEl5Rk+/xG3wkpmkv+WrcBm3q1BwHKlnhXzR8O37kcvkXQ==} engines: {bare: '>=1.16.0'} peerDependencies: bare-buffer: '*' @@ -5162,8 +5287,8 @@ packages: bare-buffer: optional: true - bare-os@3.6.1: - resolution: {integrity: sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==} + bare-os@3.6.2: + resolution: {integrity: sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==} engines: {bare: '>=1.14.0'} bare-path@3.0.0: @@ -5180,6 +5305,9 @@ packages: bare-events: optional: true + bare-url@2.3.1: + resolution: {integrity: sha512-v2yl0TnaZTdEnelkKtXZGnotiV6qATBlnNuUMrHl6v9Lmmrh9mw9RYyImPU7/4RahumSwQS1k2oKXcRfXcbjJw==} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -5187,9 +5315,13 @@ packages: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} engines: {node: ^4.5.0 || >= 5.9} - batch-cluster@13.0.0: - resolution: {integrity: sha512-EreW0Vi8TwovhYUHBXXRA5tthuU2ynGsZFlboyMJHCCUXYa2AjgwnE3ubBOJs2xJLcuXFJbi6c/8pH5+FVj8Og==} - engines: {node: '>=14'} + baseline-browser-mapping@2.8.20: + resolution: {integrity: sha512-JMWsdF+O8Orq3EMukbUN1QfbLK9mX2CkUmQBcW2T0s8OmdAUL5LLM/6wFwSrqXzlXB13yhyK9gTKS1rIizOduQ==} + hasBin: true + + batch-cluster@15.0.1: + resolution: {integrity: sha512-eUmh0ld1AUPKTEmdzwGF9QTSexXAyt9rA1F5zDfW1wUi3okA3Tal4NLdCeFI6aiKpBenQhR6NmK9bW9tBHTGPQ==} + engines: {node: '>=20'} batch@0.6.1: resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==} @@ -5256,8 +5388,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.25.3: - resolution: {integrity: sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==} + browserslist@4.27.0: + resolution: {integrity: sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -5265,6 +5397,9 @@ packages: resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} engines: {node: '>=8.0.0'} + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -5282,8 +5417,12 @@ packages: resolution: {integrity: sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==} engines: {node: '>=18.20'} - bullmq@5.58.5: - resolution: {integrity: sha512-0A6Qjxdn8j7aOcxfRZY798vO/aMuwvoZwfE6a9EOXHb1pzpBVAogsc/OfRWeUf+5wMBoYB5nthstnJo/zrQOeQ==} + bullmq@5.62.1: + resolution: {integrity: sha512-FiRxqSquGTf8W8l8OMczKfEFG0BEqJ5NzChwKZ4vbSpZSPFLSmmxXAQlW4imB1rZHnlc7sYq8o+Oy4JavoIEpQ==} + + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} @@ -5338,9 +5477,6 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} - call-me-maybe@1.0.2: - resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} - callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -5367,8 +5503,8 @@ packages: caniuse-api@3.0.0: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} - caniuse-lite@1.0.30001735: - resolution: {integrity: sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==} + caniuse-lite@1.0.30001751: + resolution: {integrity: sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==} canvas@2.11.2: resolution: {integrity: sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==} @@ -5411,10 +5547,6 @@ packages: chardet@2.1.0: resolution: {integrity: sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==} - charset@1.0.1: - resolution: {integrity: sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg==} - engines: {node: '>=4.0.0'} - check-error@2.1.1: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} @@ -5532,10 +5664,6 @@ packages: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} - clsx@1.2.1: - resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} - engines: {node: '>=6'} - clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -5554,17 +5682,10 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - color-string@1.9.1: - resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} - color-support@1.1.3: resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} hasBin: true - color@4.2.3: - resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} - engines: {node: '>=12.5.0'} - colord@2.9.3: resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} @@ -5620,8 +5741,8 @@ packages: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} - comment-json@4.2.5: - resolution: {integrity: sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==} + comment-json@4.4.1: + resolution: {integrity: sha512-r1To31BQD5060QdkC+Iheai7gHwoSZobzunqkf2/kQ6xIAfJyrKNAFUwdKvkK7Qgu7pVTKQEa7ok7Ed3ycAJgg==} engines: {node: '>= 6'} common-path-prefix@3.0.0: @@ -5642,12 +5763,6 @@ packages: resolution: {integrity: sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==} engines: {node: '>= 0.8.0'} - compute-gcd@1.2.1: - resolution: {integrity: sha512-TwMbxBNz0l71+8Sc4czv13h4kEqnchV9igQZBi6QUaz09dnz13juGnnaWWJTRsP3brxOoxeB4SA2WELLw1hCtg==} - - compute-lcm@1.1.2: - resolution: {integrity: sha512-OFNPdQAXnQhDSKioX8/XYT6sdUlXwpeMjfd6ApxMJfyZ4GxmLR1xvMERctlYhlHwIiz6CSpBc2+qYKjHGZw4TQ==} - concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -5729,24 +5844,20 @@ packages: resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==} engines: {node: '>= 0.8'} - copy-text-to-clipboard@3.2.0: - resolution: {integrity: sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q==} - engines: {node: '>=12'} - copy-webpack-plugin@11.0.0: resolution: {integrity: sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==} engines: {node: '>= 14.15.0'} peerDependencies: webpack: ^5.1.0 - core-js-compat@3.45.0: - resolution: {integrity: sha512-gRoVMBawZg0OnxaVv3zpqLLxaHmsubEGyTnqdpI/CEBvX4JadI1dMSHxagThprYRtSVbuQxvi6iUatdPxohHpA==} + core-js-compat@3.46.0: + resolution: {integrity: sha512-p9hObIIEENxSV8xIu+V68JjSeARg6UVMG5mR+JEUguG3sI6MsiS1njz2jHmyJDvA+8jX/sytkBHup6kxhM9law==} - core-js-pure@3.43.0: - resolution: {integrity: sha512-i/AgxU2+A+BbJdMxh3v7/vxi2SbFqxiFmg6VsDwYB4jkucrd1BZNA9a9gphC0fYMG5IBSgQcbQnk865VCLe7xA==} + core-js-pure@3.46.0: + resolution: {integrity: sha512-NMCW30bHNofuhwLhYPt66OLOKTMbOhgTTatKVbaQC3KRHpTCiRIBYvtshr+NBYSnBxwAFhjW/RfJ0XbIjS16rw==} - core-js@3.43.0: - resolution: {integrity: sha512-N6wEbTTZSYOY2rYAn85CuvWWkCK6QweMn7/4Nr3w+gDBeBhk/x4EJeY6FPo4QzDoJZxVTv8U7CMvgWk6pOHHqA==} + core-js@3.46.0: + resolution: {integrity: sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA==} core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -5781,17 +5892,14 @@ packages: resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} engines: {node: '>=12.0.0'} - cron@4.3.0: - resolution: {integrity: sha512-ciiYNLfSlF9MrDqnbMdRWFiA6oizSF7kA1osPP9lRzNu0Uu+AWog1UKy7SkckiDY2irrNjeO6qLyKnXC8oxmrw==} + cron@4.3.3: + resolution: {integrity: sha512-B/CJj5yL3sjtlun6RtYHvoSB26EmQ2NUmhq9ZiJSyKIM4K/fqfh9aelDFlIayD2YMeFZqWLi9hHV+c+pq2Djkw==} engines: {node: '>=18.x'} cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - crypto-js@4.2.0: - resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} - crypto-random-string@4.0.0: resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==} engines: {node: '>=12'} @@ -5802,14 +5910,14 @@ packages: peerDependencies: postcss: ^8.4 - css-declaration-sorter@7.2.0: - resolution: {integrity: sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==} + css-declaration-sorter@7.3.0: + resolution: {integrity: sha512-LQF6N/3vkAMYF4xoHLJfG718HRJh34Z8BnNhd6bosOMIVjMlhuZK5++oZa3uYAgrI5+7x2o27gUqTR2U/KjUOQ==} engines: {node: ^14 || ^16 || >=18} peerDependencies: postcss: ^8.0.9 - css-has-pseudo@7.0.2: - resolution: {integrity: sha512-nzol/h+E0bId46Kn2dQH5VElaknX2Sr0hFuB/1EomdC7j+OISt2ZzK7EHX9DZDY53WbIVAR7FYKSO2XnSf07MQ==} + css-has-pseudo@7.0.3: + resolution: {integrity: sha512-oG+vKuGyqe/xvEMoxAQrhi7uY16deJR3i7wwhBerVrGQKSqUC5GiOVxTpM9F9B9hw0J+eKeOWLH7E9gZ1Dr5rA==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -5884,8 +5992,8 @@ packages: csscolorparser@1.0.3: resolution: {integrity: sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==} - cssdb@8.3.1: - resolution: {integrity: sha512-XnDRQMXucLueX92yDe0LPKupXetWoFOgawr4O4X41l5TltgK2NVbJJVDnnOywDYfW1sTJ28AcXGKOqdRKwCcmQ==} + cssdb@8.4.2: + resolution: {integrity: sha512-PzjkRkRUS+IHDJohtxkIczlxPPZqRo0nXplsYXOMBRPjcVRjj1W4DfvRgshUYTVuUigU7ptVYkFJQ7abUB0nyg==} cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} @@ -6026,9 +6134,13 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} - default-gateway@6.0.3: - resolution: {integrity: sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==} - engines: {node: '>= 10'} + default-browser-id@5.0.0: + resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==} + engines: {node: '>=18'} + + default-browser@5.2.1: + resolution: {integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==} + engines: {node: '>=18'} defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} @@ -6045,6 +6157,10 @@ packages: resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} engines: {node: '>=8'} + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + define-properties@1.2.1: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} @@ -6079,24 +6195,20 @@ packages: detect-europe-js@0.1.2: resolution: {integrity: sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow==} - detect-libc@2.1.0: - resolution: {integrity: sha512-vEtk+OcP7VBRtQZ1EJ3bdgzSfBjgnEalLTp5zjJrS+2Z1w2KZly4SBdac/WDU3hhsNAZ9E8SC96ME4Ey8MZ7cg==} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} detect-node@2.1.0: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} - detect-package-manager@3.0.2: - resolution: {integrity: sha512-8JFjJHutStYrfWwzfretQoyNGoZVW1Fsrp4JO9spa7h/fBfwgTMEIy4/LBzRDGsxwVPHU0q+T9YvwLDJoOApLQ==} - engines: {node: '>=12'} - detect-port@1.6.1: resolution: {integrity: sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q==} engines: {node: '>= 4.0.0'} hasBin: true - devalue@5.3.2: - resolution: {integrity: sha512-UDsjUbpQn9kvm68slnrs+mfxwFkIflOhkanmyabZ8zOYk8SMEIbJ3TK+88g70hSIeytu4y18f0z/hYHMTrXIWw==} + devalue@5.4.2: + resolution: {integrity: sha512-MwPZTKEPK2k8Qgfmqrd48ZKVvzSQjgW0lXLxiIBA8dQjtf/6mw6pggHNLcyDKyf+fI6eXxlQwPsfaCMTU5U+Bw==} devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} @@ -6131,16 +6243,16 @@ packages: resolution: {integrity: sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==} engines: {node: '>=6'} - docker-compose@1.2.0: - resolution: {integrity: sha512-wIU1eHk3Op7dFgELRdmOYlPYS4gP8HhH1ZmZa13QZF59y0fblzFDFmKPhyc05phCy2hze9OEvNZAsoljrs+72w==} + docker-compose@1.3.0: + resolution: {integrity: sha512-7Gevk/5eGD50+eMD+XDnFnOrruFkL0kSd7jEG4cjmqweDSUhB7i0g8is/nBdVpl+Bx338SqIB2GLKm32M+Vs6g==} engines: {node: '>= 6.0.0'} docker-modem@5.0.6: resolution: {integrity: sha512-ens7BiayssQz/uAxGzH8zGXCtiV24rRWXdjNha5V4zSOcxmAZsfGVm/PPFbwQdqEkDnhG+SyR9E3zSHUbOKXBQ==} engines: {node: '>= 8.0'} - dockerode@4.0.7: - resolution: {integrity: sha512-R+rgrSRTRdU5mH14PZTCPZtW/zw3HDWNTS/1ZAQpL/5Upe/ye5K9WQkIysu4wBoiMwKynsz0a8qWuGsHgEvSAA==} + dockerode@4.0.9: + resolution: {integrity: sha512-iND4mcOWhPaCNh54WmK/KoSb35AFqPAUWFMffTQcp52uQt36b5uNwEJTSXntJZBbeGad72Crbi/hvDIv6us/6Q==} engines: {node: '>= 8.0'} docusaurus-lunr-search@3.6.0: @@ -6151,31 +6263,6 @@ packages: react: ^16.8.4 || ^17 || ^18 || ^19 react-dom: ^16.8.4 || ^17 || ^18 || ^19 - docusaurus-plugin-openapi@0.7.6: - resolution: {integrity: sha512-LR8DI0gO9WFy8K+r0xrVgqDkKKA9zQtDgOnX9CatP3I3Oz5lKegfTJM2fVUIp5m25elzHL+vVKNHS12Jg7sWVA==} - engines: {node: '>=18'} - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - - docusaurus-plugin-proxy@0.7.6: - resolution: {integrity: sha512-MgjzMEsQOHMljwQGglXXoGjQvs0v1DklhRgzqNLKFwpHB9xLWJZ0KQ3GgbPerW/2vy8tWGJeVhKHy5cPrmweUw==} - engines: {node: '>=14'} - - docusaurus-preset-openapi@0.7.6: - resolution: {integrity: sha512-QnArH/3X0lePB7667FyNK3EeTS8ZP8V2PQxz5m+3BMO2kIzdXDwfTIQ37boB0BTqsDfUE0yCWTVjB0W/BA1UXA==} - engines: {node: '>=18'} - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - - docusaurus-theme-openapi@0.7.6: - resolution: {integrity: sha512-euoEh8tYX/ssQcMQxBOxt3wPttz3zvPu0l5lSe6exiIwMrORB4O2b8XRB7fVa/awF7xzdIkKHMH55uc5zVOKYA==} - engines: {node: '>=18'} - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} @@ -6223,8 +6310,8 @@ packages: resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} engines: {node: '>=10'} - dotenv@17.2.2: - resolution: {integrity: sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==} + dotenv@17.2.3: + resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} engines: {node: '>=12'} dunder-proto@1.0.1: @@ -6243,14 +6330,17 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.207: - resolution: {integrity: sha512-mryFrrL/GXDTmAtIVMVf+eIXM09BBPlO5IQ7lUyKmK8d+A4VpRGG+M3ofoVef6qyF8s60rJei8ymlJxjUA8Faw==} + electron-to-chromium@1.5.243: + resolution: {integrity: sha512-ZCphxFW3Q1TVhcgS9blfut1PX8lusVi2SvXQgmEEnK4TCmE1JhH2JkjJN+DNt0pJJwfBri5AROBnz2b/C+YU9g==} - emoji-regex@10.5.0: - resolution: {integrity: sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==} + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -6344,9 +6434,6 @@ packages: es6-iterator@2.0.3: resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==} - es6-promise@3.3.1: - resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} - es6-symbol@3.1.4: resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==} engines: {node: '>=0.12'} @@ -6365,8 +6452,8 @@ packages: engines: {node: '>=12'} hasBin: true - esbuild@0.25.9: - resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} engines: {node: '>=18'} hasBin: true @@ -6404,12 +6491,6 @@ packages: peerDependencies: eslint: '>=7.0.0' - eslint-p@0.26.0: - resolution: {integrity: sha512-Y5bDWKIFEUE7dZrbBbq5SiHWadYC4h3+Q+xBAUNNAqU1VMokleoGGfK92Qsmi+EBOLUBbxrtOCND5BSqQn8NaQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - deprecated: ESLint has built-in support for multithread linting now. This package is no longer needed. - hasBin: true - eslint-plugin-compat@6.0.2: resolution: {integrity: sha512-1ME+YfJjmOz1blH0nPZpHgjMGK4kjgEeoYqGCqoBPQ/mGu/dJzdoP0f1C8H2jcWZjzhZjAMccbM/VdXhPORIfA==} engines: {node: '>=18.x'} @@ -6430,8 +6511,8 @@ packages: eslint-config-prettier: optional: true - eslint-plugin-svelte@3.12.3: - resolution: {integrity: sha512-YVNhKsHZeXVvsjZcSMjnce9gO31frICu453p5JjFiXNszHoG9k8WvsA/LAoLi4K8T69G7DIrgg1AqasDJLpgoQ==} + eslint-plugin-svelte@3.12.5: + resolution: {integrity: sha512-4KRG84eAHQfYd9OjZ1K7sCHy0nox+9KwT+s5WCCku3jTim5RV4tVENob274nCwIaApXsYPKAUAZFBxKZ3Wyfjw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.1 || ^9.0.0 @@ -6446,6 +6527,12 @@ packages: peerDependencies: eslint: '>=9.29.0' + eslint-plugin-unicorn@61.0.2: + resolution: {integrity: sha512-zLihukvneYT7f74GNbVJXfWIiNQmkc/a9vYBTE4qPkQZswolWNdu+Wsp9sIXno1JOzdn6OUwLPd19ekXVkahRA==} + engines: {node: ^20.10.0 || >=21.0.0} + peerDependencies: + eslint: '>=9.29.0' + eslint-scope@5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} @@ -6462,8 +6549,8 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.35.0: - resolution: {integrity: sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==} + eslint@9.38.0: + resolution: {integrity: sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -6522,8 +6609,8 @@ packages: estree-util-to-js@2.0.0: resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} - estree-util-value-to-estree@3.4.0: - resolution: {integrity: sha512-Zlp+gxis+gCfK12d3Srl2PdX2ybsEA8ZYy6vQGVQTNNYLEGRQQ56XB64bjemN8kxIKXP1nC9ip4Z+ILy9LGzvQ==} + estree-util-value-to-estree@3.5.0: + resolution: {integrity: sha512-aMV56R27Gv3QmfmF1MY12GWkGzzeAezAX+UplqHVASfjc9wNzI/X6hC0S9oxq61WT4aQesLGslWP9tKk6ghRZQ==} estree-util-visit@2.0.0: resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} @@ -6542,9 +6629,9 @@ packages: resolution: {integrity: sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g==} engines: {node: '>=6.0.0'} - eta@3.5.0: - resolution: {integrity: sha512-e3x3FBvGzeCIHhF+zhK8FZA2vC5uFn6b4HJjegUbIWrDb4mJ7JjTGMJY9VGIbRVpmSwHopNiaJibhjIr+HfLug==} - engines: {node: '>=6.0.0'} + eta@4.0.1: + resolution: {integrity: sha512-0h0oBEsF6qAJU7eu9ztvJoTo8D2PAq/4FvXVIQA1fek3WOTe6KPsVJycekG1+g1N6mfpblkheoGwaUhMtnlH4A==} + engines: {node: '>=20'} etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} @@ -6564,31 +6651,40 @@ packages: eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + events-universal@1.0.1: + resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} + events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + eventsource-parser@3.0.6: + resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} + engines: {node: '>=18.0.0'} + execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} - exiftool-vendored.exe@13.0.0: - resolution: {integrity: sha512-4zAMuFGgxZkOoyQIzZMHv1HlvgyJK3AkNqjAgm8A8V0UmOZO7yv3pH49cDV1OduzFJqgs6yQ6eG4OGydhKtxlg==} + exiftool-vendored.exe@13.41.0: + resolution: {integrity: sha512-7XG0PjZCm8HVsVUQAD4b/eBtvYBuGkySf2qslqHlnSR6jU1xoD1AgEprb2bCPqwhw0Jn3xzZoo/ihDo4F6fMyA==} os: [win32] - exiftool-vendored.pl@13.0.1: - resolution: {integrity: sha512-+BRRzjselpWudKR0ltAW5SUt9T82D+gzQN8DdOQUgnSVWWp7oLCeTGBRptbQz+436Ihn/mPzmo/xnf0cv/Qw1A==} + exiftool-vendored.pl@13.41.0: + resolution: {integrity: sha512-JqqRuB8TggIOC983oTnOunB/baseGYw8XCkn7ylFGOmEv7oTQAK3uUTZV76vXE1X6c5H6IdHYt0abSgi8Kzc4g==} os: ['!win32'] + hasBin: true - exiftool-vendored@28.8.0: - resolution: {integrity: sha512-R7tirJLr9fWuH9JS/KFFLB+O7jNGKuPXGxREc6YybYangEudGb+X8ERsYXk9AifMiAWh/2agNfbgkbcQcF+MxA==} + exiftool-vendored@31.3.0: + resolution: {integrity: sha512-JQeyRvh7cV81fm9eKej2btdVh2z2Ak/sx89c4OCykeQnhnI81hk9TTraBtborYA+WcLM20cwYMPmpaW/sMy5Qw==} + engines: {node: '>=20.0.0'} expect-type@1.2.1: resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} engines: {node: '>=12.0.0'} - exponential-backoff@3.1.2: - resolution: {integrity: sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==} + exponential-backoff@3.1.3: + resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} express@4.21.2: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} @@ -6644,8 +6740,8 @@ packages: fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - fast-uri@3.0.6: - resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==} + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} fast-xml-parser@5.2.5: resolution: {integrity: sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==} @@ -6698,10 +6794,6 @@ packages: resolution: {integrity: sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg==} engines: {node: '>=20'} - file-type@3.9.0: - resolution: {integrity: sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==} - engines: {node: '>=0.10.0'} - fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -6750,8 +6842,8 @@ packages: engines: {node: '>=18'} deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - follow-redirects@1.15.9: - resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -6759,9 +6851,6 @@ packages: debug: optional: true - foreach@2.0.6: - resolution: {integrity: sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==} - foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -6785,9 +6874,6 @@ packages: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} engines: {node: '>=0.4.x'} - formidable@2.1.5: - resolution: {integrity: sha512-Oz5Hwvwak/DCaXVVUtPn4oLMLLy1CdclLKO1LFgU7XzDpVMUU5UjlSLpGMocyQNNk8F6IJW9M/YdooSn2MRI+Q==} - formidable@3.5.4: resolution: {integrity: sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==} engines: {node: '>=14.0.0'} @@ -6817,8 +6903,8 @@ packages: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} - fs-extra@11.3.0: - resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==} + fs-extra@11.3.2: + resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==} engines: {node: '>=14.14'} fs-minipass@2.1.0: @@ -6901,10 +6987,6 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} - get-stdin@5.0.1: - resolution: {integrity: sha512-jZV7n6jGE3Gt7fgSTJoz91Ak5MuTLwMwkoYdjxuJ/AmjIsE1UC03y/IWkZCQGEvVNS9qoRNwy5BCqxImv0FVeA==} - engines: {node: '>=0.12.0'} - get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} @@ -6923,6 +7005,12 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob-to-regex.js@1.2.0: + resolution: {integrity: sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} @@ -6943,10 +7031,6 @@ packages: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} engines: {node: '>=10'} - globals@11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} - globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -6990,9 +7074,6 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - graphlib@2.1.8: - resolution: {integrity: sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==} - gray-matter@4.0.3: resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} engines: {node: '>=6.0'} @@ -7012,18 +7093,14 @@ packages: engines: {node: '>=0.4.7'} hasBin: true - happy-dom@18.0.1: - resolution: {integrity: sha512-qn+rKOW7KWpVTtgIUi6RVmTBZJSe2k0Db0vh1f7CWrWclkkc7/Q+FrOfkZIb2eiErLyqu5AXEzE7XthO9JVxRA==} + happy-dom@20.0.10: + resolution: {integrity: sha512-6umCCHcjQrhP5oXhrHQQvLB0bwb1UzHAHdsXy+FjtKoYjUhmNZsQL8NivwM1vDvNEChJabVrUYxUnp/ZdYmy2g==} engines: {node: '>=20.0.0'} has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - has-own-prop@2.0.0: - resolution: {integrity: sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==} - engines: {node: '>=8'} - has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} @@ -7061,9 +7138,6 @@ packages: hast-util-parse-selector@2.2.5: resolution: {integrity: sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==} - hast-util-parse-selector@3.1.1: - resolution: {integrity: sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==} - hast-util-parse-selector@4.0.0: resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} @@ -7097,9 +7171,6 @@ packages: hastscript@6.0.0: resolution: {integrity: sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==} - hastscript@7.2.0: - resolution: {integrity: sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==} - hastscript@9.0.1: resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} @@ -7107,6 +7178,10 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true + highlight.js@11.11.1: + resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} + engines: {node: '>=12.0.0'} + history@4.10.1: resolution: {integrity: sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==} @@ -7128,9 +7203,6 @@ packages: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} - html-entities@2.6.0: - resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==} - html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -7155,8 +7227,8 @@ packages: html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} - html-webpack-plugin@5.6.3: - resolution: {integrity: sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==} + html-webpack-plugin@5.6.4: + resolution: {integrity: sha512-V/PZeWsqhfpE27nKeX9EO2sbR+D17A+tLf6qU+ht66jdUsN0QLKJN27Z+1+gHrVMKgndBahes0PU6rRihDgHTw==} engines: {node: '>=10.13.0'} peerDependencies: '@rspack/core': 0.x || 1.x @@ -7219,12 +7291,6 @@ packages: resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} engines: {node: '>=8.0.0'} - http-reasons@0.1.0: - resolution: {integrity: sha512-P6kYh0lKZ+y29T2Gqz+RlC9WBLhKe8kDmcJ+A+611jFfxdPsbMRQ5aNmFRM3lENqFkK+HTTL+tlQviAiv0AbLQ==} - - http2-client@1.3.5: - resolution: {integrity: sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==} - http2-wrapper@2.2.1: resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==} engines: {node: '>=10.19.0'} @@ -7241,6 +7307,10 @@ packages: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} + hyperdyperid@1.2.0: + resolution: {integrity: sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==} + engines: {node: '>=10.18'} + i18n-iso-countries@7.14.0: resolution: {integrity: sha512-nXHJZYtNrfsi1UQbyRqm3Gou431elgLjKl//CYlnBGt5aTWdRPH1PiS2T/p/n8Q8LnqYqzQJik3Q7mkwvLokeg==} engines: {node: '>= 12'} @@ -7286,15 +7356,12 @@ packages: immediate@3.3.0: resolution: {integrity: sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==} - immer@9.0.21: - resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==} - import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} - import-in-the-middle@1.14.2: - resolution: {integrity: sha512-5tCuY9BV8ujfOpwtAGgsTx9CGUapcFMEEyByLv1B+v2+6DhAcw+Zr0nhQT7uwaZ7DiourxFEscghOR8e1aPLQw==} + import-in-the-middle@2.0.0: + resolution: {integrity: sha512-yNZhyQYqXpkT0AKq3F3KLasUSK4fHvebNH5hOsKQw2dhGSALvQ4U0BqUc5suziKvydO5u5hgN2hy1RJaho8U5A==} import-lazy@4.0.0: resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} @@ -7344,18 +7411,14 @@ packages: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} - interpret@1.4.0: - resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} - engines: {node: '>= 0.10'} - - intl-messageformat@10.7.16: - resolution: {integrity: sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug==} + intl-messageformat@10.7.18: + resolution: {integrity: sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==} invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} - ioredis@5.7.0: - resolution: {integrity: sha512-NUcA93i1lukyXU+riqEyPtSEkyFq8tX90uL659J+qpCZ3rEdViB/APC58oAhIh3+bJln2hzdlZbBZsGNrlsR8g==} + ioredis@5.8.2: + resolution: {integrity: sha512-C6uC+kleiIMmjViJINWk80sOQw5lEzse1ZmvD+S/s8p8CWapftSaC+kocGTx6xrbrJ4WmYQGC08ffHLr6ToR6Q==} engines: {node: '>=12.22.0'} ip-address@10.0.1: @@ -7379,9 +7442,6 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - is-arrayish@0.3.2: - resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} - is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -7410,6 +7470,11 @@ packages: engines: {node: '>=8'} hasBin: true + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + is-extendable@0.1.1: resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} engines: {node: '>=0.10.0'} @@ -7429,6 +7494,11 @@ packages: is-hexadecimal@2.0.1: resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + is-installed-globally@0.4.0: resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} engines: {node: '>=10'} @@ -7441,8 +7511,12 @@ packages: resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} engines: {node: '>=12'} - is-npm@6.0.0: - resolution: {integrity: sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==} + is-network-error@1.3.0: + resolution: {integrity: sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==} + engines: {node: '>=16'} + + is-npm@6.1.0: + resolution: {integrity: sha512-O2z4/kNgyjhQwVR1Wpkbfc19JIhggF97NZNCpWTnjH7kVcZMUrnut9XSN7txI7VdyIYk5ZatOq3zvSuWpU8hoA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} is-number@7.0.0: @@ -7523,6 +7597,10 @@ packages: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} + is-wsl@3.1.0: + resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} + engines: {node: '>=16'} + is-yarn-global@0.4.1: resolution: {integrity: sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==} engines: {node: '>=12'} @@ -7591,8 +7669,8 @@ packages: resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} hasBin: true - jiti@2.5.1: - resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true joi@17.13.3: @@ -7652,31 +7730,15 @@ packages: json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - json-pointer@0.6.2: - resolution: {integrity: sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==} - - json-refs@3.0.15: - resolution: {integrity: sha512-0vOQd9eLNBL18EGl5yYaO44GhixmImes2wiYn9Z3sag3QnehWrYWlB9AFtMxCL2Bj3fyxgDYkxGFEU/chlYssw==} - engines: {node: '>=0.8'} - hasBin: true - - json-schema-compare@0.2.2: - resolution: {integrity: sha512-c4WYmDKyJXhs7WWvAWm3uIYnfyWFoIp+JEoX34rctVvEkMYCPGhXtvmFFXiffBbxfZsvQ0RNnV5H7GvDF5HCqQ==} - - json-schema-merge-allof@0.8.1: - resolution: {integrity: sha512-CTUKmIlPJbsWfzRRnOXz+0MjIqvnleIXwFTzz+t9T86HnYX/Rozria6ZVGLktAU9e+NygNljveP+yxqtQp/Q4w==} - engines: {node: '>=12.0.0'} - - json-schema-resolve-allof@1.5.0: - resolution: {integrity: sha512-Jgn6BQGSLDp3D7bTYrmCbP/p7SRFz5BfpeEJ9A7sXuVADMc14aaDN1a49zqk9D26wwJlcNvjRpT63cz1VgFZeg==} - hasBin: true - json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -7694,12 +7756,22 @@ packages: jsonfile@6.2.0: resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + just-compare@2.3.0: resolution: {integrity: sha512-6shoR7HDT+fzfL3gBahx1jZG3hWLrhPAf+l7nCwahDdT9XDtosB9kIF0ZrzUp5QY8dJWfQVr5rnsPqsbvflDzg==} justified-layout@4.1.0: resolution: {integrity: sha512-M5FimNMXgiOYerVRGsXZ2YK9YNCaTtwtYp7Hb2308U1Q9TXXHx5G0p08mcVR5O53qf8bWY4NJcPBxE6zuayXSg==} + jwa@1.4.2: + resolution: {integrity: sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==} + + jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + kdbush@3.0.0: resolution: {integrity: sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==} @@ -7709,6 +7781,7 @@ packages: keygrip@1.1.0: resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} engines: {node: '>= 0.6'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -7731,15 +7804,19 @@ packages: koa-compose@4.1.0: resolution: {integrity: sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==} - koa@3.0.1: - resolution: {integrity: sha512-oDxVkRwPOHhGlxKIDiDB2h+/l05QPtefD7nSqRgDfZt8P+QVYFWjfeK8jANf5O2YXjk8egd7KntvXKYx82wOag==} + koa@3.1.1: + resolution: {integrity: sha512-KDDuvpfqSK0ZKEO2gCPedNjl5wYpfj+HNiuVRlbhd1A88S3M0ySkdf2V/EJ4NWt5dwh5PXCdcenrKK2IQJAxsg==} engines: {node: '>= 18'} - kysely-postgres-js@2.0.0: - resolution: {integrity: sha512-R1tWx6/x3tSatWvsmbHJxpBZYhNNxcnMw52QzZaHKg7ZOWtHib4iZyEaw4gb2hNKVctWQ3jfMxZT/ZaEMK6kBQ==} + kysely-postgres-js@3.0.0: + resolution: {integrity: sha512-o2t/xNSYJQDW6rVGGFPXKmZ0BEz2dGn66c2B+cO/k9ZNcU2qPWPycQPQ+B+P2MBXbKYq0xV9BZmFIvkUrmFWAQ==} + engines: {bun: '>=1.2', node: '>=20'} peerDependencies: kysely: '>= 0.24.0 < 1' - postgres: '>= 3.4.0 < 4' + postgres: ^3.4.0 + peerDependenciesMeta: + postgres: + optional: true kysely@0.28.2: resolution: {integrity: sha512-4YAVLoF0Sf0UTqlhgQMFU9iQECdah7n+13ANkiuVfRvlK+uI0Etbgd7bVP36dKlG+NXWbhGua8vnGt+sdhvT7A==} @@ -7749,8 +7826,8 @@ packages: resolution: {integrity: sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==} engines: {node: '>=14.16'} - launch-editor@2.10.0: - resolution: {integrity: sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA==} + launch-editor@2.12.0: + resolution: {integrity: sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==} lazystream@1.0.1: resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} @@ -7770,68 +7847,74 @@ packages: libphonenumber-js@1.12.9: resolution: {integrity: sha512-VWwAdNeJgN7jFOD+wN4qx83DTPMVPPAUyx9/TUkBXKLiNkuWWk6anV0439tgdtwaJDrEdqkvdN22iA6J4bUCZg==} - lightningcss-darwin-arm64@1.30.1: - resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} + lightningcss-android-arm64@1.30.2: + resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.30.2: + resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] - lightningcss-darwin-x64@1.30.1: - resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==} + lightningcss-darwin-x64@1.30.2: + resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] - lightningcss-freebsd-x64@1.30.1: - resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==} + lightningcss-freebsd-x64@1.30.2: + resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] - lightningcss-linux-arm-gnueabihf@1.30.1: - resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==} + lightningcss-linux-arm-gnueabihf@1.30.2: + resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] - lightningcss-linux-arm64-gnu@1.30.1: - resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==} + lightningcss-linux-arm64-gnu@1.30.2: + resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - lightningcss-linux-arm64-musl@1.30.1: - resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} + lightningcss-linux-arm64-musl@1.30.2: + resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - lightningcss-linux-x64-gnu@1.30.1: - resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} + lightningcss-linux-x64-gnu@1.30.2: + resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - lightningcss-linux-x64-musl@1.30.1: - resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} + lightningcss-linux-x64-musl@1.30.2: + resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - lightningcss-win32-arm64-msvc@1.30.1: - resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} + lightningcss-win32-arm64-msvc@1.30.2: + resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] - lightningcss-win32-x64-msvc@1.30.1: - resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==} + lightningcss-win32-x64-msvc@1.30.2: + resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] - lightningcss@1.30.1: - resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} + lightningcss@1.30.2: + resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} engines: {node: '>= 12.0.0'} lilconfig@2.1.0: @@ -7845,20 +7928,16 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - liquid-json@0.3.1: - resolution: {integrity: sha512-wUayTU8MS827Dam6MxgD72Ui+KOSF+u/eIqpatOtjnvgJ0+mnDq33uC2M7J0tPK+upe/DpUAuK4JUU89iBoNKQ==} - engines: {node: '>=4'} - - load-esm@1.0.2: - resolution: {integrity: sha512-nVAvWk/jeyrWyXEAs84mpQCYccxRqgKY4OznLuJhJCa0XsPSfdOIr2zvBZEj3IHEHbX97jjscKRRV539bW0Gpw==} + load-esm@1.0.3: + resolution: {integrity: sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==} engines: {node: '>=13.2.0'} load-tsconfig@0.2.5: resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - loader-runner@4.3.0: - resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} + loader-runner@4.3.1: + resolution: {integrity: sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==} engines: {node: '>=6.11.5'} loader-utils@2.0.4: @@ -7892,15 +7971,36 @@ packages: lodash.defaults@4.2.0: resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + lodash.isarguments@3.1.0: resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + lodash.memoize@4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash.uniq@4.5.0: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} @@ -7958,10 +8058,6 @@ packages: lunr@2.3.9: resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} - luxon@3.6.1: - resolution: {integrity: sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==} - engines: {node: '>=12'} - luxon@3.7.2: resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==} engines: {node: '>=12'} @@ -7973,8 +8069,8 @@ packages: magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} - magic-string@0.30.19: - resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} magicast@0.3.5: resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} @@ -7995,8 +8091,8 @@ packages: resolution: {integrity: sha512-p8lJFEiqmEQlyv+DQxFAOG/XPWN0Wp7j/Psq93Zywz7qt9CcUKFYDBOoOEKzqe6gudHVJY8/Bhqw6VDpX2lSBg==} engines: {node: '>=6.4.0'} - maplibre-gl@5.7.1: - resolution: {integrity: sha512-iCOQB6W/EGgQx8aU4SyfU5a5/GR2E+ELF92NMsqYfs3x+vnY+8mARmz4gor6XZHCz3tv19mnotVDRlRTMNKyGw==} + maplibre-gl@5.10.0: + resolution: {integrity: sha512-eLhlX8Fnpaoo7+uGqggZmXmZld6WrbzOJUPB7G8JB8XpminlTnrQTtXilMHduR8fsNVxrzD8yRRqEoajONc8LQ==} engines: {node: '>=16.14.0', npm: '>=8.1.0'} mark.js@8.11.1: @@ -8012,25 +8108,20 @@ packages: markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} - marked@11.2.0: - resolution: {integrity: sha512-HR0m3bvu0jAPYiIvLUUQtdg1g6D247//lvcekpHO1WMvbwDlwSkZAX9Lw4F4YHE1T0HaaNve0tuAWuV1UJ6vtw==} + marked@15.0.12: + resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==} engines: {node: '>= 18'} hasBin: true - marked@7.0.4: - resolution: {integrity: sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ==} - engines: {node: '>= 16'} + marked@16.4.1: + resolution: {integrity: sha512-ntROs7RaN3EvWfy3EZi14H4YxmT6A5YvywfhO+0pm+cH/dnSQRmdAmoFIc3B9aiwTehyk7pESH4ofyBY+V5hZg==} + engines: {node: '>= 20'} hasBin: true math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - md-to-react-email@5.0.5: - resolution: {integrity: sha512-OvAXqwq57uOk+WZqFFNCMZz8yDp8BD3WazW1wAKHUrPbbdr89K9DWS6JXY09vd9xNdPNeurI8DU/X4flcfaD8A==} - peerDependencies: - react: ^18.0 || ^19.0 - mdast-util-directive@3.1.0: resolution: {integrity: sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==} @@ -8103,6 +8194,9 @@ packages: resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} engines: {node: '>= 4.0.0'} + memfs@4.50.0: + resolution: {integrity: sha512-N0LUYQMUA1yS5tJKmMtU9yprPm6ZIg24yr/OVv/7t6q0kKDIho4cBbXRi1XKttUmNYDYgF/q45qrKE/UhGO0CA==} + memoizee@0.4.17: resolution: {integrity: sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==} engines: {node: '>=0.12'} @@ -8264,9 +8358,6 @@ packages: resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} engines: {node: '>= 0.6'} - mime-format@2.0.1: - resolution: {integrity: sha512-XxU3ngPbEnrYnNbIX+lYSaYg0M01v6p2ntd2YaFksTu0vayaw5OJvbdRyWs07EYRlLED5qadUZ+xo+XhOvFhwg==} - mime-types@2.1.18: resolution: {integrity: sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==} engines: {node: '>= 0.6'} @@ -8313,8 +8404,8 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - mini-css-extract-plugin@2.9.2: - resolution: {integrity: sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==} + mini-css-extract-plugin@2.9.4: + resolution: {integrity: sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ==} engines: {node: '>= 12.13.0'} peerDependencies: webpack: ^5.0.0 @@ -8322,8 +8413,8 @@ packages: minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} - minimatch@10.0.3: - resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} + minimatch@10.1.1: + resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} engines: {node: 20 || >=22} minimatch@3.1.2: @@ -8376,8 +8467,8 @@ packages: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} - minizlib@3.0.2: - resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} engines: {node: '>= 18'} mkdirp-classic@0.5.3: @@ -8396,11 +8487,6 @@ packages: engines: {node: '>=10'} hasBin: true - mkdirp@3.0.1: - resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} - engines: {node: '>=10'} - hasBin: true - mnemonist@0.40.3: resolution: {integrity: sha512-Vjyr90sJ23CKKH/qPAgUKicw/v6pRoamxIEDFOF8uSgFME7DqPRpHgRTejWVjkdGg5dXj0/NyxZHZ9bcjH+2uQ==} @@ -8411,9 +8497,6 @@ packages: module-details-from-path@1.0.4: resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} - monaco-editor@0.31.1: - resolution: {integrity: sha512-FYPwxGZAeP6mRRyrr5XTGHD9gRXVjy7GUzF4IPChnyt3fS5WrNxIkS8DNujWf6EQy0Zlzpxw8oTVE+mWI2/D1Q==} - moo@0.5.2: resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==} @@ -8467,14 +8550,11 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanoid@5.1.5: - resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==} + nanoid@5.1.6: + resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==} engines: {node: ^18 || >=20} hasBin: true - native-promise-only@0.8.1: - resolution: {integrity: sha512-zkVhZUA3y8mbz652WrL5x0fB0ehrBkulWT3TomAQ9iDtyXZvzKeEA6GPxAItBYeNYl5yngKRX612qHOhvMkDeg==} - natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -8497,12 +8577,8 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - neotraverse@0.6.15: - resolution: {integrity: sha512-HZpdkco+JeXq0G+WWpMJ4NsX3pqb5O7eR9uGz3FfoFt+LYzU8iRWp49nJtud6hsDoywM8tIrDo3gjgmOqJA8LA==} - engines: {node: '>= 10'} - - nest-commander@3.19.1: - resolution: {integrity: sha512-Pn6xcMeSnidlzZozNLnbe7P4TqXL7g0JuxqTAtJ89KT4S63ntJZKtRU6g/56h/aHUQa+m98j/c9OxBSduK7EPg==} + nest-commander@3.20.1: + resolution: {integrity: sha512-LRI7z6UlWy2vWyQR0PYnAXsaRyJvpfiuvOCmx2jk2kLXJH9+y/omPDl9NE3fq4WMaE0/AhviuUjA12eC/zDqXw==} peerDependencies: '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 @@ -8517,8 +8593,8 @@ packages: reflect-metadata: '*' rxjs: '>= 7' - nestjs-kysely@3.0.0: - resolution: {integrity: sha512-YA6tHBgXQYPNpMBPII2OvUOiaWjCCoh5pP5dUHirQcMUHxNFzInBL6MDk8y74rk2z/5IvAK9AUlsdPyJtToO6g==} + nestjs-kysely@3.1.2: + resolution: {integrity: sha512-m7gK37oza6yyRmOs6VWQ6Ri5wgqUnmWpMUByd1V8WLZcYCeVPgXGxzw3ABRWUIX/1kr+nZUhaAZRgAIhk2JXoQ==} peerDependencies: '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 @@ -8555,10 +8631,6 @@ packages: resolution: {integrity: sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==} engines: {node: '>=18'} - node-fetch-h2@2.3.0: - resolution: {integrity: sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==} - engines: {node: 4.x || >=6.0.0} - node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -8580,19 +8652,16 @@ packages: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true - node-gyp@11.4.2: - resolution: {integrity: sha512-3gD+6zsrLQH7DyYOUIutaauuXrcyxeTPyQuZQCQoNPZMHMMS5m4y0xclNpvYzoK3VNzuyxT6eF4mkIL4WSZ1eQ==} + node-gyp@11.5.0: + resolution: {integrity: sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==} engines: {node: ^18.17.0 || >=20.5.0} hasBin: true - node-readfiles@0.2.0: - resolution: {integrity: sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==} + node-releases@2.0.26: + resolution: {integrity: sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==} - node-releases@2.0.19: - resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} - - nodemailer@7.0.6: - resolution: {integrity: sha512-F44uVzgwo49xboqbFgBGkRaiMgtoBrBEWCVincJPK9+S9Adkzt/wXCLKbf7dxucmxfTI5gHGB+bEmdyzN6QKjw==} + nodemailer@7.0.10: + resolution: {integrity: sha512-Us/Se1WtT0ylXgNFfyFSx4LElllVLJXQjWi2Xz17xWw7amDKO2MLtFnVp1WACy7GkVGs+oBlRopVNUzlrGSw1w==} engines: {node: '>=6.0.0'} nopt@1.0.10: @@ -8617,8 +8686,8 @@ packages: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} - normalize-url@8.0.2: - resolution: {integrity: sha512-Ee/R3SyN4BuynXcnTaekmaVdbDAEiNrHqjQIA37mHU8G9pf7aaAD4ZX3XjBLo6rsdcxA/gtkcNYZLt30ACgynw==} + normalize-url@8.1.0: + resolution: {integrity: sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==} engines: {node: '>=14.16'} not@0.1.0: @@ -8655,28 +8724,8 @@ packages: engines: {node: ^14.16.0 || >=16.10.0} hasBin: true - oas-kit-common@1.0.8: - resolution: {integrity: sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==} - - oas-linter@3.2.2: - resolution: {integrity: sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==} - - oas-resolver-browser@2.5.6: - resolution: {integrity: sha512-Jw5elT/kwUJrnGaVuRWe1D7hmnYWB8rfDDjBnpQ+RYY/dzAewGXeTexXzt4fGEo6PUE4eqKqPWF79MZxxvMppA==} - hasBin: true - - oas-resolver@2.5.6: - resolution: {integrity: sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==} - hasBin: true - - oas-schema-walker@1.1.5: - resolution: {integrity: sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==} - - oas-validator@5.0.8: - resolution: {integrity: sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==} - - oauth4webapi@3.8.1: - resolution: {integrity: sha512-olkZDELNycOWQf9LrsELFq8n05LwJgV8UkrS0cburk6FOwf8GvLam+YB+Uj5Qvryee+vwWOfQVeI5Vm0MVg7SA==} + oauth4webapi@3.8.2: + resolution: {integrity: sha512-FzZZ+bht5X0FKe7Mwz3DAVAmlH1BV5blSak/lHMBKz0/EBMhX6B10GlQYI51+oRp8ObJaX0g6pXrAxZh5s8rjw==} object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} @@ -8704,8 +8753,8 @@ packages: obuf@1.1.2: resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} - oidc-provider@9.5.1: - resolution: {integrity: sha512-19Wa4bfz3reoudxrY7sF5SeQKxe5b3dY8hWzQdnBGS87rH0BoYoDDUDRTYciJMN3oI6S02C9xM6vuaHtoZ48eA==} + oidc-provider@9.5.2: + resolution: {integrity: sha512-lTI6U7ESvf34xuu9XPUfJX6sbIXuOsV2MUPT9YoT7dzDtajufc50iWSY2t/4xB/TuFQ4t0Q++JU2Cu270d11pg==} on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} @@ -8726,21 +8775,20 @@ packages: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} + open@10.2.0: + resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} + engines: {node: '>=18'} + open@8.4.2: resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} engines: {node: '>=12'} - openapi-to-postmanv2@4.25.0: - resolution: {integrity: sha512-sIymbkQby0gzxt2Yez8YKB6hoISEel05XwGwNrAhr6+vxJWXNxkmssQc/8UEtVkuJ9ZfUXLkip9PYACIpfPDWg==} - engines: {node: '>=8'} - hasBin: true - opener@1.5.2: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true - openid-client@6.8.0: - resolution: {integrity: sha512-oG1d1nAVhIIE+JSjLS+7E9wY1QOJpZltkzlJdbZ7kEn7Hp3hqur2TEeQ8gLOHoHkhbRAGZJKoOnEQcLOQJuIyg==} + openid-client@6.8.1: + resolution: {integrity: sha512-VoYT6enBo6Vj2j3Q5Ec0AezS+9YGzQo1f5Xc42lreMGlfP4ljiXPKVDvCADh+XHCV/bqPu/wWSiCVXbJKvrODw==} optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} @@ -8798,9 +8846,9 @@ packages: resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} engines: {node: '>=8'} - p-retry@4.6.2: - resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} - engines: {node: '>=8'} + p-retry@6.2.1: + resolution: {integrity: sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==} + engines: {node: '>=16.17'} p-timeout@3.2.0: resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} @@ -8856,9 +8904,6 @@ packages: pascal-case@3.1.2: resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} - path-browserify@1.0.1: - resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} - path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -8878,9 +8923,6 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - path-loader@1.0.12: - resolution: {integrity: sha512-n7oDG8B+k/p818uweWrOixY9/Dsr89o2TkCm6tOTex3fpdo2+BFDgR+KpB37mGKBRsBAlR8CIJMFN0OEy/7hIQ==} - path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -8904,10 +8946,6 @@ packages: path-to-regexp@3.3.0: resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} - path-to-regexp@8.2.0: - resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} - engines: {node: '>=16'} - path-to-regexp@8.3.0: resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} @@ -8915,9 +8953,6 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - path@0.12.7: - resolution: {integrity: sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==} - pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -9000,13 +9035,13 @@ packages: pkg-types@2.3.0: resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} - playwright-core@1.55.0: - resolution: {integrity: sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==} + playwright-core@1.56.1: + resolution: {integrity: sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==} engines: {node: '>=18'} hasBin: true - playwright@1.55.0: - resolution: {integrity: sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA==} + playwright@1.56.1: + resolution: {integrity: sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==} engines: {node: '>=18'} hasBin: true @@ -9049,8 +9084,8 @@ packages: peerDependencies: postcss: ^8.4.6 - postcss-color-functional-notation@7.0.10: - resolution: {integrity: sha512-k9qX+aXHBiLTRrWoCJuUFI6F1iF6QJQUXNVWJVSbqZgj57jDhBlOvD8gNUGl35tgqDivbGLhZeW3Ongz4feuKA==} + postcss-color-functional-notation@7.0.12: + resolution: {integrity: sha512-TLCW9fN5kvO/u38/uesdpbx3e8AkTYhMvDZYa9JpmImWuTE99bDQ7GU7hdOADIZsiI9/zuxfAJxny/khknp1Zw==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -9133,8 +9168,8 @@ packages: peerDependencies: postcss: ^8.4.31 - postcss-double-position-gradients@6.0.2: - resolution: {integrity: sha512-7qTqnL7nfLRyJK/AHSVrrXOuvDDzettC+wGoienURV8v2svNbu6zJC52ruZtHaO6mfcagFmuTGFdzRsJKB3k5Q==} + postcss-double-position-gradients@6.0.4: + resolution: {integrity: sha512-m6IKmxo7FxSP5nF2l63QbCC3r+bWpFUWmZXZf096WxG0m7Vl1Q1+ruFOhpdDRmKrRS+S3Jtk+TVk/7z0+BVK6g==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -9180,8 +9215,8 @@ packages: peerDependencies: postcss: ^8.4.21 - postcss-lab-function@7.0.10: - resolution: {integrity: sha512-tqs6TCEv9tC1Riq6fOzHuHcZyhg4k3gIAMB8GGY/zA1ssGdm6puHMVE7t75aOSoFg7UD2wyrFFhbldiCMyyFTQ==} + postcss-lab-function@7.0.12: + resolution: {integrity: sha512-tUcyRk1ZTPec3OuKFsqtRzW2Go5lehW29XA21lZ65XmzQkz43VY2tyWEC202F7W3mILOjw0voOiuxRGTsN+J9w==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -9198,16 +9233,22 @@ packages: ts-node: optional: true - postcss-load-config@4.0.2: - resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} - engines: {node: '>= 14'} + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} peerDependencies: + jiti: '>=1.21.0' postcss: '>=8.0.9' - ts-node: '>=9.0.0' + tsx: ^4.8.1 + yaml: ^2.4.2 peerDependenciesMeta: + jiti: + optional: true postcss: optional: true - ts-node: + tsx: + optional: true + yaml: optional: true postcss-loader@7.3.4: @@ -9384,8 +9425,8 @@ packages: peerDependencies: postcss: ^8.4 - postcss-preset-env@10.2.4: - resolution: {integrity: sha512-q+lXgqmTMdB0Ty+EQ31SuodhdfZetUlwCA/F0zRcd/XdxjzI+Rl2JhZNz5US2n/7t9ePsvuhCnEN4Bmu86zXlA==} + postcss-preset-env@10.4.0: + resolution: {integrity: sha512-2kqpOthQ6JhxqQq1FSAAZGe9COQv75Aw8WbsOvQVNJ2nSevc9Yx/IKZGuZ7XJ+iOTtVon7LfO7ELRzg8AZ+sdw==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -9496,18 +9537,6 @@ packages: resolution: {integrity: sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==} engines: {node: '>=12'} - postman-code-generators@1.14.2: - resolution: {integrity: sha512-qZAyyowfQAFE4MSCu2KtMGGQE/+oG1JhMZMJNMdZHYCSfQiVVeKxgk3oI4+KJ3d1y5rrm2D6C6x+Z+7iyqm+fA==} - engines: {node: '>=12'} - - postman-collection@4.5.0: - resolution: {integrity: sha512-152JSW9pdbaoJihwjc7Q8lc3nPg/PC9lPTHdMk7SHnHhu/GBJB7b2yb9zG7Qua578+3PxkQ/HYBuXpDSvsf7GQ==} - engines: {node: '>=10'} - - postman-url-encoder@3.0.5: - resolution: {integrity: sha512-jOrdVvzUXBC7C+9gkIkpDJ3HIxOHTIqjpQ4C1EMt1ZGeMvSEpbFCKq23DEfgsj46vMnDgyQf+1ZLp2Wm+bKSsA==} - engines: {node: '>=10'} - potpack@1.0.2: resolution: {integrity: sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==} @@ -9522,8 +9551,8 @@ packages: resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} engines: {node: '>=6.0.0'} - prettier-plugin-organize-imports@4.2.0: - resolution: {integrity: sha512-Zdy27UhlmyvATZi67BTnLcKTo8fm6Oik59Sz6H64PgZJVs6NJpPD1mT240mmJn62c98/QaL+r3kx9Q3gRpDajg==} + prettier-plugin-organize-imports@4.3.0: + resolution: {integrity: sha512-FxFz0qFhyBsGdIsb697f/EkvHzi5SZOhWAjxcx2dLt+Q532bAlhswcXGYB1yzjZ69kW8UoadFBw7TyNwlq96Iw==} peerDependencies: prettier: '>=2.0' typescript: '>=2.9' @@ -9634,8 +9663,8 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - pupa@3.1.0: - resolution: {integrity: sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==} + pupa@3.3.0: + resolution: {integrity: sha512-LjgDO2zPtoXP2wJpDjZrGdojii1uqO0cnwKoIoUzkfS98HDmbeiGmYiXo3lXeFlq2xvne1QFQhwYXSUCLKtEuA==} engines: {node: '>=12.20'} qrcode@1.5.4: @@ -9661,8 +9690,8 @@ packages: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} - quick-lru@7.2.0: - resolution: {integrity: sha512-fG4L8TlD1CacJiGMGPxM1/K8l/GaKL2eFQZ6DWAjxZYxSf07DkumbC/Mhh+u/NHvxkfQVL25By0pxBS8QE9ZrQ==} + quick-lru@7.3.0: + resolution: {integrity: sha512-k9lSsjl36EJdK7I06v7APZCbyGT2vMTsYSRX1Q2nbYmnkBqgUhRkAuzH08Ciotteu/PLJmIF2+tti7o3C/ts2g==} engines: {node: '>=18'} quickselect@2.0.0: @@ -9712,13 +9741,13 @@ packages: peerDependencies: react: ^18.3.1 - react-dom@19.1.1: - resolution: {integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==} + react-dom@19.2.0: + resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==} peerDependencies: - react: ^19.1.1 + react: ^19.2.0 - react-email@4.2.11: - resolution: {integrity: sha512-/7TXRgsTrXcV1u7kc5ZXDVlPvZqEBaYcflMhE2FgWIJh3OHLjj2FqctFTgYcp0iwzbR59a7gzJLmSKyD0wYJEQ==} + react-email@4.3.2: + resolution: {integrity: sha512-WaZcnv9OAIRULY236zDRdk+8r511ooJGH5UOb7FnVsV33hGPI+l5aIZ6drVjXi4QrlLTmLm8PsYvmXRSv31MPA==} engines: {node: '>=18.0.0'} hasBin: true @@ -9731,8 +9760,8 @@ packages: react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - react-json-view-lite@2.4.1: - resolution: {integrity: sha512-fwFYknRIBxjbFm0kBDrzgBy1xa5tDg2LyXXBepC5f1b+MY3BUClMCsvanMPn089JbV1Eg3nZcrp0VCuH43aXnA==} + react-json-view-lite@2.5.0: + resolution: {integrity: sha512-tk7o7QG9oYyELWHL8xiMQ8x4WzjCzbWNyig3uexmkLb54r8jO0yH3WCWx8UZS0c49eSA4QUmG5caiRJ8fAn58g==} engines: {node: '>=18'} peerDependencies: react: ^18.0.0 || ^19.0.0 @@ -9744,24 +9773,9 @@ packages: react-loadable: '*' webpack: '>=4.41.1 || 5.x' - react-magic-dropzone@1.0.1: - resolution: {integrity: sha512-0BIROPARmXHpk4AS3eWBOsewxoM5ndk2psYP/JmbCq8tz3uR2LIV1XiroZ9PKrmDRMctpW+TvsBCtWasuS8vFA==} - react-promise-suspense@0.3.4: resolution: {integrity: sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==} - react-redux@7.2.9: - resolution: {integrity: sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==} - peerDependencies: - react: ^16.8.3 || ^17 || ^18 - react-dom: '*' - react-native: '*' - peerDependenciesMeta: - react-dom: - optional: true - react-native: - optional: true - react-router-config@5.1.1: resolution: {integrity: sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==} peerDependencies: @@ -9782,8 +9796,8 @@ packages: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} - react@19.1.1: - resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==} + react@19.2.0: + resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} engines: {node: '>=0.10.0'} read-cache@1.0.0: @@ -9811,15 +9825,13 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} - rechoir@0.6.2: - resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} - engines: {node: '>= 0.10'} - recma-build-jsx@1.0.0: resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} - recma-jsx@1.0.0: - resolution: {integrity: sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q==} + recma-jsx@1.0.1: + resolution: {integrity: sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 recma-parse@1.0.0: resolution: {integrity: sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==} @@ -9839,31 +9851,11 @@ packages: resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} engines: {node: '>=4'} - redux-devtools-extension@2.13.9: - resolution: {integrity: sha512-cNJ8Q/EtjhQaZ71c8I9+BPySIBVEKssbPpskBfsXqb8HJ002A3KRVHfeRzwRo6mGPqsm7XuHTqNSNeS1Khig0A==} - deprecated: Package moved to @redux-devtools/extension. - peerDependencies: - redux: ^3.1.0 || ^4.0.0 - - redux-thunk@2.4.2: - resolution: {integrity: sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==} - peerDependencies: - redux: ^4 - - redux@4.2.1: - resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==} - reflect-metadata@0.2.2: resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} - refractor@4.9.0: - resolution: {integrity: sha512-nEG1SPXFoGGx+dcjftjv8cAjEusIh6ED1xhf5DG3C0x/k+rmZ2duKnc3QLpt6qeHv5fPb8uwN3VWN2BT7fr3Og==} - - reftools@1.1.9: - resolution: {integrity: sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==} - - regenerate-unicode-properties@10.2.0: - resolution: {integrity: sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==} + regenerate-unicode-properties@10.2.2: + resolution: {integrity: sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==} engines: {node: '>=4'} regenerate@1.4.2: @@ -9873,8 +9865,8 @@ packages: resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} hasBin: true - regexpu-core@6.2.0: - resolution: {integrity: sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==} + regexpu-core@6.4.0: + resolution: {integrity: sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==} engines: {node: '>=4'} registry-auth-token@5.1.0: @@ -9892,6 +9884,10 @@ packages: resolution: {integrity: sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==} hasBin: true + regjsparser@0.13.0: + resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==} + hasBin: true + rehype-parse@7.0.1: resolution: {integrity: sha512-fOiR9a9xH+Le19i4fGzIEowAbwG7idy2Jzs4mOrFWBSJ0sNUgy0ev871dwWnbOo371SjgjG4pwzrbgSVrKxecw==} @@ -9918,8 +9914,8 @@ packages: remark-gfm@4.0.1: resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} - remark-mdx@3.1.0: - resolution: {integrity: sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==} + remark-mdx@3.1.1: + resolution: {integrity: sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==} remark-parse@11.0.0: resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} @@ -9945,9 +9941,9 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - require-in-the-middle@7.5.2: - resolution: {integrity: sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==} - engines: {node: '>=8.6.0'} + require-in-the-middle@8.0.0: + resolution: {integrity: sha512-9s0pnM5tH8G4dSI3pms2GboYOs25LwOGnRMxN/Hx3TYT1K0rh6OjaWf4dI0DAQnMyaEXWoGVnSTPQasqwzTTAA==} + engines: {node: '>=9.3.0 || >=8.10.0 <9.0.0'} require-like@0.1.2: resolution: {integrity: sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==} @@ -9958,9 +9954,6 @@ packages: requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - reselect@4.1.8: - resolution: {integrity: sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==} - resolve-alpn@1.2.1: resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} @@ -9974,8 +9967,8 @@ packages: resolve-protobuf-schema@2.1.0: resolution: {integrity: sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==} - resolve@1.22.10: - resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} engines: {node: '>= 0.4'} hasBin: true @@ -10019,8 +10012,8 @@ packages: robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} - rollup-plugin-visualizer@6.0.3: - resolution: {integrity: sha512-ZU41GwrkDcCpVoffviuM9Clwjy5fcUxlz0oMoTXTYsK+tcIFzbdacnrr2n8TXcHxbGKKXtOdjxM2HUS4HjkwIw==} + rollup-plugin-visualizer@6.0.5: + resolution: {integrity: sha512-9+HlNgKCVbJDs8tVtjQ43US12eqaiHyyiLMdBwQ7vSZPiHMysGNo2E88TAp1si5wx8NAoYriI2A5kuKfIakmJg==} engines: {node: '>=18'} hasBin: true peerDependencies: @@ -10032,8 +10025,8 @@ packages: rollup: optional: true - rollup@4.50.1: - resolution: {integrity: sha512-78E9voJHwnXQMiQdiqswVLZwJIzdBKJ1GdI5Zx6XwoFKUIk09/sSrr+05QFzvYb8q6Y9pPV45zzDuYa3907TZA==} + rollup@4.52.5: + resolution: {integrity: sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -10049,6 +10042,10 @@ packages: engines: {node: '>=12.0.0'} hasBin: true + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + run-async@2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} @@ -10099,8 +10096,8 @@ packages: scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} - scheduler@0.26.0: - resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} schema-dts@1.1.5: resolution: {integrity: sha512-RJr9EaCmsLzBX2NDiO5Z3ux2BVosNZN5jo0gWgsyKvxKIUL5R3swNvoorulAeL9kLB0iTSX7V6aokhla2m7xbg==} @@ -10109,8 +10106,8 @@ packages: resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} engines: {node: '>= 10.13.0'} - schema-utils@4.3.2: - resolution: {integrity: sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==} + schema-utils@4.3.3: + resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} engines: {node: '>= 10.13.0'} search-insights@2.17.3: @@ -10138,13 +10135,8 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.6.3: - resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} - engines: {node: '>=10'} - hasBin: true - - semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} engines: {node: '>=10'} hasBin: true @@ -10177,8 +10169,8 @@ packages: set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - set-cookie-parser@2.7.1: - resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} @@ -10201,8 +10193,8 @@ packages: resolution: {integrity: sha512-rLGSWeK2ufzCVx05wYd+xrWnOOdSV7xNUW5/XFgx3Bc02hBkpMlrd2F1dDII7/jhWzv0MSyBFh5uJIy9hLdfuw==} hasBin: true - sharp@0.34.3: - resolution: {integrity: sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==} + sharp@0.34.4: + resolution: {integrity: sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} shebang-command@2.0.0: @@ -10217,29 +10209,6 @@ packages: resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} engines: {node: '>= 0.4'} - shelljs@0.8.5: - resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==} - engines: {node: '>=4'} - hasBin: true - - should-equal@2.0.0: - resolution: {integrity: sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==} - - should-format@3.0.3: - resolution: {integrity: sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==} - - should-type-adaptors@1.1.0: - resolution: {integrity: sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==} - - should-type@1.4.0: - resolution: {integrity: sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==} - - should-util@1.0.1: - resolution: {integrity: sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==} - - should@13.2.3: - resolution: {integrity: sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==} - side-channel-list@1.0.0: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} engines: {node: '>= 0.4'} @@ -10272,13 +10241,10 @@ packages: simple-get@3.1.1: resolution: {integrity: sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==} - simple-icons@15.15.0: - resolution: {integrity: sha512-ohh1Uo9AjH10WN5wpPmtjnmbSLv6MjiULHS4dYA821uIsPAp0Q3XoluPnjBnQAFsztasmM6z2dezJBrQbtserg==} + simple-icons@15.18.0: + resolution: {integrity: sha512-lYpvaIuZZr6N50YSdYZQzrKccSSF3dqcgcoz2vMKVQCc/fJWD8nFszJVZz2tCDTSu082rqRYfuYRUPhjdixDAA==} engines: {node: '>=0.12.18'} - simple-swizzle@0.2.2: - resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} - sirv@2.0.4: resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} engines: {node: '>= 10'} @@ -10362,6 +10328,10 @@ packages: resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} engines: {node: '>= 8'} + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + space-separated-tokens@1.1.5: resolution: {integrity: sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==} @@ -10385,8 +10355,8 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - sql-formatter@15.6.9: - resolution: {integrity: sha512-r9VKnkRfKW7jbhTgytwbM+JqmFclQYN9L58Z3UTktuy9V1f1Y+rGK3t70Truh2wIOJzvZkzobAQ2PwGjjXsr6Q==} + sql-formatter@15.6.10: + resolution: {integrity: sha512-0bJOPQrRO/JkjQhiThVayq0hOKnI1tHI+2OTkmT7TGtc6kqS+V7kveeMzRW+RNQGxofmTmet9ILvztyuxv0cJQ==} hasBin: true srcset@4.0.0: @@ -10396,8 +10366,8 @@ packages: ssh-remote-port-forward@1.0.4: resolution: {integrity: sha512-x0LV1eVDwjf1gmG7TTnfqIzf+3VPRz7vrNIjX6oYLbeCrf/PeVY6hkT68Mg+q02qXxQhrLjB0jfgvhevoCRmLQ==} - ssh2@1.16.0: - resolution: {integrity: sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==} + ssh2@1.17.0: + resolution: {integrity: sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==} engines: {node: '>=10.16.0'} ssri@12.0.0: @@ -10410,9 +10380,6 @@ packages: standard-as-callback@2.1.0: resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} - state-local@1.0.7: - resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==} - statuses@1.5.0: resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} engines: {node: '>= 0.6'} @@ -10425,8 +10392,8 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - std-env@3.9.0: - resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} stdin-discarder@0.2.2: resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} @@ -10439,8 +10406,8 @@ packages: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} - streamx@2.22.1: - resolution: {integrity: sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==} + streamx@2.23.0: + resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==} string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} @@ -10506,9 +10473,6 @@ packages: strip-literal@3.0.0: resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} - striptags@3.2.0: - resolution: {integrity: sha512-g45ZOGzHDMe2bdYMdIvdAfCQkCTDMGBazSw1ypMowwGIee7ZQ5dU0rBJ8Jqgl+jAKIv4dbeE1jscZq9wid1Tkw==} - strnum@2.1.1: resolution: {integrity: sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==} @@ -10516,11 +10480,11 @@ packages: resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==} engines: {node: '>=18'} - style-to-js@1.1.17: - resolution: {integrity: sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==} + style-to-js@1.1.18: + resolution: {integrity: sha512-JFPn62D4kJaPTnhFUI244MThx+FEGbi+9dw1b9yBBQ+1CZpV7QAT8kUtJ7b7EUNdHajjF/0x8fT+16oLJoojLg==} - style-to-object@1.0.9: - resolution: {integrity: sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==} + style-to-object@1.0.11: + resolution: {integrity: sha512-5A560JmXr7wDyGLK12Nq/EYS38VkGlglVzkis1JEdbGWSnbQIEhZzTJhzURXN5/8WwwFCs/f/VVcmkTppbXLow==} stylehacks@6.1.1: resolution: {integrity: sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==} @@ -10537,11 +10501,6 @@ packages: resolution: {integrity: sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==} engines: {node: '>=14.18.0'} - superagent@7.1.6: - resolution: {integrity: sha512-gZkVCQR1gy/oUXr+kxJMLDjla434KmSOKbx5iGD30Ql+AkJQ/YlPKECJy2nhqOsHLjGHzoDTXNSjhnvWhzKk7g==} - engines: {node: '>=6.4.0 <13 || >=14'} - deprecated: Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net - supercluster@7.1.5: resolution: {integrity: sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg==} @@ -10564,17 +10523,17 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - svelte-check@4.3.1: - resolution: {integrity: sha512-lkh8gff5gpHLjxIV+IaApMxQhTGnir2pNUAqcNgeKkvK5bT/30Ey/nzBxNLDlkztCH4dP7PixkMt9SWEKFPBWg==} + svelte-check@4.3.3: + resolution: {integrity: sha512-RYP0bEwenDXzfv0P1sKAwjZSlaRyqBn0Fz1TVni58lqyEiqgwztTpmodJrGzP6ZT2aHl4MbTvWP6gbmQ3FOnBg==} engines: {node: '>= 18.0.0'} hasBin: true peerDependencies: svelte: ^4.0.0 || ^5.0.0-next.0 typescript: '>=5.0.0' - svelte-eslint-parser@1.3.2: - resolution: {integrity: sha512-whla4VlUbwJidn/bNyC3Ho3pBrXnR2CBEkuJwtaURW+wfwgKHPaYtZAmwAkp6HWWKCw1ILZL6iKsFdVY11rpDA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + svelte-eslint-parser@1.4.0: + resolution: {integrity: sha512-fjPzOfipR5S7gQ/JvI9r2H8y9gMGXO3JtmrylHLLyahEMquXI0lrebcjT+9/hNgDej0H7abTyox5HpHmW1PSWA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0, pnpm: 10.18.3} peerDependencies: svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 peerDependenciesMeta: @@ -10584,6 +10543,9 @@ packages: svelte-gestures@5.2.2: resolution: {integrity: sha512-Y+chXPaSx8OsPoFppUwPk8PJzgrZ7xoDJKXeiEc7JBqyKKzXer9hlf8F9O34eFuAWB4/WQEvccACvyBplESL7A==} + svelte-highlight@7.8.4: + resolution: {integrity: sha512-aVp+Q0hH9kI7PlSDrklmFTF4Uj7wYj7UGuqkREnkXlqpEffxr2g6esZcMMaTgECdg5rb2mJqM88+nwS10ecoTg==} + svelte-i18n@4.0.1: resolution: {integrity: sha512-jaykGlGT5PUaaq04JWbJREvivlCnALtT+m87Kbm0fxyYHynkQaxQMnIKHLm2WeIuBRoljzwgyvz0Z6/CMwfdmQ==} engines: {node: '>= 16'} @@ -10591,8 +10553,8 @@ packages: peerDependencies: svelte: ^3 || ^4 || ^5 - svelte-maplibre@1.2.1: - resolution: {integrity: sha512-IVkbc54hQXznyaiFN69RIdjqbLHriNYPVEo1DQMtWSm1kLovrt/aZuhV4eOoZKn6wIvY2Vz34jXPS33f/d/GNw==} + svelte-maplibre@1.2.5: + resolution: {integrity: sha512-Uklcbi6inW9GA0MuSusbXmFr/MQPmXrjuP8hS1+yFX3ySvCQ477tsM3I7Jo/fUDK3XAxFSIHW6hZfucnM3kXwQ==} peerDependencies: '@deck.gl/core': ^9 '@deck.gl/layers': ^9 @@ -10623,8 +10585,8 @@ packages: peerDependencies: svelte: ^5.30.2 - svelte@5.38.10: - resolution: {integrity: sha512-UY+OhrWK7WI22bCZ00P/M3HtyWgwJPi9IxSRkoAE2MeAy6kl7ZlZWJZ8RaB+X4KD/G+wjis+cGVnVYaoqbzBqg==} + svelte@5.43.0: + resolution: {integrity: sha512-1sRxVbgJAB+UGzwkc3GUoiBSzEOf0jqzccMaVoI2+pI+kASUe9qubslxace8+Mzhqw19k4syTA5niCIJwfXpOA==} engines: {node: '>=18'} svg-parser@2.0.4: @@ -10635,12 +10597,13 @@ packages: engines: {node: '>=14.0.0'} hasBin: true - swagger-ui-dist@5.21.0: - resolution: {integrity: sha512-E0K3AB6HvQd8yQNSMR7eE5bk+323AUxjtCz/4ZNKiahOlPhPJxqn3UPIGs00cyY/dhrTDJ61L7C/a8u6zhGrZg==} + swagger-ui-dist@5.29.4: + resolution: {integrity: sha512-gJFDz/gyLOCQtWwAgqs6Rk78z9ONnqTnlW11gimG9nLap8drKa3AJBKpzIQMIjl5PD2Ix+Tn+mc/tfoT2tgsng==} - swagger2openapi@7.0.8: - resolution: {integrity: sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==} - hasBin: true + swr@2.3.6: + resolution: {integrity: sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 symbol-observable@4.0.0: resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} @@ -10659,8 +10622,8 @@ packages: os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android] hasBin: true - tabbable@6.2.0: - resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} + tabbable@6.3.0: + resolution: {integrity: sha512-EIHvdY5bPLuWForiR/AN2Bxngzpuwn1is4asboytXtpTgsArc+WmSJKVLlhdh71u7jFcryDqB2A8lQvj78MkyQ==} tailwind-merge@3.3.1: resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} @@ -10675,40 +10638,40 @@ packages: tailwind-merge: optional: true - tailwindcss-email-variants@3.0.4: - resolution: {integrity: sha512-ohtLSifyWQDAtddJnfbcxkIDCIyXp6Yb83hXRprrS+/2dSyme4OlUZAP+TDwQc0K8D0LAw80eKI6psgejxys8A==} + tailwindcss-email-variants@3.0.5: + resolution: {integrity: sha512-BbULkY0/0VLXwSzAZP0wxyO0siXJARatreZTSW+U6dM+LuTzPsfG/SfxxuouINM1xtIoPGGU++jKkMyfYKJe7Q==} engines: {node: '>=18'} peerDependencies: tailwindcss: '>=3.4.0 < 4' - tailwindcss-mso@2.0.2: - resolution: {integrity: sha512-GaR8RW/Kan+YWEQ9Y9Ah6AYy7R2wEQ3X++YK4ffJVWycCTd6ryMLezqmyhi7KWHqsgQOb4nhjJYayI+JF44BXw==} + tailwindcss-mso@2.0.3: + resolution: {integrity: sha512-on7ooPmtWoR+wgvvDmu3Q1SYx13PQghjo0vNuSwCLgW0cgGiQcmMYRl+zDX7IhM/lt8BOoKY7VFAE2VIDkXD3w==} engines: {node: '>=18.20'} peerDependencies: tailwindcss: '>=3.4.0 < 4' - tailwindcss-preset-email@1.4.0: - resolution: {integrity: sha512-UgvLHT5UsPEEXjto1WlR1wYXmYKeMaS2OPTJQqyufsU12os/EjBpeygEjTdrId7U2/mwDF4grlgo81qlzYSByg==} + tailwindcss-preset-email@1.4.1: + resolution: {integrity: sha512-mNzmFti3hRKTDpbQEdymegxB+WMiACYdHcvmLN5gfIFqJRLhkO4ffT9mqFM5zVQehpxS3dMVczwBKvDT0xpTQA==} peerDependencies: tailwindcss: '>=3.4.17' - tailwindcss@3.4.17: - resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==} + tailwindcss@3.4.18: + resolution: {integrity: sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==} engines: {node: '>=14.0.0'} hasBin: true - tailwindcss@4.1.13: - resolution: {integrity: sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==} + tailwindcss@4.1.16: + resolution: {integrity: sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==} - tapable@2.2.2: - resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==} + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} - tar-fs@2.1.3: - resolution: {integrity: sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==} + tar-fs@2.1.4: + resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} - tar-fs@3.1.0: - resolution: {integrity: sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==} + tar-fs@3.1.1: + resolution: {integrity: sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==} tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} @@ -10721,8 +10684,8 @@ packages: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} - tar@7.4.3: - resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} + tar@7.5.1: + resolution: {integrity: sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==} engines: {node: '>=18'} terser-webpack-plugin@5.3.14: @@ -10741,8 +10704,8 @@ packages: uglify-js: optional: true - terser@5.43.1: - resolution: {integrity: sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==} + terser@5.44.0: + resolution: {integrity: sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==} engines: {node: '>=10'} hasBin: true @@ -10750,8 +10713,8 @@ packages: resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} engines: {node: '>=18'} - testcontainers@11.5.1: - resolution: {integrity: sha512-YSSP4lSJB8498zTeu4HYTZYgSky54ozBmIDdC8PFU5inj+vBo5hPpilhcYTgmsqsYjrXOJGV7jl0MWByS7GwuA==} + testcontainers@11.7.2: + resolution: {integrity: sha512-jeFzeyzLhIouRAbLnQNapJ2esBs/mvXkkYvO1/vSZehT3/7+Q557qaNxwKwMqAbfxfSh7gcx1OLlMsQUZ9JLdA==} text-decoder@1.2.3: resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} @@ -10767,12 +10730,22 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + thingies@2.5.0: + resolution: {integrity: sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==} + engines: {node: '>=10.18'} + peerDependencies: + tslib: ^2 + three@0.179.1: resolution: {integrity: sha512-5y/elSIQbrvKOISxpwXCR4sQqHtGiOI+MKLc3SsBdDXA2hz3Mdp3X59aUp8DyybMa34aeBwbFTpdoLJaUDEWSw==} three@0.180.0: resolution: {integrity: sha512-o+qycAMZrh+TsE01GqWUxUIKR1AL0S8pq7zDkYOQw8GqfX8b8VoCKYUoHbhiX5j+7hr8XsuHDVU6+gkQJQKg9w==} + throttleit@2.1.0: + resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==} + engines: {node: '>=18'} + through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} @@ -10872,6 +10845,12 @@ packages: resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} engines: {node: '>=18'} + tree-dump@1.1.0: + resolution: {integrity: sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -10958,8 +10937,8 @@ packages: typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - typescript-eslint@8.43.0: - resolution: {integrity: sha512-FyRGJKUGvcFekRRcBKFBlAhnp4Ng8rhe8tuvvkR9OiU0gfd4vyvTRQHEckO6VDlH57jbeUQem2IpqPq9kLJH+w==} + typescript-eslint@8.46.2: + resolution: {integrity: sha512-vbw8bOmiuYNdzzV3lsiWv6sRwjyuKJMQqWulBOU7M0RrxedXledX8G8kBbQeiOYDnTfiXz0Y4081E1QMNB6iQg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -10970,16 +10949,16 @@ packages: engines: {node: '>=14.17'} hasBin: true - typescript@5.9.2: - resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} hasBin: true ua-is-frozen@0.1.2: resolution: {integrity: sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw==} - ua-parser-js@2.0.5: - resolution: {integrity: sha512-sZErtx3rhpvZQanWW5umau4o/snfoLqRcQwQIZ54377WtRzIecnIKvjpkd5JwPcSUMglGnbIgcsQBGAbdi3S9Q==} + ua-parser-js@2.0.6: + resolution: {integrity: sha512-EmaxXfltJaDW75SokrY4/lXMrVyXomE/0FpIIqP2Ctic93gK7rlme55Cwkz8l3YZ6gqf94fCU7AnIkidd/KXPg==} hasBin: true uglify-js@3.19.3: @@ -10995,8 +10974,8 @@ packages: resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} engines: {node: '>=8'} - uint8array-extras@1.4.1: - resolution: {integrity: sha512-+NWHrac9dvilNgme+gP4YrBSumsaMZP0fNBtXXFIf33RLLKEcBUKaQZ7ULUbS0sBfcjxIZ4V96OTRkCbM7hxpw==} + uint8array-extras@1.5.0: + resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} engines: {node: '>=18'} undici-types@5.26.5: @@ -11005,8 +10984,8 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - undici-types@7.12.0: - resolution: {integrity: sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==} + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} undici@7.16.0: resolution: {integrity: sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==} @@ -11024,12 +11003,12 @@ packages: resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} engines: {node: '>=4'} - unicode-match-property-value-ecmascript@2.2.0: - resolution: {integrity: sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==} + unicode-match-property-value-ecmascript@2.2.1: + resolution: {integrity: sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==} engines: {node: '>=4'} - unicode-property-aliases-ecmascript@2.1.0: - resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} + unicode-property-aliases-ecmascript@2.2.0: + resolution: {integrity: sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==} engines: {node: '>=4'} unified@11.0.5: @@ -11056,8 +11035,8 @@ packages: unist-util-is@4.1.0: resolution: {integrity: sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==} - unist-util-is@6.0.0: - resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} unist-util-position-from-estree@2.0.0: resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} @@ -11074,8 +11053,8 @@ packages: unist-util-visit-parents@3.1.1: resolution: {integrity: sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==} - unist-util-visit-parents@6.0.1: - resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} unist-util-visit@2.0.3: resolution: {integrity: sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==} @@ -11095,8 +11074,8 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} - unplugin-swc@1.5.7: - resolution: {integrity: sha512-Ng4uuLAodZToA0kQk3+oY8b0C/Q9oV0ohRMixH2nqWMhCF/wNuMYZXZznYpwRLmF7wC36TFIOywBAxCLOReoeg==} + unplugin-swc@1.5.8: + resolution: {integrity: sha512-6qVAQsCn/AMFiw7XGXkNC/q/Or4OpL0zRPertzLT1BoigjOQp0ktPxDWY4lc51FGwVbzW4V1BURaal1rpbJwSg==} peerDependencies: '@swc/core': ^1.2.108 @@ -11104,8 +11083,8 @@ packages: resolution: {integrity: sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==} engines: {node: '>=18.12.0'} - update-browserslist-db@1.1.3: - resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + update-browserslist-db@1.1.4: + resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -11134,15 +11113,20 @@ packages: resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==} engines: {node: '>= 0.4'} + urlpattern-polyfill@8.0.2: + resolution: {integrity: sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==} + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + utf8-byte-length@1.0.5: resolution: {integrity: sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==} util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - util@0.10.4: - resolution: {integrity: sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==} - utila@0.4.0: resolution: {integrity: sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==} @@ -11170,27 +11154,8 @@ packages: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true - uuid@9.0.1: - resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} - hasBin: true - - validate.io-array@1.0.6: - resolution: {integrity: sha512-DeOy7CnPEziggrOO5CZhVKJw6S3Yi7e9e65R1Nl/RTN1vTQKnzjfvks0/8kQ40FP/dsjRAOd4hxmJ7uLa6vxkg==} - - validate.io-function@1.0.2: - resolution: {integrity: sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ==} - - validate.io-integer-array@1.0.0: - resolution: {integrity: sha512-mTrMk/1ytQHtCY0oNO3dztafHYyGU88KL+jRxWuzfOmQb+4qqnWmI+gykvGp8usKZOM0H7keJHEbRaFiYA0VrA==} - - validate.io-integer@1.0.5: - resolution: {integrity: sha512-22izsYSLojN/P6bppBqhgUDjCkr5RY2jd+N2a3DCAUey8ydvrZ/OkGvFPR7qfOpwR2LC5p4Ngzxz36g5Vgr/hQ==} - - validate.io-number@1.0.3: - resolution: {integrity: sha512-kRAyotcbNaSYoDnXvb4MHg/0a1egJdLwS6oJ38TJY7aw9n93Fl/3blIXdyYvPOp55CNxywooG/3BcrwNrBpcSg==} - - validator@13.15.15: - resolution: {integrity: sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==} + validator@13.15.20: + resolution: {integrity: sha512-KxPOq3V2LmfQPP4eqf3Mq/zrT0Dqp2Vmx2Bn285LwVahLc+CsxOM0crBHczm8ijlcjZ0Q5Xd6LW3z3odTPnlrw==} engines: {node: '>= 0.10'} value-equal@1.0.1: @@ -11209,8 +11174,8 @@ packages: vfile-message@2.0.4: resolution: {integrity: sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==} - vfile-message@4.0.2: - resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} vfile@4.2.1: resolution: {integrity: sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==} @@ -11235,8 +11200,8 @@ packages: vite: optional: true - vite@7.1.5: - resolution: {integrity: sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==} + vite@7.1.12: + resolution: {integrity: sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -11356,18 +11321,21 @@ packages: engines: {node: '>= 10.13.0'} hasBin: true - webpack-dev-middleware@5.3.4: - resolution: {integrity: sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==} - engines: {node: '>= 12.13.0'} + webpack-dev-middleware@7.4.5: + resolution: {integrity: sha512-uxQ6YqGdE4hgDKNf7hUiPXOdtkXvBJXrfEGYSx7P7LC8hnUYGK70X6xQXUvXeNyBDDcsiQXpG2m3G9vxowaEuA==} + engines: {node: '>= 18.12.0'} peerDependencies: - webpack: ^4.0.0 || ^5.0.0 + webpack: ^5.0.0 + peerDependenciesMeta: + webpack: + optional: true - webpack-dev-server@4.15.2: - resolution: {integrity: sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==} - engines: {node: '>= 12.13.0'} + webpack-dev-server@5.2.2: + resolution: {integrity: sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==} + engines: {node: '>= 18.12.0'} hasBin: true peerDependencies: - webpack: ^4.37.0 || ^5.0.0 + webpack: ^5.0.0 webpack-cli: '*' peerDependenciesMeta: webpack: @@ -11404,6 +11372,16 @@ packages: webpack-cli: optional: true + webpack@5.102.1: + resolution: {integrity: sha512-7h/weGm9d/ywQ6qzJ+Xy+r9n/3qgp/thalBbpOi5i223dPXKi04IBtqPN9nTd+jBc7QKfvDbaBnFipYp4sJAUQ==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + webpackbar@6.0.1: resolution: {integrity: sha512-TnErZpmuKdwWBdMoexjio3KKX6ZtoKHRVvLIU0A47R0VVBDtx3ZyOJDktgYixhoJokZTYTt1Z37OkO9pnGJa9Q==} engines: {node: '>=14.21.3'} @@ -11538,6 +11516,10 @@ packages: utf-8-validate: optional: true + wsl-utils@0.1.0: + resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} + engines: {node: '>=18'} + xdg-basedir@5.1.0: resolution: {integrity: sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==} engines: {node: '>=12'} @@ -11623,13 +11605,16 @@ packages: resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} engines: {node: '>=18'} - zimmerframe@1.1.2: - resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} + zimmerframe@1.1.4: + resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} zip-stream@6.0.1: resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} engines: {node: '>= 14'} + zod@4.1.12: + resolution: {integrity: sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==} + zwitch@1.0.5: resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==} @@ -11640,119 +11625,148 @@ snapshots: '@adobe/css-tools@4.4.4': {} - '@algolia/autocomplete-core@1.17.9(@algolia/client-search@5.29.0)(algoliasearch@5.29.0)(search-insights@2.17.3)': + '@ai-sdk/gateway@2.0.3(zod@4.1.12)': dependencies: - '@algolia/autocomplete-plugin-algolia-insights': 1.17.9(@algolia/client-search@5.29.0)(algoliasearch@5.29.0)(search-insights@2.17.3) - '@algolia/autocomplete-shared': 1.17.9(@algolia/client-search@5.29.0)(algoliasearch@5.29.0) + '@ai-sdk/provider': 2.0.0 + '@ai-sdk/provider-utils': 3.0.14(zod@4.1.12) + '@vercel/oidc': 3.0.3 + zod: 4.1.12 + + '@ai-sdk/provider-utils@3.0.14(zod@4.1.12)': + dependencies: + '@ai-sdk/provider': 2.0.0 + '@standard-schema/spec': 1.0.0 + eventsource-parser: 3.0.6 + zod: 4.1.12 + + '@ai-sdk/provider@2.0.0': + dependencies: + json-schema: 0.4.0 + + '@ai-sdk/react@2.0.82(react@18.3.1)(zod@4.1.12)': + dependencies: + '@ai-sdk/provider-utils': 3.0.14(zod@4.1.12) + ai: 5.0.82(zod@4.1.12) + react: 18.3.1 + swr: 2.3.6(react@18.3.1) + throttleit: 2.1.0 + optionalDependencies: + zod: 4.1.12 + + '@algolia/abtesting@1.7.0': + dependencies: + '@algolia/client-common': 5.41.0 + '@algolia/requester-browser-xhr': 5.41.0 + '@algolia/requester-fetch': 5.41.0 + '@algolia/requester-node-http': 5.41.0 + + '@algolia/autocomplete-core@1.19.2(@algolia/client-search@5.41.0)(algoliasearch@5.41.0)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-plugin-algolia-insights': 1.19.2(@algolia/client-search@5.41.0)(algoliasearch@5.41.0)(search-insights@2.17.3) + '@algolia/autocomplete-shared': 1.19.2(@algolia/client-search@5.41.0)(algoliasearch@5.41.0) transitivePeerDependencies: - '@algolia/client-search' - algoliasearch - search-insights - '@algolia/autocomplete-plugin-algolia-insights@1.17.9(@algolia/client-search@5.29.0)(algoliasearch@5.29.0)(search-insights@2.17.3)': + '@algolia/autocomplete-plugin-algolia-insights@1.19.2(@algolia/client-search@5.41.0)(algoliasearch@5.41.0)(search-insights@2.17.3)': dependencies: - '@algolia/autocomplete-shared': 1.17.9(@algolia/client-search@5.29.0)(algoliasearch@5.29.0) + '@algolia/autocomplete-shared': 1.19.2(@algolia/client-search@5.41.0)(algoliasearch@5.41.0) search-insights: 2.17.3 transitivePeerDependencies: - '@algolia/client-search' - algoliasearch - '@algolia/autocomplete-preset-algolia@1.17.9(@algolia/client-search@5.29.0)(algoliasearch@5.29.0)': + '@algolia/autocomplete-shared@1.19.2(@algolia/client-search@5.41.0)(algoliasearch@5.41.0)': dependencies: - '@algolia/autocomplete-shared': 1.17.9(@algolia/client-search@5.29.0)(algoliasearch@5.29.0) - '@algolia/client-search': 5.29.0 - algoliasearch: 5.29.0 + '@algolia/client-search': 5.41.0 + algoliasearch: 5.41.0 - '@algolia/autocomplete-shared@1.17.9(@algolia/client-search@5.29.0)(algoliasearch@5.29.0)': + '@algolia/client-abtesting@5.41.0': dependencies: - '@algolia/client-search': 5.29.0 - algoliasearch: 5.29.0 + '@algolia/client-common': 5.41.0 + '@algolia/requester-browser-xhr': 5.41.0 + '@algolia/requester-fetch': 5.41.0 + '@algolia/requester-node-http': 5.41.0 - '@algolia/client-abtesting@5.29.0': + '@algolia/client-analytics@5.41.0': dependencies: - '@algolia/client-common': 5.29.0 - '@algolia/requester-browser-xhr': 5.29.0 - '@algolia/requester-fetch': 5.29.0 - '@algolia/requester-node-http': 5.29.0 + '@algolia/client-common': 5.41.0 + '@algolia/requester-browser-xhr': 5.41.0 + '@algolia/requester-fetch': 5.41.0 + '@algolia/requester-node-http': 5.41.0 - '@algolia/client-analytics@5.29.0': + '@algolia/client-common@5.41.0': {} + + '@algolia/client-insights@5.41.0': dependencies: - '@algolia/client-common': 5.29.0 - '@algolia/requester-browser-xhr': 5.29.0 - '@algolia/requester-fetch': 5.29.0 - '@algolia/requester-node-http': 5.29.0 + '@algolia/client-common': 5.41.0 + '@algolia/requester-browser-xhr': 5.41.0 + '@algolia/requester-fetch': 5.41.0 + '@algolia/requester-node-http': 5.41.0 - '@algolia/client-common@5.29.0': {} - - '@algolia/client-insights@5.29.0': + '@algolia/client-personalization@5.41.0': dependencies: - '@algolia/client-common': 5.29.0 - '@algolia/requester-browser-xhr': 5.29.0 - '@algolia/requester-fetch': 5.29.0 - '@algolia/requester-node-http': 5.29.0 + '@algolia/client-common': 5.41.0 + '@algolia/requester-browser-xhr': 5.41.0 + '@algolia/requester-fetch': 5.41.0 + '@algolia/requester-node-http': 5.41.0 - '@algolia/client-personalization@5.29.0': + '@algolia/client-query-suggestions@5.41.0': dependencies: - '@algolia/client-common': 5.29.0 - '@algolia/requester-browser-xhr': 5.29.0 - '@algolia/requester-fetch': 5.29.0 - '@algolia/requester-node-http': 5.29.0 + '@algolia/client-common': 5.41.0 + '@algolia/requester-browser-xhr': 5.41.0 + '@algolia/requester-fetch': 5.41.0 + '@algolia/requester-node-http': 5.41.0 - '@algolia/client-query-suggestions@5.29.0': + '@algolia/client-search@5.41.0': dependencies: - '@algolia/client-common': 5.29.0 - '@algolia/requester-browser-xhr': 5.29.0 - '@algolia/requester-fetch': 5.29.0 - '@algolia/requester-node-http': 5.29.0 - - '@algolia/client-search@5.29.0': - dependencies: - '@algolia/client-common': 5.29.0 - '@algolia/requester-browser-xhr': 5.29.0 - '@algolia/requester-fetch': 5.29.0 - '@algolia/requester-node-http': 5.29.0 + '@algolia/client-common': 5.41.0 + '@algolia/requester-browser-xhr': 5.41.0 + '@algolia/requester-fetch': 5.41.0 + '@algolia/requester-node-http': 5.41.0 '@algolia/events@4.0.1': {} - '@algolia/ingestion@1.29.0': + '@algolia/ingestion@1.41.0': dependencies: - '@algolia/client-common': 5.29.0 - '@algolia/requester-browser-xhr': 5.29.0 - '@algolia/requester-fetch': 5.29.0 - '@algolia/requester-node-http': 5.29.0 + '@algolia/client-common': 5.41.0 + '@algolia/requester-browser-xhr': 5.41.0 + '@algolia/requester-fetch': 5.41.0 + '@algolia/requester-node-http': 5.41.0 - '@algolia/monitoring@1.29.0': + '@algolia/monitoring@1.41.0': dependencies: - '@algolia/client-common': 5.29.0 - '@algolia/requester-browser-xhr': 5.29.0 - '@algolia/requester-fetch': 5.29.0 - '@algolia/requester-node-http': 5.29.0 + '@algolia/client-common': 5.41.0 + '@algolia/requester-browser-xhr': 5.41.0 + '@algolia/requester-fetch': 5.41.0 + '@algolia/requester-node-http': 5.41.0 - '@algolia/recommend@5.29.0': + '@algolia/recommend@5.41.0': dependencies: - '@algolia/client-common': 5.29.0 - '@algolia/requester-browser-xhr': 5.29.0 - '@algolia/requester-fetch': 5.29.0 - '@algolia/requester-node-http': 5.29.0 + '@algolia/client-common': 5.41.0 + '@algolia/requester-browser-xhr': 5.41.0 + '@algolia/requester-fetch': 5.41.0 + '@algolia/requester-node-http': 5.41.0 - '@algolia/requester-browser-xhr@5.29.0': + '@algolia/requester-browser-xhr@5.41.0': dependencies: - '@algolia/client-common': 5.29.0 + '@algolia/client-common': 5.41.0 - '@algolia/requester-fetch@5.29.0': + '@algolia/requester-fetch@5.41.0': dependencies: - '@algolia/client-common': 5.29.0 + '@algolia/client-common': 5.41.0 - '@algolia/requester-node-http@5.29.0': + '@algolia/requester-node-http@5.41.0': dependencies: - '@algolia/client-common': 5.29.0 + '@algolia/client-common': 5.41.0 '@alloc/quick-lru@5.2.0': {} '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 '@angular-devkit/core@19.2.15(chokidar@4.0.3)': dependencies: @@ -11765,11 +11779,22 @@ snapshots: optionalDependencies: chokidar: 4.0.3 - '@angular-devkit/schematics-cli@19.2.15(@types/node@22.18.5)(chokidar@4.0.3)': + '@angular-devkit/core@19.2.17(chokidar@4.0.3)': + dependencies: + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + jsonc-parser: 3.3.1 + picomatch: 4.0.2 + rxjs: 7.8.1 + source-map: 0.7.4 + optionalDependencies: + chokidar: 4.0.3 + + '@angular-devkit/schematics-cli@19.2.15(@types/node@22.19.0)(chokidar@4.0.3)': dependencies: '@angular-devkit/core': 19.2.15(chokidar@4.0.3) '@angular-devkit/schematics': 19.2.15(chokidar@4.0.3) - '@inquirer/prompts': 7.3.2(@types/node@22.18.5) + '@inquirer/prompts': 7.3.2(@types/node@22.19.0) ansi-colors: 4.1.3 symbol-observable: 4.0.0 yargs-parser: 21.1.1 @@ -11787,6 +11812,16 @@ snapshots: transitivePeerDependencies: - chokidar + '@angular-devkit/schematics@19.2.17(chokidar@4.0.3)': + dependencies: + '@angular-devkit/core': 19.2.17(chokidar@4.0.3) + jsonc-parser: 3.3.1 + magic-string: 0.30.17 + ora: 5.4.1 + rxjs: 7.8.1 + transitivePeerDependencies: + - chokidar + '@asamuzakjp/css-color@3.2.0': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) @@ -11801,15 +11836,15 @@ snapshots: '@aws-crypto/sha256-js': 5.2.0 '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.887.0 - '@aws-sdk/util-locate-window': 3.873.0 + '@aws-sdk/types': 3.914.0 + '@aws-sdk/util-locate-window': 3.893.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 '@aws-crypto/sha256-js@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.887.0 + '@aws-sdk/types': 3.914.0 tslib: 2.8.1 '@aws-crypto/supports-web-crypto@5.2.0': @@ -11818,390 +11853,387 @@ snapshots: '@aws-crypto/util@5.2.0': dependencies: - '@aws-sdk/types': 3.887.0 + '@aws-sdk/types': 3.914.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 - '@aws-sdk/client-sesv2@3.890.0': + '@aws-sdk/client-sesv2@3.919.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.890.0 - '@aws-sdk/credential-provider-node': 3.890.0 - '@aws-sdk/middleware-host-header': 3.887.0 - '@aws-sdk/middleware-logger': 3.887.0 - '@aws-sdk/middleware-recursion-detection': 3.887.0 - '@aws-sdk/middleware-user-agent': 3.890.0 - '@aws-sdk/region-config-resolver': 3.890.0 - '@aws-sdk/signature-v4-multi-region': 3.890.0 - '@aws-sdk/types': 3.887.0 - '@aws-sdk/util-endpoints': 3.890.0 - '@aws-sdk/util-user-agent-browser': 3.887.0 - '@aws-sdk/util-user-agent-node': 3.890.0 - '@smithy/config-resolver': 4.2.2 - '@smithy/core': 3.11.0 - '@smithy/fetch-http-handler': 5.2.1 - '@smithy/hash-node': 4.1.1 - '@smithy/invalid-dependency': 4.1.1 - '@smithy/middleware-content-length': 4.1.1 - '@smithy/middleware-endpoint': 4.2.2 - '@smithy/middleware-retry': 4.2.2 - '@smithy/middleware-serde': 4.1.1 - '@smithy/middleware-stack': 4.1.1 - '@smithy/node-config-provider': 4.2.2 - '@smithy/node-http-handler': 4.2.1 - '@smithy/protocol-http': 5.2.1 - '@smithy/smithy-client': 4.6.2 - '@smithy/types': 4.5.0 - '@smithy/url-parser': 4.1.1 - '@smithy/util-base64': 4.1.0 - '@smithy/util-body-length-browser': 4.1.0 - '@smithy/util-body-length-node': 4.1.0 - '@smithy/util-defaults-mode-browser': 4.1.2 - '@smithy/util-defaults-mode-node': 4.1.2 - '@smithy/util-endpoints': 3.1.2 - '@smithy/util-middleware': 4.1.1 - '@smithy/util-retry': 4.1.1 - '@smithy/util-utf8': 4.1.0 + '@aws-sdk/core': 3.916.0 + '@aws-sdk/credential-provider-node': 3.919.0 + '@aws-sdk/middleware-host-header': 3.914.0 + '@aws-sdk/middleware-logger': 3.914.0 + '@aws-sdk/middleware-recursion-detection': 3.919.0 + '@aws-sdk/middleware-user-agent': 3.916.0 + '@aws-sdk/region-config-resolver': 3.914.0 + '@aws-sdk/signature-v4-multi-region': 3.916.0 + '@aws-sdk/types': 3.914.0 + '@aws-sdk/util-endpoints': 3.916.0 + '@aws-sdk/util-user-agent-browser': 3.914.0 + '@aws-sdk/util-user-agent-node': 3.916.0 + '@smithy/config-resolver': 4.4.0 + '@smithy/core': 3.17.1 + '@smithy/fetch-http-handler': 5.3.4 + '@smithy/hash-node': 4.2.3 + '@smithy/invalid-dependency': 4.2.3 + '@smithy/middleware-content-length': 4.2.3 + '@smithy/middleware-endpoint': 4.3.5 + '@smithy/middleware-retry': 4.4.5 + '@smithy/middleware-serde': 4.2.3 + '@smithy/middleware-stack': 4.2.3 + '@smithy/node-config-provider': 4.3.3 + '@smithy/node-http-handler': 4.4.3 + '@smithy/protocol-http': 5.3.3 + '@smithy/smithy-client': 4.9.1 + '@smithy/types': 4.8.0 + '@smithy/url-parser': 4.2.3 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.4 + '@smithy/util-defaults-mode-node': 4.2.6 + '@smithy/util-endpoints': 3.2.3 + '@smithy/util-middleware': 4.2.3 + '@smithy/util-retry': 4.2.3 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sso@3.890.0': + '@aws-sdk/client-sso@3.919.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.890.0 - '@aws-sdk/middleware-host-header': 3.887.0 - '@aws-sdk/middleware-logger': 3.887.0 - '@aws-sdk/middleware-recursion-detection': 3.887.0 - '@aws-sdk/middleware-user-agent': 3.890.0 - '@aws-sdk/region-config-resolver': 3.890.0 - '@aws-sdk/types': 3.887.0 - '@aws-sdk/util-endpoints': 3.890.0 - '@aws-sdk/util-user-agent-browser': 3.887.0 - '@aws-sdk/util-user-agent-node': 3.890.0 - '@smithy/config-resolver': 4.2.2 - '@smithy/core': 3.11.0 - '@smithy/fetch-http-handler': 5.2.1 - '@smithy/hash-node': 4.1.1 - '@smithy/invalid-dependency': 4.1.1 - '@smithy/middleware-content-length': 4.1.1 - '@smithy/middleware-endpoint': 4.2.2 - '@smithy/middleware-retry': 4.2.2 - '@smithy/middleware-serde': 4.1.1 - '@smithy/middleware-stack': 4.1.1 - '@smithy/node-config-provider': 4.2.2 - '@smithy/node-http-handler': 4.2.1 - '@smithy/protocol-http': 5.2.1 - '@smithy/smithy-client': 4.6.2 - '@smithy/types': 4.5.0 - '@smithy/url-parser': 4.1.1 - '@smithy/util-base64': 4.1.0 - '@smithy/util-body-length-browser': 4.1.0 - '@smithy/util-body-length-node': 4.1.0 - '@smithy/util-defaults-mode-browser': 4.1.2 - '@smithy/util-defaults-mode-node': 4.1.2 - '@smithy/util-endpoints': 3.1.2 - '@smithy/util-middleware': 4.1.1 - '@smithy/util-retry': 4.1.1 - '@smithy/util-utf8': 4.1.0 + '@aws-sdk/core': 3.916.0 + '@aws-sdk/middleware-host-header': 3.914.0 + '@aws-sdk/middleware-logger': 3.914.0 + '@aws-sdk/middleware-recursion-detection': 3.919.0 + '@aws-sdk/middleware-user-agent': 3.916.0 + '@aws-sdk/region-config-resolver': 3.914.0 + '@aws-sdk/types': 3.914.0 + '@aws-sdk/util-endpoints': 3.916.0 + '@aws-sdk/util-user-agent-browser': 3.914.0 + '@aws-sdk/util-user-agent-node': 3.916.0 + '@smithy/config-resolver': 4.4.0 + '@smithy/core': 3.17.1 + '@smithy/fetch-http-handler': 5.3.4 + '@smithy/hash-node': 4.2.3 + '@smithy/invalid-dependency': 4.2.3 + '@smithy/middleware-content-length': 4.2.3 + '@smithy/middleware-endpoint': 4.3.5 + '@smithy/middleware-retry': 4.4.5 + '@smithy/middleware-serde': 4.2.3 + '@smithy/middleware-stack': 4.2.3 + '@smithy/node-config-provider': 4.3.3 + '@smithy/node-http-handler': 4.4.3 + '@smithy/protocol-http': 5.3.3 + '@smithy/smithy-client': 4.9.1 + '@smithy/types': 4.8.0 + '@smithy/url-parser': 4.2.3 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.4 + '@smithy/util-defaults-mode-node': 4.2.6 + '@smithy/util-endpoints': 3.2.3 + '@smithy/util-middleware': 4.2.3 + '@smithy/util-retry': 4.2.3 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/core@3.890.0': + '@aws-sdk/core@3.916.0': dependencies: - '@aws-sdk/types': 3.887.0 - '@aws-sdk/xml-builder': 3.887.0 - '@smithy/core': 3.11.0 - '@smithy/node-config-provider': 4.2.2 - '@smithy/property-provider': 4.1.1 - '@smithy/protocol-http': 5.2.1 - '@smithy/signature-v4': 5.2.1 - '@smithy/smithy-client': 4.6.2 - '@smithy/types': 4.5.0 - '@smithy/util-base64': 4.1.0 - '@smithy/util-body-length-browser': 4.1.0 - '@smithy/util-middleware': 4.1.1 - '@smithy/util-utf8': 4.1.0 - fast-xml-parser: 5.2.5 + '@aws-sdk/types': 3.914.0 + '@aws-sdk/xml-builder': 3.914.0 + '@smithy/core': 3.17.1 + '@smithy/node-config-provider': 4.3.3 + '@smithy/property-provider': 4.2.3 + '@smithy/protocol-http': 5.3.3 + '@smithy/signature-v4': 5.3.3 + '@smithy/smithy-client': 4.9.1 + '@smithy/types': 4.8.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-middleware': 4.2.3 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-env@3.890.0': + '@aws-sdk/credential-provider-env@3.916.0': dependencies: - '@aws-sdk/core': 3.890.0 - '@aws-sdk/types': 3.887.0 - '@smithy/property-provider': 4.1.1 - '@smithy/types': 4.5.0 + '@aws-sdk/core': 3.916.0 + '@aws-sdk/types': 3.914.0 + '@smithy/property-provider': 4.2.3 + '@smithy/types': 4.8.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-http@3.890.0': + '@aws-sdk/credential-provider-http@3.916.0': dependencies: - '@aws-sdk/core': 3.890.0 - '@aws-sdk/types': 3.887.0 - '@smithy/fetch-http-handler': 5.2.1 - '@smithy/node-http-handler': 4.2.1 - '@smithy/property-provider': 4.1.1 - '@smithy/protocol-http': 5.2.1 - '@smithy/smithy-client': 4.6.2 - '@smithy/types': 4.5.0 - '@smithy/util-stream': 4.3.1 + '@aws-sdk/core': 3.916.0 + '@aws-sdk/types': 3.914.0 + '@smithy/fetch-http-handler': 5.3.4 + '@smithy/node-http-handler': 4.4.3 + '@smithy/property-provider': 4.2.3 + '@smithy/protocol-http': 5.3.3 + '@smithy/smithy-client': 4.9.1 + '@smithy/types': 4.8.0 + '@smithy/util-stream': 4.5.4 tslib: 2.8.1 - '@aws-sdk/credential-provider-ini@3.890.0': + '@aws-sdk/credential-provider-ini@3.919.0': dependencies: - '@aws-sdk/core': 3.890.0 - '@aws-sdk/credential-provider-env': 3.890.0 - '@aws-sdk/credential-provider-http': 3.890.0 - '@aws-sdk/credential-provider-process': 3.890.0 - '@aws-sdk/credential-provider-sso': 3.890.0 - '@aws-sdk/credential-provider-web-identity': 3.890.0 - '@aws-sdk/nested-clients': 3.890.0 - '@aws-sdk/types': 3.887.0 - '@smithy/credential-provider-imds': 4.1.2 - '@smithy/property-provider': 4.1.1 - '@smithy/shared-ini-file-loader': 4.2.0 - '@smithy/types': 4.5.0 + '@aws-sdk/core': 3.916.0 + '@aws-sdk/credential-provider-env': 3.916.0 + '@aws-sdk/credential-provider-http': 3.916.0 + '@aws-sdk/credential-provider-process': 3.916.0 + '@aws-sdk/credential-provider-sso': 3.919.0 + '@aws-sdk/credential-provider-web-identity': 3.919.0 + '@aws-sdk/nested-clients': 3.919.0 + '@aws-sdk/types': 3.914.0 + '@smithy/credential-provider-imds': 4.2.3 + '@smithy/property-provider': 4.2.3 + '@smithy/shared-ini-file-loader': 4.3.3 + '@smithy/types': 4.8.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-node@3.890.0': + '@aws-sdk/credential-provider-node@3.919.0': dependencies: - '@aws-sdk/credential-provider-env': 3.890.0 - '@aws-sdk/credential-provider-http': 3.890.0 - '@aws-sdk/credential-provider-ini': 3.890.0 - '@aws-sdk/credential-provider-process': 3.890.0 - '@aws-sdk/credential-provider-sso': 3.890.0 - '@aws-sdk/credential-provider-web-identity': 3.890.0 - '@aws-sdk/types': 3.887.0 - '@smithy/credential-provider-imds': 4.1.2 - '@smithy/property-provider': 4.1.1 - '@smithy/shared-ini-file-loader': 4.2.0 - '@smithy/types': 4.5.0 + '@aws-sdk/credential-provider-env': 3.916.0 + '@aws-sdk/credential-provider-http': 3.916.0 + '@aws-sdk/credential-provider-ini': 3.919.0 + '@aws-sdk/credential-provider-process': 3.916.0 + '@aws-sdk/credential-provider-sso': 3.919.0 + '@aws-sdk/credential-provider-web-identity': 3.919.0 + '@aws-sdk/types': 3.914.0 + '@smithy/credential-provider-imds': 4.2.3 + '@smithy/property-provider': 4.2.3 + '@smithy/shared-ini-file-loader': 4.3.3 + '@smithy/types': 4.8.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-process@3.890.0': + '@aws-sdk/credential-provider-process@3.916.0': dependencies: - '@aws-sdk/core': 3.890.0 - '@aws-sdk/types': 3.887.0 - '@smithy/property-provider': 4.1.1 - '@smithy/shared-ini-file-loader': 4.2.0 - '@smithy/types': 4.5.0 + '@aws-sdk/core': 3.916.0 + '@aws-sdk/types': 3.914.0 + '@smithy/property-provider': 4.2.3 + '@smithy/shared-ini-file-loader': 4.3.3 + '@smithy/types': 4.8.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-sso@3.890.0': + '@aws-sdk/credential-provider-sso@3.919.0': dependencies: - '@aws-sdk/client-sso': 3.890.0 - '@aws-sdk/core': 3.890.0 - '@aws-sdk/token-providers': 3.890.0 - '@aws-sdk/types': 3.887.0 - '@smithy/property-provider': 4.1.1 - '@smithy/shared-ini-file-loader': 4.2.0 - '@smithy/types': 4.5.0 + '@aws-sdk/client-sso': 3.919.0 + '@aws-sdk/core': 3.916.0 + '@aws-sdk/token-providers': 3.919.0 + '@aws-sdk/types': 3.914.0 + '@smithy/property-provider': 4.2.3 + '@smithy/shared-ini-file-loader': 4.3.3 + '@smithy/types': 4.8.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-web-identity@3.890.0': + '@aws-sdk/credential-provider-web-identity@3.919.0': dependencies: - '@aws-sdk/core': 3.890.0 - '@aws-sdk/nested-clients': 3.890.0 - '@aws-sdk/types': 3.887.0 - '@smithy/property-provider': 4.1.1 - '@smithy/shared-ini-file-loader': 4.2.0 - '@smithy/types': 4.5.0 + '@aws-sdk/core': 3.916.0 + '@aws-sdk/nested-clients': 3.919.0 + '@aws-sdk/types': 3.914.0 + '@smithy/property-provider': 4.2.3 + '@smithy/shared-ini-file-loader': 4.3.3 + '@smithy/types': 4.8.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/middleware-host-header@3.887.0': + '@aws-sdk/middleware-host-header@3.914.0': dependencies: - '@aws-sdk/types': 3.887.0 - '@smithy/protocol-http': 5.2.1 - '@smithy/types': 4.5.0 + '@aws-sdk/types': 3.914.0 + '@smithy/protocol-http': 5.3.3 + '@smithy/types': 4.8.0 tslib: 2.8.1 - '@aws-sdk/middleware-logger@3.887.0': + '@aws-sdk/middleware-logger@3.914.0': dependencies: - '@aws-sdk/types': 3.887.0 - '@smithy/types': 4.5.0 + '@aws-sdk/types': 3.914.0 + '@smithy/types': 4.8.0 tslib: 2.8.1 - '@aws-sdk/middleware-recursion-detection@3.887.0': + '@aws-sdk/middleware-recursion-detection@3.919.0': dependencies: - '@aws-sdk/types': 3.887.0 - '@aws/lambda-invoke-store': 0.0.1 - '@smithy/protocol-http': 5.2.1 - '@smithy/types': 4.5.0 + '@aws-sdk/types': 3.914.0 + '@aws/lambda-invoke-store': 0.1.1 + '@smithy/protocol-http': 5.3.3 + '@smithy/types': 4.8.0 tslib: 2.8.1 - '@aws-sdk/middleware-sdk-s3@3.890.0': + '@aws-sdk/middleware-sdk-s3@3.916.0': dependencies: - '@aws-sdk/core': 3.890.0 - '@aws-sdk/types': 3.887.0 - '@aws-sdk/util-arn-parser': 3.873.0 - '@smithy/core': 3.11.0 - '@smithy/node-config-provider': 4.2.2 - '@smithy/protocol-http': 5.2.1 - '@smithy/signature-v4': 5.2.1 - '@smithy/smithy-client': 4.6.2 - '@smithy/types': 4.5.0 - '@smithy/util-config-provider': 4.1.0 - '@smithy/util-middleware': 4.1.1 - '@smithy/util-stream': 4.3.1 - '@smithy/util-utf8': 4.1.0 + '@aws-sdk/core': 3.916.0 + '@aws-sdk/types': 3.914.0 + '@aws-sdk/util-arn-parser': 3.893.0 + '@smithy/core': 3.17.1 + '@smithy/node-config-provider': 4.3.3 + '@smithy/protocol-http': 5.3.3 + '@smithy/signature-v4': 5.3.3 + '@smithy/smithy-client': 4.9.1 + '@smithy/types': 4.8.0 + '@smithy/util-config-provider': 4.2.0 + '@smithy/util-middleware': 4.2.3 + '@smithy/util-stream': 4.5.4 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@aws-sdk/middleware-user-agent@3.890.0': + '@aws-sdk/middleware-user-agent@3.916.0': dependencies: - '@aws-sdk/core': 3.890.0 - '@aws-sdk/types': 3.887.0 - '@aws-sdk/util-endpoints': 3.890.0 - '@smithy/core': 3.11.0 - '@smithy/protocol-http': 5.2.1 - '@smithy/types': 4.5.0 + '@aws-sdk/core': 3.916.0 + '@aws-sdk/types': 3.914.0 + '@aws-sdk/util-endpoints': 3.916.0 + '@smithy/core': 3.17.1 + '@smithy/protocol-http': 5.3.3 + '@smithy/types': 4.8.0 tslib: 2.8.1 - '@aws-sdk/nested-clients@3.890.0': + '@aws-sdk/nested-clients@3.919.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.890.0 - '@aws-sdk/middleware-host-header': 3.887.0 - '@aws-sdk/middleware-logger': 3.887.0 - '@aws-sdk/middleware-recursion-detection': 3.887.0 - '@aws-sdk/middleware-user-agent': 3.890.0 - '@aws-sdk/region-config-resolver': 3.890.0 - '@aws-sdk/types': 3.887.0 - '@aws-sdk/util-endpoints': 3.890.0 - '@aws-sdk/util-user-agent-browser': 3.887.0 - '@aws-sdk/util-user-agent-node': 3.890.0 - '@smithy/config-resolver': 4.2.2 - '@smithy/core': 3.11.0 - '@smithy/fetch-http-handler': 5.2.1 - '@smithy/hash-node': 4.1.1 - '@smithy/invalid-dependency': 4.1.1 - '@smithy/middleware-content-length': 4.1.1 - '@smithy/middleware-endpoint': 4.2.2 - '@smithy/middleware-retry': 4.2.2 - '@smithy/middleware-serde': 4.1.1 - '@smithy/middleware-stack': 4.1.1 - '@smithy/node-config-provider': 4.2.2 - '@smithy/node-http-handler': 4.2.1 - '@smithy/protocol-http': 5.2.1 - '@smithy/smithy-client': 4.6.2 - '@smithy/types': 4.5.0 - '@smithy/url-parser': 4.1.1 - '@smithy/util-base64': 4.1.0 - '@smithy/util-body-length-browser': 4.1.0 - '@smithy/util-body-length-node': 4.1.0 - '@smithy/util-defaults-mode-browser': 4.1.2 - '@smithy/util-defaults-mode-node': 4.1.2 - '@smithy/util-endpoints': 3.1.2 - '@smithy/util-middleware': 4.1.1 - '@smithy/util-retry': 4.1.1 - '@smithy/util-utf8': 4.1.0 + '@aws-sdk/core': 3.916.0 + '@aws-sdk/middleware-host-header': 3.914.0 + '@aws-sdk/middleware-logger': 3.914.0 + '@aws-sdk/middleware-recursion-detection': 3.919.0 + '@aws-sdk/middleware-user-agent': 3.916.0 + '@aws-sdk/region-config-resolver': 3.914.0 + '@aws-sdk/types': 3.914.0 + '@aws-sdk/util-endpoints': 3.916.0 + '@aws-sdk/util-user-agent-browser': 3.914.0 + '@aws-sdk/util-user-agent-node': 3.916.0 + '@smithy/config-resolver': 4.4.0 + '@smithy/core': 3.17.1 + '@smithy/fetch-http-handler': 5.3.4 + '@smithy/hash-node': 4.2.3 + '@smithy/invalid-dependency': 4.2.3 + '@smithy/middleware-content-length': 4.2.3 + '@smithy/middleware-endpoint': 4.3.5 + '@smithy/middleware-retry': 4.4.5 + '@smithy/middleware-serde': 4.2.3 + '@smithy/middleware-stack': 4.2.3 + '@smithy/node-config-provider': 4.3.3 + '@smithy/node-http-handler': 4.4.3 + '@smithy/protocol-http': 5.3.3 + '@smithy/smithy-client': 4.9.1 + '@smithy/types': 4.8.0 + '@smithy/url-parser': 4.2.3 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.4 + '@smithy/util-defaults-mode-node': 4.2.6 + '@smithy/util-endpoints': 3.2.3 + '@smithy/util-middleware': 4.2.3 + '@smithy/util-retry': 4.2.3 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/region-config-resolver@3.890.0': + '@aws-sdk/region-config-resolver@3.914.0': dependencies: - '@aws-sdk/types': 3.887.0 - '@smithy/node-config-provider': 4.2.2 - '@smithy/types': 4.5.0 - '@smithy/util-config-provider': 4.1.0 - '@smithy/util-middleware': 4.1.1 + '@aws-sdk/types': 3.914.0 + '@smithy/config-resolver': 4.4.0 + '@smithy/types': 4.8.0 tslib: 2.8.1 - '@aws-sdk/signature-v4-multi-region@3.890.0': + '@aws-sdk/signature-v4-multi-region@3.916.0': dependencies: - '@aws-sdk/middleware-sdk-s3': 3.890.0 - '@aws-sdk/types': 3.887.0 - '@smithy/protocol-http': 5.2.1 - '@smithy/signature-v4': 5.2.1 - '@smithy/types': 4.5.0 + '@aws-sdk/middleware-sdk-s3': 3.916.0 + '@aws-sdk/types': 3.914.0 + '@smithy/protocol-http': 5.3.3 + '@smithy/signature-v4': 5.3.3 + '@smithy/types': 4.8.0 tslib: 2.8.1 - '@aws-sdk/token-providers@3.890.0': + '@aws-sdk/token-providers@3.919.0': dependencies: - '@aws-sdk/core': 3.890.0 - '@aws-sdk/nested-clients': 3.890.0 - '@aws-sdk/types': 3.887.0 - '@smithy/property-provider': 4.1.1 - '@smithy/shared-ini-file-loader': 4.2.0 - '@smithy/types': 4.5.0 + '@aws-sdk/core': 3.916.0 + '@aws-sdk/nested-clients': 3.919.0 + '@aws-sdk/types': 3.914.0 + '@smithy/property-provider': 4.2.3 + '@smithy/shared-ini-file-loader': 4.3.3 + '@smithy/types': 4.8.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/types@3.887.0': + '@aws-sdk/types@3.914.0': dependencies: - '@smithy/types': 4.5.0 + '@smithy/types': 4.8.0 tslib: 2.8.1 - '@aws-sdk/util-arn-parser@3.873.0': + '@aws-sdk/util-arn-parser@3.893.0': dependencies: tslib: 2.8.1 - '@aws-sdk/util-endpoints@3.890.0': + '@aws-sdk/util-endpoints@3.916.0': dependencies: - '@aws-sdk/types': 3.887.0 - '@smithy/types': 4.5.0 - '@smithy/url-parser': 4.1.1 - '@smithy/util-endpoints': 3.1.2 + '@aws-sdk/types': 3.914.0 + '@smithy/types': 4.8.0 + '@smithy/url-parser': 4.2.3 + '@smithy/util-endpoints': 3.2.3 tslib: 2.8.1 - '@aws-sdk/util-locate-window@3.873.0': + '@aws-sdk/util-locate-window@3.893.0': dependencies: tslib: 2.8.1 - '@aws-sdk/util-user-agent-browser@3.887.0': + '@aws-sdk/util-user-agent-browser@3.914.0': dependencies: - '@aws-sdk/types': 3.887.0 - '@smithy/types': 4.5.0 + '@aws-sdk/types': 3.914.0 + '@smithy/types': 4.8.0 bowser: 2.12.1 tslib: 2.8.1 - '@aws-sdk/util-user-agent-node@3.890.0': + '@aws-sdk/util-user-agent-node@3.916.0': dependencies: - '@aws-sdk/middleware-user-agent': 3.890.0 - '@aws-sdk/types': 3.887.0 - '@smithy/node-config-provider': 4.2.2 - '@smithy/types': 4.5.0 + '@aws-sdk/middleware-user-agent': 3.916.0 + '@aws-sdk/types': 3.914.0 + '@smithy/node-config-provider': 4.3.3 + '@smithy/types': 4.8.0 tslib: 2.8.1 - '@aws-sdk/xml-builder@3.887.0': + '@aws-sdk/xml-builder@3.914.0': dependencies: - '@smithy/types': 4.5.0 + '@smithy/types': 4.8.0 + fast-xml-parser: 5.2.5 tslib: 2.8.1 - '@aws/lambda-invoke-store@0.0.1': {} + '@aws/lambda-invoke-store@0.1.1': {} '@babel/code-frame@7.27.1': dependencies: - '@babel/helper-validator-identifier': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.27.7': {} + '@babel/compat-data@7.28.5': {} - '@babel/core@7.27.7': + '@babel/core@7.28.5': dependencies: - '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 + '@babel/generator': 7.28.5 '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.27.3(@babel/core@7.27.7) - '@babel/helpers': 7.27.6 - '@babel/parser': 7.28.4 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) + '@babel/helpers': 7.28.4 + '@babel/parser': 7.28.5 '@babel/template': 7.27.2 - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3 gensync: 1.0.0-beta.2 @@ -12210,721 +12242,730 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.28.3': + '@babel/generator@7.28.5': dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 '@babel/helper-annotate-as-pure@7.27.3': dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 '@babel/helper-compilation-targets@7.27.2': dependencies: - '@babel/compat-data': 7.27.7 + '@babel/compat-data': 7.28.5 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.25.3 + browserslist: 4.27.0 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.27.1(@babel/core@7.27.7)': + '@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-member-expression-to-functions': 7.27.1 + '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.27.7) + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.5) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-regexp-features-plugin@7.27.1(@babel/core@7.27.7)': + '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-annotate-as-pure': 7.27.3 - regexpu-core: 6.2.0 + regexpu-core: 6.4.0 semver: 6.3.1 - '@babel/helper-define-polyfill-provider@0.6.5(@babel/core@7.27.7)': + '@babel/helper-define-polyfill-provider@0.6.5(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-plugin-utils': 7.27.1 debug: 4.4.3 lodash.debounce: 4.0.8 - resolve: 1.22.10 + resolve: 1.22.11 transitivePeerDependencies: - supports-color '@babel/helper-globals@7.28.0': {} - '@babel/helper-member-expression-to-functions@7.27.1': + '@babel/helper-member-expression-to-functions@7.28.5': dependencies: - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color '@babel/helper-module-imports@7.27.1': dependencies: - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.27.3(@babel/core@7.27.7)': + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color '@babel/helper-optimise-call-expression@7.27.1': dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 '@babel/helper-plugin-utils@7.27.1': {} - '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.27.7)': + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-wrap-function': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/helper-wrap-function': 7.28.3 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/helper-replace-supers@7.27.1(@babel/core@7.27.7)': + '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 - '@babel/helper-member-expression-to-functions': 7.27.1 + '@babel/core': 7.28.5 + '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color '@babel/helper-skip-transparent-expression-wrappers@7.27.1': dependencies: - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color '@babel/helper-string-parser@7.27.1': {} - '@babel/helper-validator-identifier@7.27.1': {} + '@babel/helper-validator-identifier@7.28.5': {} '@babel/helper-validator-option@7.27.1': {} - '@babel/helper-wrap-function@7.27.1': + '@babel/helper-wrap-function@7.28.3': dependencies: '@babel/template': 7.27.2 - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/helpers@7.27.6': + '@babel/helpers@7.28.4': dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 - '@babel/parser@7.28.4': + '@babel/parser@7.28.5': dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-transform-optional-chaining': 7.27.1(@babel/core@7.27.7) + '@babel/plugin-transform-optional-chaining': 7.28.5(@babel/core@7.28.5) transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.3(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.27.7)': + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 - '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.27.7)': + '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-import-assertions@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-syntax-import-assertions@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.27.7)': + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.27.7) + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-async-generator-functions@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-async-generator-functions@7.28.0(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.27.7) - '@babel/traverse': 7.28.4 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.5) + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-async-to-generator@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-async-to-generator@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-module-imports': 7.27.1 '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.27.7) + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.5) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-block-scoping@7.27.5(@babel/core@7.27.7)': + '@babel/plugin-transform-block-scoping@7.28.5(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-class-properties@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-class-properties@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 - '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.27.7) + '@babel/core': 7.28.5 + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-class-static-block@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-class-static-block@7.28.3(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 - '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.27.7) + '@babel/core': 7.28.5 + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-classes@7.27.7(@babel/core@7.27.7)': + '@babel/plugin-transform-classes@7.28.4(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-globals': 7.28.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.27.7) - '@babel/traverse': 7.28.4 - globals: 11.12.0 + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.5) + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-computed-properties@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-computed-properties@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 '@babel/template': 7.27.2 - '@babel/plugin-transform-destructuring@7.27.7(@babel/core@7.27.7)': + '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-dotall-regex@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-dotall-regex@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.27.7) + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.27.7) + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-exponentiation-operator@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-explicit-resource-management@7.28.0(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-exponentiation-operator@7.28.5(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-json-strings@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-json-strings@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-literals@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-literals@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-logical-assignment-operators@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-logical-assignment-operators@7.28.5(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 - '@babel/helper-module-transforms': 7.27.3(@babel/core@7.27.7) + '@babel/core': 7.28.5 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 - '@babel/helper-module-transforms': 7.27.3(@babel/core@7.27.7) + '@babel/core': 7.28.5 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-systemjs@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-modules-systemjs@7.28.5(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 - '@babel/helper-module-transforms': 7.27.3(@babel/core@7.27.7) + '@babel/core': 7.28.5 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 - '@babel/helper-module-transforms': 7.27.3(@babel/core@7.27.7) + '@babel/core': 7.28.5 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-named-capturing-groups-regex@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-named-capturing-groups-regex@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.27.7) + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-nullish-coalescing-operator@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-nullish-coalescing-operator@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-numeric-separator@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-numeric-separator@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-object-rest-spread@7.27.7(@babel/core@7.27.7)': + '@babel/plugin-transform-object-rest-spread@7.28.4(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-destructuring': 7.27.7(@babel/core@7.27.7) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.27.7) - '@babel/traverse': 7.28.4 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.5) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.5) + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.27.7) + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.5) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-optional-catch-binding@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-optional-catch-binding@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-optional-chaining@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-optional-chaining@7.28.5(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.27.7)': + '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-private-methods@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-private-methods@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 - '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.27.7) + '@babel/core': 7.28.5 + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-property-in-object@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-private-property-in-object@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.27.7) + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-constant-elements@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-react-constant-elements@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-display-name@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 - '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.27.7) + '@babel/core': 7.28.5 + '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.5) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-module-imports': 7.27.1 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.27.7) - '@babel/types': 7.28.4 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-regenerator@7.27.5(@babel/core@7.27.7)': + '@babel/plugin-transform-regenerator@7.28.4(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-regexp-modifiers@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-regexp-modifiers@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.27.7) + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-runtime@7.27.4(@babel/core@7.27.7)': + '@babel/plugin-transform-runtime@7.28.5(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-module-imports': 7.27.1 '@babel/helper-plugin-utils': 7.27.1 - babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.27.7) - babel-plugin-polyfill-corejs3: 0.11.1(@babel/core@7.27.7) - babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.27.7) + babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.28.5) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.5) + babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.28.5) semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-spread@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-spread@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-typescript@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-typescript@7.28.5(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.27.7) + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.27.7) + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-unicode-property-regex@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-unicode-property-regex@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.27.7) + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.27.7) + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-unicode-sets-regex@7.27.1(@babel/core@7.27.7)': + '@babel/plugin-transform-unicode-sets-regex@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.27.7) + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) '@babel/helper-plugin-utils': 7.27.1 - '@babel/preset-env@7.27.2(@babel/core@7.27.7)': + '@babel/preset-env@7.28.5(@babel/core@7.28.5)': dependencies: - '@babel/compat-data': 7.27.7 - '@babel/core': 7.27.7 + '@babel/compat-data': 7.28.5 + '@babel/core': 7.28.5 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.27.7) - '@babel/plugin-syntax-import-assertions': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.27.7) - '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-async-generator-functions': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-async-to-generator': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-block-scoping': 7.27.5(@babel/core@7.27.7) - '@babel/plugin-transform-class-properties': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-class-static-block': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-classes': 7.27.7(@babel/core@7.27.7) - '@babel/plugin-transform-computed-properties': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-destructuring': 7.27.7(@babel/core@7.27.7) - '@babel/plugin-transform-dotall-regex': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-exponentiation-operator': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-json-strings': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-logical-assignment-operators': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-modules-systemjs': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-named-capturing-groups-regex': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-nullish-coalescing-operator': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-numeric-separator': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-object-rest-spread': 7.27.7(@babel/core@7.27.7) - '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-optional-catch-binding': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-optional-chaining': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.27.7) - '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-private-property-in-object': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-regenerator': 7.27.5(@babel/core@7.27.7) - '@babel/plugin-transform-regexp-modifiers': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-spread': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-unicode-property-regex': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-unicode-sets-regex': 7.27.1(@babel/core@7.27.7) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.27.7) - babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.27.7) - babel-plugin-polyfill-corejs3: 0.11.1(@babel/core@7.27.7) - babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.27.7) - core-js-compat: 3.45.0 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.28.5) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.3(@babel/core@7.28.5) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.5) + '@babel/plugin-syntax-import-assertions': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.28.5) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-async-generator-functions': 7.28.0(@babel/core@7.28.5) + '@babel/plugin-transform-async-to-generator': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-block-scoping': 7.28.5(@babel/core@7.28.5) + '@babel/plugin-transform-class-properties': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-class-static-block': 7.28.3(@babel/core@7.28.5) + '@babel/plugin-transform-classes': 7.28.4(@babel/core@7.28.5) + '@babel/plugin-transform-computed-properties': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.5) + '@babel/plugin-transform-dotall-regex': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-explicit-resource-management': 7.28.0(@babel/core@7.28.5) + '@babel/plugin-transform-exponentiation-operator': 7.28.5(@babel/core@7.28.5) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-json-strings': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-logical-assignment-operators': 7.28.5(@babel/core@7.28.5) + '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-modules-systemjs': 7.28.5(@babel/core@7.28.5) + '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-nullish-coalescing-operator': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-numeric-separator': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-object-rest-spread': 7.28.4(@babel/core@7.28.5) + '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-optional-catch-binding': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-optional-chaining': 7.28.5(@babel/core@7.28.5) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.5) + '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-private-property-in-object': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-regenerator': 7.28.4(@babel/core@7.28.5) + '@babel/plugin-transform-regexp-modifiers': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-spread': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-unicode-property-regex': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-unicode-sets-regex': 7.27.1(@babel/core@7.28.5) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.28.5) + babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.28.5) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.5) + babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.28.5) + core-js-compat: 3.46.0 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.27.7)': + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 esutils: 2.0.3 - '@babel/preset-react@7.27.1(@babel/core@7.27.7)': + '@babel/preset-react@7.28.5(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-transform-react-display-name': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.27.7) + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.28.5) + '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.28.5) transitivePeerDependencies: - supports-color - '@babel/preset-typescript@7.27.1(@babel/core@7.27.7)': + '@babel/preset-typescript@7.28.5(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.27.7) - '@babel/plugin-transform-typescript': 7.27.1(@babel/core@7.27.7) + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.5) transitivePeerDependencies: - supports-color - '@babel/runtime-corejs3@7.27.6': + '@babel/runtime-corejs3@7.28.4': dependencies: - core-js-pure: 3.43.0 + core-js-pure: 3.46.0 '@babel/runtime@7.28.4': {} '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 - '@babel/traverse@7.28.4': + '@babel/traverse@7.28.5': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 + '@babel/generator': 7.28.5 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.4 + '@babel/parser': 7.28.5 '@babel/template': 7.27.2 - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 debug: 4.4.3 transitivePeerDependencies: - supports-color - '@babel/types@7.28.4': + '@babel/types@7.28.5': dependencies: '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 '@balena/dockerignore@1.0.2': {} @@ -12965,44 +13006,71 @@ snapshots: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 + '@csstools/postcss-alpha-function@1.0.1(postcss@8.5.6)': + dependencies: + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6) + '@csstools/utilities': 2.0.0(postcss@8.5.6) + postcss: 8.5.6 + '@csstools/postcss-cascade-layers@5.0.2(postcss@8.5.6)': dependencies: '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) postcss: 8.5.6 postcss-selector-parser: 7.1.0 - '@csstools/postcss-color-function@4.0.10(postcss@8.5.6)': + '@csstools/postcss-color-function-display-p3-linear@1.0.1(postcss@8.5.6)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6) '@csstools/utilities': 2.0.0(postcss@8.5.6) postcss: 8.5.6 - '@csstools/postcss-color-mix-function@3.0.10(postcss@8.5.6)': + '@csstools/postcss-color-function@4.0.12(postcss@8.5.6)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6) '@csstools/utilities': 2.0.0(postcss@8.5.6) postcss: 8.5.6 - '@csstools/postcss-color-mix-variadic-function-arguments@1.0.0(postcss@8.5.6)': + '@csstools/postcss-color-mix-function@3.0.12(postcss@8.5.6)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6) '@csstools/utilities': 2.0.0(postcss@8.5.6) postcss: 8.5.6 - '@csstools/postcss-content-alt-text@2.0.6(postcss@8.5.6)': + '@csstools/postcss-color-mix-variadic-function-arguments@1.0.2(postcss@8.5.6)': + dependencies: + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6) + '@csstools/utilities': 2.0.0(postcss@8.5.6) + postcss: 8.5.6 + + '@csstools/postcss-content-alt-text@2.0.8(postcss@8.5.6)': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6) + '@csstools/utilities': 2.0.0(postcss@8.5.6) + postcss: 8.5.6 + + '@csstools/postcss-contrast-color-function@2.0.12(postcss@8.5.6)': + dependencies: + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6) '@csstools/utilities': 2.0.0(postcss@8.5.6) postcss: 8.5.6 @@ -13019,34 +13087,34 @@ snapshots: postcss: 8.5.6 postcss-value-parser: 4.2.0 - '@csstools/postcss-gamut-mapping@2.0.10(postcss@8.5.6)': + '@csstools/postcss-gamut-mapping@2.0.11(postcss@8.5.6)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 postcss: 8.5.6 - '@csstools/postcss-gradients-interpolation-method@5.0.10(postcss@8.5.6)': + '@csstools/postcss-gradients-interpolation-method@5.0.12(postcss@8.5.6)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6) '@csstools/utilities': 2.0.0(postcss@8.5.6) postcss: 8.5.6 - '@csstools/postcss-hwb-function@4.0.10(postcss@8.5.6)': + '@csstools/postcss-hwb-function@4.0.12(postcss@8.5.6)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6) '@csstools/utilities': 2.0.0(postcss@8.5.6) postcss: 8.5.6 - '@csstools/postcss-ic-unit@4.0.2(postcss@8.5.6)': + '@csstools/postcss-ic-unit@4.0.4(postcss@8.5.6)': dependencies: - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6) '@csstools/utilities': 2.0.0(postcss@8.5.6) postcss: 8.5.6 postcss-value-parser: 4.2.0 @@ -13061,11 +13129,11 @@ snapshots: postcss: 8.5.6 postcss-selector-parser: 7.1.0 - '@csstools/postcss-light-dark-function@2.0.9(postcss@8.5.6)': + '@csstools/postcss-light-dark-function@2.0.11(postcss@8.5.6)': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6) '@csstools/utilities': 2.0.0(postcss@8.5.6) postcss: 8.5.6 @@ -13118,16 +13186,16 @@ snapshots: postcss: 8.5.6 postcss-value-parser: 4.2.0 - '@csstools/postcss-oklab-function@4.0.10(postcss@8.5.6)': + '@csstools/postcss-oklab-function@4.0.12(postcss@8.5.6)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6) '@csstools/utilities': 2.0.0(postcss@8.5.6) postcss: 8.5.6 - '@csstools/postcss-progressive-custom-properties@4.1.0(postcss@8.5.6)': + '@csstools/postcss-progressive-custom-properties@4.2.1(postcss@8.5.6)': dependencies: postcss: 8.5.6 postcss-value-parser: 4.2.0 @@ -13139,12 +13207,12 @@ snapshots: '@csstools/css-tokenizer': 3.0.4 postcss: 8.5.6 - '@csstools/postcss-relative-color-syntax@3.0.10(postcss@8.5.6)': + '@csstools/postcss-relative-color-syntax@3.0.12(postcss@8.5.6)': dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6) '@csstools/utilities': 2.0.0(postcss@8.5.6) postcss: 8.5.6 @@ -13167,7 +13235,7 @@ snapshots: '@csstools/css-tokenizer': 3.0.4 postcss: 8.5.6 - '@csstools/postcss-text-decoration-shorthand@4.0.2(postcss@8.5.6)': + '@csstools/postcss-text-decoration-shorthand@4.0.3(postcss@8.5.6)': dependencies: '@csstools/color-helpers': 5.1.0 postcss: 8.5.6 @@ -13198,42 +13266,44 @@ snapshots: '@discoveryjs/json-ext@0.5.7': {} - '@docsearch/css@3.9.0': {} + '@docsearch/css@4.2.0': {} - '@docsearch/react@3.9.0(@algolia/client-search@5.29.0)(@types/react@19.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)': + '@docsearch/react@4.2.0(@algolia/client-search@5.41.0)(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)': dependencies: - '@algolia/autocomplete-core': 1.17.9(@algolia/client-search@5.29.0)(algoliasearch@5.29.0)(search-insights@2.17.3) - '@algolia/autocomplete-preset-algolia': 1.17.9(@algolia/client-search@5.29.0)(algoliasearch@5.29.0) - '@docsearch/css': 3.9.0 - algoliasearch: 5.29.0 + '@ai-sdk/react': 2.0.82(react@18.3.1)(zod@4.1.12) + '@algolia/autocomplete-core': 1.19.2(@algolia/client-search@5.41.0)(algoliasearch@5.41.0)(search-insights@2.17.3) + '@docsearch/css': 4.2.0 + ai: 5.0.82(zod@4.1.12) + algoliasearch: 5.41.0 + marked: 16.4.1 + zod: 4.1.12 optionalDependencies: - '@types/react': 19.1.13 + '@types/react': 19.2.2 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) search-insights: 2.17.3 transitivePeerDependencies: - '@algolia/client-search' - '@docusaurus/babel@3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@docusaurus/babel@3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/core': 7.27.7 - '@babel/generator': 7.28.3 - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.27.7) - '@babel/plugin-transform-runtime': 7.27.4(@babel/core@7.27.7) - '@babel/preset-env': 7.27.2(@babel/core@7.27.7) - '@babel/preset-react': 7.27.1(@babel/core@7.27.7) - '@babel/preset-typescript': 7.27.1(@babel/core@7.27.7) + '@babel/core': 7.28.5 + '@babel/generator': 7.28.5 + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.28.5) + '@babel/plugin-transform-runtime': 7.28.5(@babel/core@7.28.5) + '@babel/preset-env': 7.28.5(@babel/core@7.28.5) + '@babel/preset-react': 7.28.5(@babel/core@7.28.5) + '@babel/preset-typescript': 7.28.5(@babel/core@7.28.5) '@babel/runtime': 7.28.4 - '@babel/runtime-corejs3': 7.27.6 - '@babel/traverse': 7.28.4 - '@docusaurus/logger': 3.8.1 - '@docusaurus/utils': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@babel/runtime-corejs3': 7.28.4 + '@babel/traverse': 7.28.5 + '@docusaurus/logger': 3.9.2 + '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) babel-plugin-dynamic-import-node: 2.3.3 - fs-extra: 11.3.0 + fs-extra: 11.3.2 tslib: 2.8.1 transitivePeerDependencies: - '@swc/core' - - acorn - esbuild - react - react-dom @@ -13241,38 +13311,37 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/bundler@3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + '@docusaurus/bundler@3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)': dependencies: - '@babel/core': 7.27.7 - '@docusaurus/babel': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/cssnano-preset': 3.8.1 - '@docusaurus/logger': 3.8.1 - '@docusaurus/types': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - babel-loader: 9.2.1(@babel/core@7.27.7)(webpack@5.100.2) + '@babel/core': 7.28.5 + '@docusaurus/babel': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/cssnano-preset': 3.9.2 + '@docusaurus/logger': 3.9.2 + '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + babel-loader: 9.2.1(@babel/core@7.28.5)(webpack@5.102.1) clean-css: 5.3.3 - copy-webpack-plugin: 11.0.0(webpack@5.100.2) - css-loader: 6.11.0(webpack@5.100.2) - css-minimizer-webpack-plugin: 5.0.1(clean-css@5.3.3)(webpack@5.100.2) + copy-webpack-plugin: 11.0.0(webpack@5.102.1) + css-loader: 6.11.0(webpack@5.102.1) + css-minimizer-webpack-plugin: 5.0.1(clean-css@5.3.3)(webpack@5.102.1) cssnano: 6.1.2(postcss@8.5.6) - file-loader: 6.2.0(webpack@5.100.2) + file-loader: 6.2.0(webpack@5.102.1) html-minifier-terser: 7.2.0 - mini-css-extract-plugin: 2.9.2(webpack@5.100.2) - null-loader: 4.0.1(webpack@5.100.2) + mini-css-extract-plugin: 2.9.4(webpack@5.102.1) + null-loader: 4.0.1(webpack@5.102.1) postcss: 8.5.6 - postcss-loader: 7.3.4(postcss@8.5.6)(typescript@5.9.2)(webpack@5.100.2) - postcss-preset-env: 10.2.4(postcss@8.5.6) - terser-webpack-plugin: 5.3.14(webpack@5.100.2) + postcss-loader: 7.3.4(postcss@8.5.6)(typescript@5.9.3)(webpack@5.102.1) + postcss-preset-env: 10.4.0(postcss@8.5.6) + terser-webpack-plugin: 5.3.14(webpack@5.102.1) tslib: 2.8.1 - url-loader: 4.1.1(file-loader@6.2.0(webpack@5.100.2))(webpack@5.100.2) - webpack: 5.100.2 - webpackbar: 6.0.1(webpack@5.100.2) + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.102.1))(webpack@5.102.1) + webpack: 5.102.1 + webpackbar: 6.0.1(webpack@5.102.1) transitivePeerDependencies: - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' - - acorn - csso - esbuild - lightningcss @@ -13283,31 +13352,31 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/core@3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + '@docusaurus/core@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)': dependencies: - '@docusaurus/babel': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/bundler': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/logger': 3.8.1 - '@docusaurus/mdx-loader': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-common': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mdx-js/react': 3.1.1(@types/react@19.1.13)(react@18.3.1) + '@docusaurus/babel': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/bundler': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/logger': 3.9.2 + '@docusaurus/mdx-loader': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mdx-js/react': 3.1.1(@types/react@19.2.2)(react@18.3.1) boxen: 6.2.1 chalk: 4.1.2 chokidar: 3.6.0 cli-table3: 0.6.5 combine-promises: 1.2.0 commander: 5.1.0 - core-js: 3.43.0 + core-js: 3.46.0 detect-port: 1.6.1 escape-html: 1.0.3 eta: 2.2.0 eval: 0.1.8 execa: 5.1.1 - fs-extra: 11.3.0 + fs-extra: 11.3.2 html-tags: 3.3.1 - html-webpack-plugin: 5.6.3(webpack@5.100.2) + html-webpack-plugin: 5.6.4(webpack@5.102.1) leven: 3.1.0 lodash: 4.17.21 open: 8.4.2 @@ -13317,18 +13386,18 @@ snapshots: react-dom: 18.3.1(react@18.3.1) react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)' react-loadable: '@docusaurus/react-loadable@6.0.0(react@18.3.1)' - react-loadable-ssr-addon-v5-slorber: 1.0.1(@docusaurus/react-loadable@6.0.0(react@18.3.1))(webpack@5.100.2) + react-loadable-ssr-addon-v5-slorber: 1.0.1(@docusaurus/react-loadable@6.0.0(react@18.3.1))(webpack@5.102.1) react-router: 5.3.4(react@18.3.1) react-router-config: 5.1.1(react-router@5.3.4(react@18.3.1))(react@18.3.1) react-router-dom: 5.3.4(react@18.3.1) - semver: 7.7.2 + semver: 7.7.3 serve-handler: 6.1.6 tinypool: 1.1.1 tslib: 2.8.1 update-notifier: 6.0.2 - webpack: 5.100.2 + webpack: 5.102.1 webpack-bundle-analyzer: 4.10.2 - webpack-dev-server: 4.15.2(webpack@5.100.2) + webpack-dev-server: 5.2.2(webpack@5.102.1) webpack-merge: 6.0.1 transitivePeerDependencies: - '@docusaurus/faster' @@ -13336,7 +13405,6 @@ snapshots: - '@rspack/core' - '@swc/core' - '@swc/css' - - acorn - bufferutil - csso - debug @@ -13348,29 +13416,29 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/cssnano-preset@3.8.1': + '@docusaurus/cssnano-preset@3.9.2': dependencies: cssnano-preset-advanced: 6.1.2(postcss@8.5.6) postcss: 8.5.6 postcss-sort-media-queries: 5.2.0(postcss@8.5.6) tslib: 2.8.1 - '@docusaurus/logger@3.8.1': + '@docusaurus/logger@3.9.2': dependencies: chalk: 4.1.2 tslib: 2.8.1 - '@docusaurus/mdx-loader@3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@docusaurus/mdx-loader@3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@docusaurus/logger': 3.8.1 - '@docusaurus/utils': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mdx-js/mdx': 3.1.0(acorn@8.15.0) + '@docusaurus/logger': 3.9.2 + '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mdx-js/mdx': 3.1.1 '@slorber/remark-comment': 1.0.0 escape-html: 1.0.3 - estree-util-value-to-estree: 3.4.0 - file-loader: 6.2.0(webpack@5.100.2) - fs-extra: 11.3.0 + estree-util-value-to-estree: 3.5.0 + file-loader: 6.2.0(webpack@5.102.1) + fs-extra: 11.3.2 image-size: 2.0.2 mdast-util-mdx: 3.0.0 mdast-util-to-string: 4.0.0 @@ -13385,22 +13453,21 @@ snapshots: tslib: 2.8.1 unified: 11.0.5 unist-util-visit: 5.0.0 - url-loader: 4.1.1(file-loader@6.2.0(webpack@5.100.2))(webpack@5.100.2) + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.102.1))(webpack@5.102.1) vfile: 6.0.3 - webpack: 5.100.2 + webpack: 5.102.1 transitivePeerDependencies: - '@swc/core' - - acorn - esbuild - supports-color - uglify-js - webpack-cli - '@docusaurus/module-type-aliases@3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@docusaurus/module-type-aliases@3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@docusaurus/types': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/history': 4.7.11 - '@types/react': 19.1.13 + '@types/react': 19.2.2 '@types/react-router-config': 5.0.11 '@types/react-router-dom': 5.3.3 react: 18.3.1 @@ -13409,26 +13476,25 @@ snapshots: react-loadable: '@docusaurus/react-loadable@6.0.0(react@18.3.1)' transitivePeerDependencies: - '@swc/core' - - acorn - esbuild - supports-color - uglify-js - webpack-cli - '@docusaurus/plugin-content-blog@3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + '@docusaurus/plugin-content-blog@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)': dependencies: - '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/logger': 3.8.1 - '@docusaurus/mdx-loader': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/plugin-content-docs': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/theme-common': 3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/types': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-common': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/logger': 3.9.2 + '@docusaurus/mdx-loader': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) cheerio: 1.0.0-rc.12 feed: 4.2.2 - fs-extra: 11.3.0 + fs-extra: 11.3.2 lodash: 4.17.21 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -13437,7 +13503,7 @@ snapshots: tslib: 2.8.1 unist-util-visit: 5.0.0 utility-types: 3.11.0 - webpack: 5.100.2 + webpack: 5.102.1 transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' @@ -13445,7 +13511,6 @@ snapshots: - '@rspack/core' - '@swc/core' - '@swc/css' - - acorn - bufferutil - csso - debug @@ -13457,20 +13522,20 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + '@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)': dependencies: - '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/logger': 3.8.1 - '@docusaurus/mdx-loader': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/module-type-aliases': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/theme-common': 3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/types': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-common': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/logger': 3.9.2 + '@docusaurus/mdx-loader': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/module-type-aliases': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/react-router-config': 5.0.11 combine-promises: 1.2.0 - fs-extra: 11.3.0 + fs-extra: 11.3.2 js-yaml: 4.1.0 lodash: 4.17.21 react: 18.3.1 @@ -13478,7 +13543,7 @@ snapshots: schema-dts: 1.1.5 tslib: 2.8.1 utility-types: 3.11.0 - webpack: 5.100.2 + webpack: 5.102.1 transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' @@ -13486,7 +13551,6 @@ snapshots: - '@rspack/core' - '@swc/core' - '@swc/css' - - acorn - bufferutil - csso - debug @@ -13498,18 +13562,18 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-content-pages@3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + '@docusaurus/plugin-content-pages@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)': dependencies: - '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/mdx-loader': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/types': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - fs-extra: 11.3.0 + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/mdx-loader': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + fs-extra: 11.3.2 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) tslib: 2.8.1 - webpack: 5.100.2 + webpack: 5.102.1 transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' @@ -13517,7 +13581,6 @@ snapshots: - '@rspack/core' - '@swc/core' - '@swc/css' - - acorn - bufferutil - csso - debug @@ -13529,12 +13592,12 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-css-cascade-layers@3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + '@docusaurus/plugin-css-cascade-layers@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)': dependencies: - '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/types': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' @@ -13543,7 +13606,6 @@ snapshots: - '@rspack/core' - '@swc/core' - '@swc/css' - - acorn - bufferutil - csso - debug @@ -13557,15 +13619,15 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-debug@3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + '@docusaurus/plugin-debug@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)': dependencies: - '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/types': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - fs-extra: 11.3.0 + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + fs-extra: 11.3.2 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-json-view-lite: 2.4.1(react@18.3.1) + react-json-view-lite: 2.5.0(react@18.3.1) tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' @@ -13574,7 +13636,6 @@ snapshots: - '@rspack/core' - '@swc/core' - '@swc/css' - - acorn - bufferutil - csso - debug @@ -13586,11 +13647,11 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-analytics@3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + '@docusaurus/plugin-google-analytics@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)': dependencies: - '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/types': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) tslib: 2.8.1 @@ -13601,7 +13662,6 @@ snapshots: - '@rspack/core' - '@swc/core' - '@swc/css' - - acorn - bufferutil - csso - debug @@ -13613,11 +13673,11 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-gtag@3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + '@docusaurus/plugin-google-gtag@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)': dependencies: - '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/types': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/gtag.js': 0.0.12 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -13629,7 +13689,6 @@ snapshots: - '@rspack/core' - '@swc/core' - '@swc/css' - - acorn - bufferutil - csso - debug @@ -13641,11 +13700,11 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-tag-manager@3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + '@docusaurus/plugin-google-tag-manager@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)': dependencies: - '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/types': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) tslib: 2.8.1 @@ -13656,7 +13715,6 @@ snapshots: - '@rspack/core' - '@swc/core' - '@swc/css' - - acorn - bufferutil - csso - debug @@ -13668,15 +13726,15 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-sitemap@3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + '@docusaurus/plugin-sitemap@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)': dependencies: - '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/logger': 3.8.1 - '@docusaurus/types': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-common': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - fs-extra: 11.3.0 + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/logger': 3.9.2 + '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + fs-extra: 11.3.2 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) sitemap: 7.1.2 @@ -13688,7 +13746,6 @@ snapshots: - '@rspack/core' - '@swc/core' - '@swc/css' - - acorn - bufferutil - csso - debug @@ -13700,18 +13757,18 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-svgr@3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + '@docusaurus/plugin-svgr@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)': dependencies: - '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/types': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@svgr/core': 8.1.0(typescript@5.9.2) - '@svgr/webpack': 8.1.0(typescript@5.9.2) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@svgr/core': 8.1.0(typescript@5.9.3) + '@svgr/webpack': 8.1.0(typescript@5.9.3) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) tslib: 2.8.1 - webpack: 5.100.2 + webpack: 5.102.1 transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' @@ -13719,7 +13776,6 @@ snapshots: - '@rspack/core' - '@swc/core' - '@swc/css' - - acorn - bufferutil - csso - debug @@ -13731,23 +13787,23 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/preset-classic@3.8.1(@algolia/client-search@5.29.0)(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(@types/react@19.1.13)(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.9.2)': + '@docusaurus/preset-classic@3.9.2(@algolia/client-search@5.41.0)(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.9.3)': dependencies: - '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/plugin-content-blog': 3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/plugin-content-docs': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/plugin-content-pages': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/plugin-css-cascade-layers': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/plugin-debug': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/plugin-google-analytics': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/plugin-google-gtag': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/plugin-google-tag-manager': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/plugin-sitemap': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/plugin-svgr': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/theme-classic': 3.8.1(@types/react@19.1.13)(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/theme-common': 3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/theme-search-algolia': 3.8.1(@algolia/client-search@5.29.0)(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(@types/react@19.1.13)(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.9.2) - '@docusaurus/types': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/plugin-content-blog': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/plugin-content-pages': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/plugin-css-cascade-layers': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/plugin-debug': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/plugin-google-analytics': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/plugin-google-gtag': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/plugin-google-tag-manager': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/plugin-sitemap': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/plugin-svgr': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/theme-classic': 3.9.2(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/theme-search-algolia': 3.9.2(@algolia/client-search@5.41.0)(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.9.3) + '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) transitivePeerDependencies: @@ -13759,7 +13815,6 @@ snapshots: - '@swc/core' - '@swc/css' - '@types/react' - - acorn - bufferutil - csso - debug @@ -13774,27 +13829,26 @@ snapshots: '@docusaurus/react-loadable@6.0.0(react@18.3.1)': dependencies: - '@types/react': 19.1.13 + '@types/react': 19.2.2 react: 18.3.1 - '@docusaurus/theme-classic@3.8.1(@types/react@19.1.13)(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + '@docusaurus/theme-classic@3.9.2(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)': dependencies: - '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/logger': 3.8.1 - '@docusaurus/mdx-loader': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/module-type-aliases': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/plugin-content-blog': 3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/plugin-content-docs': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/plugin-content-pages': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/theme-common': 3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/theme-translations': 3.8.1 - '@docusaurus/types': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-common': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mdx-js/react': 3.1.1(@types/react@19.1.13)(react@18.3.1) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/logger': 3.9.2 + '@docusaurus/mdx-loader': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/module-type-aliases': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/plugin-content-blog': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/plugin-content-pages': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/theme-translations': 3.9.2 + '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mdx-js/react': 3.1.1(@types/react@19.2.2)(react@18.3.1) clsx: 2.1.1 - copy-text-to-clipboard: 3.2.0 infima: 0.2.0-alpha.45 lodash: 4.17.21 nprogress: 0.2.0 @@ -13814,7 +13868,6 @@ snapshots: - '@swc/core' - '@swc/css' - '@types/react' - - acorn - bufferutil - csso - debug @@ -13826,15 +13879,15 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/theme-common@3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@docusaurus/theme-common@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@docusaurus/mdx-loader': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/module-type-aliases': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/plugin-content-docs': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/utils': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-common': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/mdx-loader': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/module-type-aliases': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/history': 4.7.11 - '@types/react': 19.1.13 + '@types/react': 19.2.2 '@types/react-router-config': 5.0.11 clsx: 2.1.1 parse-numeric-range: 1.3.0 @@ -13845,27 +13898,26 @@ snapshots: utility-types: 3.11.0 transitivePeerDependencies: - '@swc/core' - - acorn - esbuild - supports-color - uglify-js - webpack-cli - '@docusaurus/theme-search-algolia@3.8.1(@algolia/client-search@5.29.0)(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(@types/react@19.1.13)(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.9.2)': + '@docusaurus/theme-search-algolia@3.9.2(@algolia/client-search@5.41.0)(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.9.3)': dependencies: - '@docsearch/react': 3.9.0(@algolia/client-search@5.29.0)(@types/react@19.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3) - '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/logger': 3.8.1 - '@docusaurus/plugin-content-docs': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/theme-common': 3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/theme-translations': 3.8.1 - '@docusaurus/utils': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - algoliasearch: 5.29.0 - algoliasearch-helper: 3.26.0(algoliasearch@5.29.0) + '@docsearch/react': 4.2.0(@algolia/client-search@5.41.0)(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/logger': 3.9.2 + '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/theme-translations': 3.9.2 + '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + algoliasearch: 5.41.0 + algoliasearch-helper: 3.26.0(algoliasearch@5.41.0) clsx: 2.1.1 eta: 2.2.0 - fs-extra: 11.3.0 + fs-extra: 11.3.2 lodash: 4.17.21 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -13880,7 +13932,6 @@ snapshots: - '@swc/core' - '@swc/css' - '@types/react' - - acorn - bufferutil - csso - debug @@ -13893,41 +13944,40 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/theme-translations@3.8.1': + '@docusaurus/theme-translations@3.9.2': dependencies: - fs-extra: 11.3.0 + fs-extra: 11.3.2 tslib: 2.8.1 - '@docusaurus/tsconfig@3.8.1': {} + '@docusaurus/tsconfig@3.9.2': {} - '@docusaurus/types@3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@docusaurus/types@3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@mdx-js/mdx': 3.1.0(acorn@8.15.0) + '@mdx-js/mdx': 3.1.1 '@types/history': 4.7.11 - '@types/react': 19.1.13 + '@types/mdast': 4.0.4 + '@types/react': 19.2.2 commander: 5.1.0 joi: 17.13.3 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)' utility-types: 3.11.0 - webpack: 5.100.2 + webpack: 5.102.1 webpack-merge: 5.10.0 transitivePeerDependencies: - '@swc/core' - - acorn - esbuild - supports-color - uglify-js - webpack-cli - '@docusaurus/utils-common@3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@docusaurus/utils-common@3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@docusaurus/types': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) tslib: 2.8.1 transitivePeerDependencies: - '@swc/core' - - acorn - esbuild - react - react-dom @@ -13935,19 +13985,18 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/utils-validation@3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@docusaurus/utils-validation@3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@docusaurus/logger': 3.8.1 - '@docusaurus/utils': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-common': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - fs-extra: 11.3.0 + '@docusaurus/logger': 3.9.2 + '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + fs-extra: 11.3.2 joi: 17.13.3 js-yaml: 4.1.0 lodash: 4.17.21 tslib: 2.8.1 transitivePeerDependencies: - '@swc/core' - - acorn - esbuild - react - react-dom @@ -13955,15 +14004,15 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/utils@3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@docusaurus/utils@3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@docusaurus/logger': 3.8.1 - '@docusaurus/types': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-common': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/logger': 3.9.2 + '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) escape-string-regexp: 4.0.0 execa: 5.1.1 - file-loader: 6.2.0(webpack@5.100.2) - fs-extra: 11.3.0 + file-loader: 6.2.0(webpack@5.102.1) + fs-extra: 11.3.2 github-slugger: 1.5.0 globby: 11.1.0 gray-matter: 4.0.3 @@ -13975,12 +14024,11 @@ snapshots: prompts: 2.4.2 resolve-pathname: 3.0.0 tslib: 2.8.1 - url-loader: 4.1.1(file-loader@6.2.0(webpack@5.100.2))(webpack@5.100.2) + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.102.1))(webpack@5.102.1) utility-types: 3.11.0 - webpack: 5.100.2 + webpack: 5.102.1 transitivePeerDependencies: - '@swc/core' - - acorn - esbuild - react - react-dom @@ -13996,171 +14044,177 @@ snapshots: '@esbuild/aix-ppc64@0.19.12': optional: true - '@esbuild/aix-ppc64@0.25.9': + '@esbuild/aix-ppc64@0.25.12': optional: true '@esbuild/android-arm64@0.19.12': optional: true - '@esbuild/android-arm64@0.25.9': + '@esbuild/android-arm64@0.25.12': optional: true '@esbuild/android-arm@0.19.12': optional: true - '@esbuild/android-arm@0.25.9': + '@esbuild/android-arm@0.25.12': optional: true '@esbuild/android-x64@0.19.12': optional: true - '@esbuild/android-x64@0.25.9': + '@esbuild/android-x64@0.25.12': optional: true '@esbuild/darwin-arm64@0.19.12': optional: true - '@esbuild/darwin-arm64@0.25.9': + '@esbuild/darwin-arm64@0.25.12': optional: true '@esbuild/darwin-x64@0.19.12': optional: true - '@esbuild/darwin-x64@0.25.9': + '@esbuild/darwin-x64@0.25.12': optional: true '@esbuild/freebsd-arm64@0.19.12': optional: true - '@esbuild/freebsd-arm64@0.25.9': + '@esbuild/freebsd-arm64@0.25.12': optional: true '@esbuild/freebsd-x64@0.19.12': optional: true - '@esbuild/freebsd-x64@0.25.9': + '@esbuild/freebsd-x64@0.25.12': optional: true '@esbuild/linux-arm64@0.19.12': optional: true - '@esbuild/linux-arm64@0.25.9': + '@esbuild/linux-arm64@0.25.12': optional: true '@esbuild/linux-arm@0.19.12': optional: true - '@esbuild/linux-arm@0.25.9': + '@esbuild/linux-arm@0.25.12': optional: true '@esbuild/linux-ia32@0.19.12': optional: true - '@esbuild/linux-ia32@0.25.9': + '@esbuild/linux-ia32@0.25.12': optional: true '@esbuild/linux-loong64@0.19.12': optional: true - '@esbuild/linux-loong64@0.25.9': + '@esbuild/linux-loong64@0.25.12': optional: true '@esbuild/linux-mips64el@0.19.12': optional: true - '@esbuild/linux-mips64el@0.25.9': + '@esbuild/linux-mips64el@0.25.12': optional: true '@esbuild/linux-ppc64@0.19.12': optional: true - '@esbuild/linux-ppc64@0.25.9': + '@esbuild/linux-ppc64@0.25.12': optional: true '@esbuild/linux-riscv64@0.19.12': optional: true - '@esbuild/linux-riscv64@0.25.9': + '@esbuild/linux-riscv64@0.25.12': optional: true '@esbuild/linux-s390x@0.19.12': optional: true - '@esbuild/linux-s390x@0.25.9': + '@esbuild/linux-s390x@0.25.12': optional: true '@esbuild/linux-x64@0.19.12': optional: true - '@esbuild/linux-x64@0.25.9': + '@esbuild/linux-x64@0.25.12': optional: true - '@esbuild/netbsd-arm64@0.25.9': + '@esbuild/netbsd-arm64@0.25.12': optional: true '@esbuild/netbsd-x64@0.19.12': optional: true - '@esbuild/netbsd-x64@0.25.9': + '@esbuild/netbsd-x64@0.25.12': optional: true - '@esbuild/openbsd-arm64@0.25.9': + '@esbuild/openbsd-arm64@0.25.12': optional: true '@esbuild/openbsd-x64@0.19.12': optional: true - '@esbuild/openbsd-x64@0.25.9': + '@esbuild/openbsd-x64@0.25.12': optional: true - '@esbuild/openharmony-arm64@0.25.9': + '@esbuild/openharmony-arm64@0.25.12': optional: true '@esbuild/sunos-x64@0.19.12': optional: true - '@esbuild/sunos-x64@0.25.9': + '@esbuild/sunos-x64@0.25.12': optional: true '@esbuild/win32-arm64@0.19.12': optional: true - '@esbuild/win32-arm64@0.25.9': + '@esbuild/win32-arm64@0.25.12': optional: true '@esbuild/win32-ia32@0.19.12': optional: true - '@esbuild/win32-ia32@0.25.9': + '@esbuild/win32-ia32@0.25.12': optional: true '@esbuild/win32-x64@0.19.12': optional: true - '@esbuild/win32-x64@0.25.9': + '@esbuild/win32-x64@0.25.12': optional: true - '@eslint-community/eslint-utils@4.9.0(eslint@9.35.0(jiti@2.5.1))': + '@eslint-community/eslint-utils@4.9.0(eslint@9.38.0(jiti@2.6.1))': dependencies: - eslint: 9.35.0(jiti@2.5.1) + eslint: 9.38.0(jiti@2.6.1) eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.12.1': {} + '@eslint-community/regexpp@4.12.2': {} - '@eslint/config-array@0.21.0': + '@eslint/config-array@0.21.1': dependencies: - '@eslint/object-schema': 2.1.6 + '@eslint/object-schema': 2.1.7 debug: 4.4.3 minimatch: 3.1.2 transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.3.1': {} + '@eslint/config-helpers@0.4.1': + dependencies: + '@eslint/core': 0.16.0 '@eslint/core@0.15.2': dependencies: '@types/json-schema': 7.0.15 + '@eslint/core@0.16.0': + dependencies: + '@types/json-schema': 7.0.15 + '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 @@ -14175,20 +14229,27 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.35.0': {} + '@eslint/js@9.38.0': {} - '@eslint/object-schema@2.1.6': {} + '@eslint/object-schema@2.1.7': {} '@eslint/plugin-kit@0.3.5': dependencies: '@eslint/core': 0.15.2 levn: 0.4.1 - '@exodus/schemasafe@1.3.0': {} + '@eslint/plugin-kit@0.4.0': + dependencies: + '@eslint/core': 0.16.0 + levn: 0.4.1 - '@faker-js/faker@10.0.0': {} + '@extism/extism@2.0.0-rc13': {} - '@faker-js/faker@5.5.3': {} + '@extism/js-pdk@1.1.1': + dependencies: + urlpattern-polyfill: 8.0.2 + + '@faker-js/faker@10.1.0': {} '@fig/complete-commander@3.2.0(commander@11.1.0)': dependencies: @@ -14206,10 +14267,10 @@ snapshots: '@floating-ui/utils@0.2.10': {} - '@formatjs/ecma402-abstract@2.3.4': + '@formatjs/ecma402-abstract@2.3.6': dependencies: '@formatjs/fast-memoize': 2.2.7 - '@formatjs/intl-localematcher': 0.6.1 + '@formatjs/intl-localematcher': 0.6.2 decimal.js: 10.6.0 tslib: 2.8.1 @@ -14217,30 +14278,30 @@ snapshots: dependencies: tslib: 2.8.1 - '@formatjs/icu-messageformat-parser@2.11.2': + '@formatjs/icu-messageformat-parser@2.11.4': dependencies: - '@formatjs/ecma402-abstract': 2.3.4 - '@formatjs/icu-skeleton-parser': 1.8.14 + '@formatjs/ecma402-abstract': 2.3.6 + '@formatjs/icu-skeleton-parser': 1.8.16 tslib: 2.8.1 - '@formatjs/icu-skeleton-parser@1.8.14': + '@formatjs/icu-skeleton-parser@1.8.16': dependencies: - '@formatjs/ecma402-abstract': 2.3.4 + '@formatjs/ecma402-abstract': 2.3.6 tslib: 2.8.1 - '@formatjs/intl-localematcher@0.6.1': + '@formatjs/intl-localematcher@0.6.2': dependencies: tslib: 2.8.1 - '@golevelup/nestjs-discovery@4.0.3(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6)': + '@golevelup/nestjs-discovery@5.0.0(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8)': dependencies: - '@nestjs/common': 11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.6)(@nestjs/websockets@11.1.6)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.8(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.8)(@nestjs/websockets@11.1.8)(reflect-metadata@0.2.2)(rxjs@7.8.2) lodash: 4.17.21 - '@grpc/grpc-js@1.13.4': + '@grpc/grpc-js@1.14.0': dependencies: - '@grpc/proto-loader': 0.7.15 + '@grpc/proto-loader': 0.8.0 '@js-sdsl/ordered-map': 4.4.2 '@grpc/proto-loader@0.7.15': @@ -14250,6 +14311,13 @@ snapshots: protobufjs: 7.5.4 yargs: 17.7.2 + '@grpc/proto-loader@0.8.0': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.2 + protobufjs: 7.5.4 + yargs: 17.7.2 + '@hapi/hoek@9.3.0': {} '@hapi/topo@5.1.0': @@ -14267,125 +14335,136 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@img/sharp-darwin-arm64@0.34.3': + '@img/colour@1.0.0': {} + + '@img/sharp-darwin-arm64@0.34.4': optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.2.0 + '@img/sharp-libvips-darwin-arm64': 1.2.3 optional: true - '@img/sharp-darwin-x64@0.34.3': + '@img/sharp-darwin-x64@0.34.4': optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.2.0 + '@img/sharp-libvips-darwin-x64': 1.2.3 optional: true - '@img/sharp-libvips-darwin-arm64@1.2.0': + '@img/sharp-libvips-darwin-arm64@1.2.3': optional: true - '@img/sharp-libvips-darwin-x64@1.2.0': + '@img/sharp-libvips-darwin-x64@1.2.3': optional: true - '@img/sharp-libvips-linux-arm64@1.2.0': + '@img/sharp-libvips-linux-arm64@1.2.3': optional: true - '@img/sharp-libvips-linux-arm@1.2.0': + '@img/sharp-libvips-linux-arm@1.2.3': optional: true - '@img/sharp-libvips-linux-ppc64@1.2.0': + '@img/sharp-libvips-linux-ppc64@1.2.3': optional: true - '@img/sharp-libvips-linux-s390x@1.2.0': + '@img/sharp-libvips-linux-s390x@1.2.3': optional: true - '@img/sharp-libvips-linux-x64@1.2.0': + '@img/sharp-libvips-linux-x64@1.2.3': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.2.0': + '@img/sharp-libvips-linuxmusl-arm64@1.2.3': optional: true - '@img/sharp-libvips-linuxmusl-x64@1.2.0': + '@img/sharp-libvips-linuxmusl-x64@1.2.3': optional: true - '@img/sharp-linux-arm64@0.34.3': + '@img/sharp-linux-arm64@0.34.4': optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.2.0 + '@img/sharp-libvips-linux-arm64': 1.2.3 optional: true - '@img/sharp-linux-arm@0.34.3': + '@img/sharp-linux-arm@0.34.4': optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.2.0 + '@img/sharp-libvips-linux-arm': 1.2.3 optional: true - '@img/sharp-linux-ppc64@0.34.3': + '@img/sharp-linux-ppc64@0.34.4': optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.2.0 + '@img/sharp-libvips-linux-ppc64': 1.2.3 optional: true - '@img/sharp-linux-s390x@0.34.3': + '@img/sharp-linux-s390x@0.34.4': optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.2.0 + '@img/sharp-libvips-linux-s390x': 1.2.3 optional: true - '@img/sharp-linux-x64@0.34.3': + '@img/sharp-linux-x64@0.34.4': optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.2.0 + '@img/sharp-libvips-linux-x64': 1.2.3 optional: true - '@img/sharp-linuxmusl-arm64@0.34.3': + '@img/sharp-linuxmusl-arm64@0.34.4': optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.2.0 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.3 optional: true - '@img/sharp-linuxmusl-x64@0.34.3': + '@img/sharp-linuxmusl-x64@0.34.4': optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.2.0 + '@img/sharp-libvips-linuxmusl-x64': 1.2.3 optional: true - '@img/sharp-wasm32@0.34.3': + '@img/sharp-wasm32@0.34.4': dependencies: '@emnapi/runtime': 1.5.0 optional: true - '@img/sharp-win32-arm64@0.34.3': + '@img/sharp-win32-arm64@0.34.4': optional: true - '@img/sharp-win32-ia32@0.34.3': + '@img/sharp-win32-ia32@0.34.4': optional: true - '@img/sharp-win32-x64@0.34.3': + '@img/sharp-win32-x64@0.34.4': optional: true - '@immich/ui@0.29.0(@internationalized/date@3.8.2)(svelte@5.38.10)': + '@immich/justified-layout-wasm@0.4.3': {} + + '@immich/svelte-markdown-preprocess@0.1.0(svelte@5.43.0)': dependencies: + svelte: 5.43.0 + + '@immich/ui@0.43.0(@internationalized/date@3.8.2)(svelte@5.43.0)': + dependencies: + '@immich/svelte-markdown-preprocess': 0.1.0(svelte@5.43.0) '@mdi/js': 7.4.47 - bits-ui: 2.9.8(@internationalized/date@3.8.2)(svelte@5.38.10) - simple-icons: 15.15.0 - svelte: 5.38.10 + bits-ui: 2.9.8(@internationalized/date@3.8.2)(svelte@5.43.0) + luxon: 3.7.2 + simple-icons: 15.18.0 + svelte: 5.43.0 + svelte-highlight: 7.8.4 tailwind-merge: 3.3.1 - tailwind-variants: 3.1.1(tailwind-merge@3.3.1)(tailwindcss@4.1.13) - tailwindcss: 4.1.13 + tailwind-variants: 3.1.1(tailwind-merge@3.3.1)(tailwindcss@4.1.16) + tailwindcss: 4.1.16 transitivePeerDependencies: - '@internationalized/date' - '@inquirer/checkbox@4.2.1(@types/node@22.18.5)': + '@inquirer/checkbox@4.2.1(@types/node@22.19.0)': dependencies: - '@inquirer/core': 10.1.15(@types/node@22.18.5) + '@inquirer/core': 10.1.15(@types/node@22.19.0) '@inquirer/figures': 1.0.13 - '@inquirer/type': 3.0.8(@types/node@22.18.5) + '@inquirer/type': 3.0.8(@types/node@22.19.0) ansi-escapes: 4.3.2 yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 - '@inquirer/confirm@5.1.15(@types/node@22.18.5)': + '@inquirer/confirm@5.1.15(@types/node@22.19.0)': dependencies: - '@inquirer/core': 10.1.15(@types/node@22.18.5) - '@inquirer/type': 3.0.8(@types/node@22.18.5) + '@inquirer/core': 10.1.15(@types/node@22.19.0) + '@inquirer/type': 3.0.8(@types/node@22.19.0) optionalDependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 - '@inquirer/core@10.1.15(@types/node@22.18.5)': + '@inquirer/core@10.1.15(@types/node@22.19.0)': dependencies: '@inquirer/figures': 1.0.13 - '@inquirer/type': 3.0.8(@types/node@22.18.5) + '@inquirer/type': 3.0.8(@types/node@22.19.0) ansi-escapes: 4.3.2 cli-width: 4.1.0 mute-stream: 2.0.0 @@ -14393,121 +14472,121 @@ snapshots: wrap-ansi: 6.2.0 yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 - '@inquirer/editor@4.2.17(@types/node@22.18.5)': + '@inquirer/editor@4.2.17(@types/node@22.19.0)': dependencies: - '@inquirer/core': 10.1.15(@types/node@22.18.5) - '@inquirer/external-editor': 1.0.2(@types/node@22.18.5) - '@inquirer/type': 3.0.8(@types/node@22.18.5) + '@inquirer/core': 10.1.15(@types/node@22.19.0) + '@inquirer/external-editor': 1.0.2(@types/node@22.19.0) + '@inquirer/type': 3.0.8(@types/node@22.19.0) optionalDependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 - '@inquirer/expand@4.0.17(@types/node@22.18.5)': + '@inquirer/expand@4.0.17(@types/node@22.19.0)': dependencies: - '@inquirer/core': 10.1.15(@types/node@22.18.5) - '@inquirer/type': 3.0.8(@types/node@22.18.5) + '@inquirer/core': 10.1.15(@types/node@22.19.0) + '@inquirer/type': 3.0.8(@types/node@22.19.0) yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 - '@inquirer/external-editor@1.0.2(@types/node@22.18.5)': + '@inquirer/external-editor@1.0.2(@types/node@22.19.0)': dependencies: chardet: 2.1.0 iconv-lite: 0.7.0 optionalDependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 '@inquirer/figures@1.0.13': {} - '@inquirer/input@4.2.1(@types/node@22.18.5)': + '@inquirer/input@4.2.1(@types/node@22.19.0)': dependencies: - '@inquirer/core': 10.1.15(@types/node@22.18.5) - '@inquirer/type': 3.0.8(@types/node@22.18.5) + '@inquirer/core': 10.1.15(@types/node@22.19.0) + '@inquirer/type': 3.0.8(@types/node@22.19.0) optionalDependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 - '@inquirer/number@3.0.17(@types/node@22.18.5)': + '@inquirer/number@3.0.17(@types/node@22.19.0)': dependencies: - '@inquirer/core': 10.1.15(@types/node@22.18.5) - '@inquirer/type': 3.0.8(@types/node@22.18.5) + '@inquirer/core': 10.1.15(@types/node@22.19.0) + '@inquirer/type': 3.0.8(@types/node@22.19.0) optionalDependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 - '@inquirer/password@4.0.17(@types/node@22.18.5)': + '@inquirer/password@4.0.17(@types/node@22.19.0)': dependencies: - '@inquirer/core': 10.1.15(@types/node@22.18.5) - '@inquirer/type': 3.0.8(@types/node@22.18.5) + '@inquirer/core': 10.1.15(@types/node@22.19.0) + '@inquirer/type': 3.0.8(@types/node@22.19.0) ansi-escapes: 4.3.2 optionalDependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 - '@inquirer/prompts@7.3.2(@types/node@22.18.5)': + '@inquirer/prompts@7.3.2(@types/node@22.19.0)': dependencies: - '@inquirer/checkbox': 4.2.1(@types/node@22.18.5) - '@inquirer/confirm': 5.1.15(@types/node@22.18.5) - '@inquirer/editor': 4.2.17(@types/node@22.18.5) - '@inquirer/expand': 4.0.17(@types/node@22.18.5) - '@inquirer/input': 4.2.1(@types/node@22.18.5) - '@inquirer/number': 3.0.17(@types/node@22.18.5) - '@inquirer/password': 4.0.17(@types/node@22.18.5) - '@inquirer/rawlist': 4.1.5(@types/node@22.18.5) - '@inquirer/search': 3.1.0(@types/node@22.18.5) - '@inquirer/select': 4.3.1(@types/node@22.18.5) + '@inquirer/checkbox': 4.2.1(@types/node@22.19.0) + '@inquirer/confirm': 5.1.15(@types/node@22.19.0) + '@inquirer/editor': 4.2.17(@types/node@22.19.0) + '@inquirer/expand': 4.0.17(@types/node@22.19.0) + '@inquirer/input': 4.2.1(@types/node@22.19.0) + '@inquirer/number': 3.0.17(@types/node@22.19.0) + '@inquirer/password': 4.0.17(@types/node@22.19.0) + '@inquirer/rawlist': 4.1.5(@types/node@22.19.0) + '@inquirer/search': 3.1.0(@types/node@22.19.0) + '@inquirer/select': 4.3.1(@types/node@22.19.0) optionalDependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 - '@inquirer/prompts@7.8.0(@types/node@22.18.5)': + '@inquirer/prompts@7.8.0(@types/node@22.19.0)': dependencies: - '@inquirer/checkbox': 4.2.1(@types/node@22.18.5) - '@inquirer/confirm': 5.1.15(@types/node@22.18.5) - '@inquirer/editor': 4.2.17(@types/node@22.18.5) - '@inquirer/expand': 4.0.17(@types/node@22.18.5) - '@inquirer/input': 4.2.1(@types/node@22.18.5) - '@inquirer/number': 3.0.17(@types/node@22.18.5) - '@inquirer/password': 4.0.17(@types/node@22.18.5) - '@inquirer/rawlist': 4.1.5(@types/node@22.18.5) - '@inquirer/search': 3.1.0(@types/node@22.18.5) - '@inquirer/select': 4.3.1(@types/node@22.18.5) + '@inquirer/checkbox': 4.2.1(@types/node@22.19.0) + '@inquirer/confirm': 5.1.15(@types/node@22.19.0) + '@inquirer/editor': 4.2.17(@types/node@22.19.0) + '@inquirer/expand': 4.0.17(@types/node@22.19.0) + '@inquirer/input': 4.2.1(@types/node@22.19.0) + '@inquirer/number': 3.0.17(@types/node@22.19.0) + '@inquirer/password': 4.0.17(@types/node@22.19.0) + '@inquirer/rawlist': 4.1.5(@types/node@22.19.0) + '@inquirer/search': 3.1.0(@types/node@22.19.0) + '@inquirer/select': 4.3.1(@types/node@22.19.0) optionalDependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 - '@inquirer/rawlist@4.1.5(@types/node@22.18.5)': + '@inquirer/rawlist@4.1.5(@types/node@22.19.0)': dependencies: - '@inquirer/core': 10.1.15(@types/node@22.18.5) - '@inquirer/type': 3.0.8(@types/node@22.18.5) + '@inquirer/core': 10.1.15(@types/node@22.19.0) + '@inquirer/type': 3.0.8(@types/node@22.19.0) yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 - '@inquirer/search@3.1.0(@types/node@22.18.5)': + '@inquirer/search@3.1.0(@types/node@22.19.0)': dependencies: - '@inquirer/core': 10.1.15(@types/node@22.18.5) + '@inquirer/core': 10.1.15(@types/node@22.19.0) '@inquirer/figures': 1.0.13 - '@inquirer/type': 3.0.8(@types/node@22.18.5) + '@inquirer/type': 3.0.8(@types/node@22.19.0) yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 - '@inquirer/select@4.3.1(@types/node@22.18.5)': + '@inquirer/select@4.3.1(@types/node@22.19.0)': dependencies: - '@inquirer/core': 10.1.15(@types/node@22.18.5) + '@inquirer/core': 10.1.15(@types/node@22.19.0) '@inquirer/figures': 1.0.13 - '@inquirer/type': 3.0.8(@types/node@22.18.5) + '@inquirer/type': 3.0.8(@types/node@22.19.0) ansi-escapes: 4.3.2 yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 - '@inquirer/type@3.0.8(@types/node@22.18.5)': + '@inquirer/type@3.0.8(@types/node@22.19.0)': optionalDependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 '@internationalized/date@3.8.2': dependencies: '@swc/helpers': 0.5.17 - '@ioredis/commands@1.3.0': {} + '@ioredis/commands@1.4.0': {} '@isaacs/balanced-match@4.0.1': {} @@ -14539,36 +14618,72 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 22.18.5 - '@types/yargs': 17.0.33 + '@types/node': 22.19.0 + '@types/yargs': 17.0.34 chalk: 4.1.2 '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/remapping@2.3.5': dependencies: '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/resolve-uri@3.1.2': {} - '@jridgewell/source-map@0.3.6': + '@jridgewell/source-map@0.3.11': dependencies: '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/sourcemap-codec@1.5.5': {} - '@jridgewell/trace-mapping@0.3.30': + '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 '@js-sdsl/ordered-map@4.4.2': {} + '@jsonjoy.com/base64@1.1.2(tslib@2.8.1)': + dependencies: + tslib: 2.8.1 + + '@jsonjoy.com/buffers@1.2.1(tslib@2.8.1)': + dependencies: + tslib: 2.8.1 + + '@jsonjoy.com/codegen@1.0.0(tslib@2.8.1)': + dependencies: + tslib: 2.8.1 + + '@jsonjoy.com/json-pack@1.21.0(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/base64': 1.1.2(tslib@2.8.1) + '@jsonjoy.com/buffers': 1.2.1(tslib@2.8.1) + '@jsonjoy.com/codegen': 1.0.0(tslib@2.8.1) + '@jsonjoy.com/json-pointer': 1.0.2(tslib@2.8.1) + '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) + hyperdyperid: 1.2.0 + thingies: 2.5.0(tslib@2.8.1) + tree-dump: 1.1.0(tslib@2.8.1) + tslib: 2.8.1 + + '@jsonjoy.com/json-pointer@1.0.2(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/codegen': 1.0.0(tslib@2.8.1) + '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) + tslib: 2.8.1 + + '@jsonjoy.com/util@1.9.0(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/buffers': 1.2.1(tslib@2.8.1) + '@jsonjoy.com/codegen': 1.0.0(tslib@2.8.1) + tslib: 2.8.1 + '@koa/cors@5.0.0': dependencies: vary: 1.1.2 @@ -14582,12 +14697,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@koddsson/eslint-plugin-tscompat@0.2.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2)': + '@koddsson/eslint-plugin-tscompat@0.2.0(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@mdn/browser-compat-data': 6.0.27 - '@typescript-eslint/type-utils': 8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/utils': 8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) - browserslist: 4.25.3 + '@typescript-eslint/type-utils': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + browserslist: 4.27.0 transitivePeerDependencies: - eslint - supports-color @@ -14616,14 +14731,14 @@ snapshots: '@mapbox/node-pre-gyp@1.0.11': dependencies: - detect-libc: 2.1.0 + detect-libc: 2.1.2 https-proxy-agent: 5.0.1 make-dir: 3.1.0 node-fetch: 2.7.0 nopt: 5.0.0 npmlog: 5.0.1 rimraf: 3.0.2 - semver: 7.7.2 + semver: 7.7.3 tar: 6.2.1 transitivePeerDependencies: - encoding @@ -14632,14 +14747,14 @@ snapshots: '@mapbox/node-pre-gyp@1.0.11(encoding@0.1.13)': dependencies: - detect-libc: 2.1.0 + detect-libc: 2.1.2 https-proxy-agent: 5.0.1 make-dir: 3.1.0 node-fetch: 2.7.0(encoding@0.1.13) nopt: 5.0.0 npmlog: 5.0.1 rimraf: 3.0.2 - semver: 7.7.2 + semver: 7.7.3 tar: 6.2.1 transitivePeerDependencies: - encoding @@ -14669,7 +14784,7 @@ snapshots: '@mapbox/whoots-js@3.1.0': {} - '@maplibre/maplibre-gl-style-spec@23.3.0': + '@maplibre/maplibre-gl-style-spec@24.3.1': dependencies: '@mapbox/jsonlint-lines-primitives': 2.0.2 '@mapbox/unitbezier': 0.0.1 @@ -14699,12 +14814,13 @@ snapshots: '@mdn/browser-compat-data@6.0.27': {} - '@mdx-js/mdx@3.1.0(acorn@8.15.0)': + '@mdx-js/mdx@3.1.1': dependencies: '@types/estree': 1.0.8 '@types/estree-jsx': 1.0.5 '@types/hast': 3.0.4 '@types/mdx': 2.0.13 + acorn: 8.15.0 collapse-white-space: 2.1.0 devlop: 1.1.0 estree-util-is-identifier-name: 3.0.0 @@ -14713,41 +14829,29 @@ snapshots: hast-util-to-jsx-runtime: 2.3.6 markdown-extensions: 2.0.0 recma-build-jsx: 1.0.0 - recma-jsx: 1.0.0(acorn@8.15.0) + recma-jsx: 1.0.1(acorn@8.15.0) recma-stringify: 1.0.0 rehype-recma: 1.0.0 - remark-mdx: 3.1.0 + remark-mdx: 3.1.1 remark-parse: 11.0.0 remark-rehype: 11.1.2 - source-map: 0.7.4 + source-map: 0.7.6 unified: 11.0.5 unist-util-position-from-estree: 2.0.0 unist-util-stringify-position: 4.0.0 unist-util-visit: 5.0.0 vfile: 6.0.3 transitivePeerDependencies: - - acorn - supports-color - '@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1)': + '@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1)': dependencies: '@types/mdx': 2.0.13 - '@types/react': 19.1.13 + '@types/react': 19.2.2 react: 18.3.1 '@microsoft/tsdoc@0.15.1': {} - '@monaco-editor/loader@1.5.0': - dependencies: - state-local: 1.0.7 - - '@monaco-editor/react@4.7.0(monaco-editor@0.31.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@monaco-editor/loader': 1.5.0 - monaco-editor: 0.31.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': optional: true @@ -14768,32 +14872,32 @@ snapshots: '@namnode/store@0.1.0': {} - '@nestjs/bull-shared@11.0.3(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6)': + '@nestjs/bull-shared@11.0.4(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8)': dependencies: - '@nestjs/common': 11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.6)(@nestjs/websockets@11.1.6)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.8(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.8)(@nestjs/websockets@11.1.8)(reflect-metadata@0.2.2)(rxjs@7.8.2) tslib: 2.8.1 - '@nestjs/bullmq@11.0.3(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6)(bullmq@5.58.5)': + '@nestjs/bullmq@11.0.4(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8)(bullmq@5.62.1)': dependencies: - '@nestjs/bull-shared': 11.0.3(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6) - '@nestjs/common': 11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.6)(@nestjs/websockets@11.1.6)(reflect-metadata@0.2.2)(rxjs@7.8.2) - bullmq: 5.58.5 + '@nestjs/bull-shared': 11.0.4(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8) + '@nestjs/common': 11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.8(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.8)(@nestjs/websockets@11.1.8)(reflect-metadata@0.2.2)(rxjs@7.8.2) + bullmq: 5.62.1 tslib: 2.8.1 - '@nestjs/cli@11.0.10(@swc/core@1.13.5(@swc/helpers@0.5.17))(@types/node@22.18.5)': + '@nestjs/cli@11.0.10(@swc/core@1.14.0(@swc/helpers@0.5.17))(@types/node@22.19.0)': dependencies: '@angular-devkit/core': 19.2.15(chokidar@4.0.3) '@angular-devkit/schematics': 19.2.15(chokidar@4.0.3) - '@angular-devkit/schematics-cli': 19.2.15(@types/node@22.18.5)(chokidar@4.0.3) - '@inquirer/prompts': 7.8.0(@types/node@22.18.5) - '@nestjs/schematics': 11.0.7(chokidar@4.0.3)(typescript@5.8.3) + '@angular-devkit/schematics-cli': 19.2.15(@types/node@22.19.0)(chokidar@4.0.3) + '@inquirer/prompts': 7.8.0(@types/node@22.19.0) + '@nestjs/schematics': 11.0.9(chokidar@4.0.3)(typescript@5.8.3) ansis: 4.1.0 chokidar: 4.0.3 cli-table3: 0.6.5 commander: 4.1.1 - fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.8.3)(webpack@5.100.2(@swc/core@1.13.5(@swc/helpers@0.5.17))) + fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.8.3)(webpack@5.100.2(@swc/core@1.14.0(@swc/helpers@0.5.17))) glob: 11.0.3 node-emoji: 1.11.0 ora: 5.4.1 @@ -14801,21 +14905,21 @@ snapshots: tsconfig-paths: 4.2.0 tsconfig-paths-webpack-plugin: 4.2.0 typescript: 5.8.3 - webpack: 5.100.2(@swc/core@1.13.5(@swc/helpers@0.5.17)) + webpack: 5.100.2(@swc/core@1.14.0(@swc/helpers@0.5.17)) webpack-node-externals: 3.0.0 optionalDependencies: - '@swc/core': 1.13.5(@swc/helpers@0.5.17) + '@swc/core': 1.14.0(@swc/helpers@0.5.17) transitivePeerDependencies: - '@types/node' - esbuild - uglify-js - webpack-cli - '@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: file-type: 21.0.0 iterare: 1.2.1 - load-esm: 1.0.2 + load-esm: 1.0.3 reflect-metadata: 0.2.2 rxjs: 7.8.2 tslib: 2.8.1 @@ -14826,45 +14930,45 @@ snapshots: transitivePeerDependencies: - supports-color - '@nestjs/core@11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.6)(@nestjs/websockets@11.1.6)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@nestjs/core@11.1.8(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.8)(@nestjs/websockets@11.1.8)(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nuxt/opencollective': 0.4.1 fast-safe-stringify: 2.1.1 iterare: 1.2.1 - path-to-regexp: 8.2.0 + path-to-regexp: 8.3.0 reflect-metadata: 0.2.2 rxjs: 7.8.2 tslib: 2.8.1 uid: 2.0.2 optionalDependencies: - '@nestjs/platform-express': 11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6) - '@nestjs/websockets': 11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6)(@nestjs/platform-socket.io@11.1.6)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/platform-express': 11.1.8(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8) + '@nestjs/websockets': 11.1.8(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8)(@nestjs/platform-socket.io@11.1.8)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/mapped-types@2.1.0(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)': + '@nestjs/mapped-types@2.1.0(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)': dependencies: - '@nestjs/common': 11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) reflect-metadata: 0.2.2 optionalDependencies: class-transformer: 0.5.1 class-validator: 0.14.2 - '@nestjs/platform-express@11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6)': + '@nestjs/platform-express@11.1.8(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8)': dependencies: - '@nestjs/common': 11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.6)(@nestjs/websockets@11.1.6)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.8(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.8)(@nestjs/websockets@11.1.8)(reflect-metadata@0.2.2)(rxjs@7.8.2) cors: 2.8.5 express: 5.1.0 multer: 2.0.2 - path-to-regexp: 8.2.0 + path-to-regexp: 8.3.0 tslib: 2.8.1 transitivePeerDependencies: - supports-color - '@nestjs/platform-socket.io@11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.6)(rxjs@7.8.2)': + '@nestjs/platform-socket.io@11.1.8(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.8)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/websockets': 11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6)(@nestjs/platform-socket.io@11.1.6)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/websockets': 11.1.8(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8)(@nestjs/platform-socket.io@11.1.8)(reflect-metadata@0.2.2)(rxjs@7.8.2) rxjs: 7.8.2 socket.io: 4.8.1 tslib: 2.8.1 @@ -14873,68 +14977,68 @@ snapshots: - supports-color - utf-8-validate - '@nestjs/schedule@6.0.0(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6)': + '@nestjs/schedule@6.0.1(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8)': dependencies: - '@nestjs/common': 11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.6)(@nestjs/websockets@11.1.6)(reflect-metadata@0.2.2)(rxjs@7.8.2) - cron: 4.3.0 + '@nestjs/common': 11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.8(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.8)(@nestjs/websockets@11.1.8)(reflect-metadata@0.2.2)(rxjs@7.8.2) + cron: 4.3.3 - '@nestjs/schematics@11.0.7(chokidar@4.0.3)(typescript@5.8.3)': + '@nestjs/schematics@11.0.9(chokidar@4.0.3)(typescript@5.8.3)': dependencies: - '@angular-devkit/core': 19.2.15(chokidar@4.0.3) - '@angular-devkit/schematics': 19.2.15(chokidar@4.0.3) - comment-json: 4.2.5 + '@angular-devkit/core': 19.2.17(chokidar@4.0.3) + '@angular-devkit/schematics': 19.2.17(chokidar@4.0.3) + comment-json: 4.4.1 jsonc-parser: 3.3.1 pluralize: 8.0.0 typescript: 5.8.3 transitivePeerDependencies: - chokidar - '@nestjs/schematics@11.0.7(chokidar@4.0.3)(typescript@5.9.2)': + '@nestjs/schematics@11.0.9(chokidar@4.0.3)(typescript@5.9.3)': dependencies: - '@angular-devkit/core': 19.2.15(chokidar@4.0.3) - '@angular-devkit/schematics': 19.2.15(chokidar@4.0.3) - comment-json: 4.2.5 + '@angular-devkit/core': 19.2.17(chokidar@4.0.3) + '@angular-devkit/schematics': 19.2.17(chokidar@4.0.3) + comment-json: 4.4.1 jsonc-parser: 3.3.1 pluralize: 8.0.0 - typescript: 5.9.2 + typescript: 5.9.3 transitivePeerDependencies: - chokidar - '@nestjs/swagger@11.2.0(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)': + '@nestjs/swagger@11.2.1(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)': dependencies: '@microsoft/tsdoc': 0.15.1 - '@nestjs/common': 11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.6)(@nestjs/websockets@11.1.6)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/mapped-types': 2.1.0(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2) + '@nestjs/common': 11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.8(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.8)(@nestjs/websockets@11.1.8)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/mapped-types': 2.1.0(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2) js-yaml: 4.1.0 lodash: 4.17.21 - path-to-regexp: 8.2.0 + path-to-regexp: 8.3.0 reflect-metadata: 0.2.2 - swagger-ui-dist: 5.21.0 + swagger-ui-dist: 5.29.4 optionalDependencies: class-transformer: 0.5.1 class-validator: 0.14.2 - '@nestjs/testing@11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6)(@nestjs/platform-express@11.1.6)': + '@nestjs/testing@11.1.8(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8)(@nestjs/platform-express@11.1.8)': dependencies: - '@nestjs/common': 11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.6)(@nestjs/websockets@11.1.6)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.8(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.8)(@nestjs/websockets@11.1.8)(reflect-metadata@0.2.2)(rxjs@7.8.2) tslib: 2.8.1 optionalDependencies: - '@nestjs/platform-express': 11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6) + '@nestjs/platform-express': 11.1.8(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8) - '@nestjs/websockets@11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6)(@nestjs/platform-socket.io@11.1.6)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@nestjs/websockets@11.1.8(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8)(@nestjs/platform-socket.io@11.1.8)(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.6)(@nestjs/websockets@11.1.6)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.8(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.8)(@nestjs/websockets@11.1.8)(reflect-metadata@0.2.2)(rxjs@7.8.2) iterare: 1.2.1 object-hash: 3.0.0 reflect-metadata: 0.2.2 rxjs: 7.8.2 tslib: 2.8.1 optionalDependencies: - '@nestjs/platform-socket.io': 11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.6)(rxjs@7.8.2) + '@nestjs/platform-socket.io': 11.1.8(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.8)(rxjs@7.8.2) '@noble/hashes@1.8.0': {} @@ -14962,7 +15066,7 @@ snapshots: '@npmcli/fs@4.0.0': dependencies: - semver: 7.7.2 + semver: 7.7.3 '@nuxt/opencollective@0.4.1': dependencies: @@ -14970,124 +15074,124 @@ snapshots: '@oazapfts/runtime@1.0.4': {} - '@opentelemetry/api-logs@0.205.0': + '@opentelemetry/api-logs@0.207.0': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/api@1.9.0': {} - '@opentelemetry/context-async-hooks@2.1.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core@2.1.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.37.0 - '@opentelemetry/exporter-logs-otlp-grpc@0.205.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-logs-otlp-grpc@0.207.0(@opentelemetry/api@1.9.0)': dependencies: - '@grpc/grpc-js': 1.13.4 + '@grpc/grpc-js': 1.14.0 '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-grpc-exporter-base': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-logs': 0.205.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.207.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-logs-otlp-http@0.205.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-logs-otlp-http@0.207.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/api-logs': 0.205.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-logs': 0.205.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api-logs': 0.207.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.207.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-logs-otlp-proto@0.205.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-logs-otlp-proto@0.207.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/api-logs': 0.205.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-logs': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api-logs': 0.207.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-metrics-otlp-grpc@0.205.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-metrics-otlp-grpc@0.207.0(@opentelemetry/api@1.9.0)': dependencies: - '@grpc/grpc-js': 1.13.4 + '@grpc/grpc-js': 1.14.0 '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-metrics-otlp-http': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-grpc-exporter-base': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-metrics': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-http': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.2.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-metrics-otlp-http@0.205.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-metrics-otlp-http@0.207.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-metrics': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.2.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-metrics-otlp-proto@0.205.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-metrics-otlp-proto@0.207.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-metrics-otlp-http': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-metrics': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-http': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.2.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-prometheus@0.205.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-prometheus@0.207.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-metrics': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.2.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-trace-otlp-grpc@0.205.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-trace-otlp-grpc@0.207.0(@opentelemetry/api@1.9.0)': dependencies: - '@grpc/grpc-js': 1.13.4 + '@grpc/grpc-js': 1.14.0 '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-grpc-exporter-base': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-trace-otlp-http@0.205.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-trace-otlp-http@0.207.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-trace-otlp-proto@0.205.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-trace-otlp-proto@0.207.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-zipkin@2.1.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-zipkin@2.2.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.37.0 '@opentelemetry/host-metrics@0.36.0(@opentelemetry/api@1.9.0)': @@ -15095,158 +15199,157 @@ snapshots: '@opentelemetry/api': 1.9.0 systeminformation: 5.23.8 - '@opentelemetry/instrumentation-http@0.205.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-http@0.207.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.205.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.207.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.37.0 forwarded-parse: 2.1.2 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-ioredis@0.53.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-ioredis@0.55.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/redis-common': 0.38.0 + '@opentelemetry/instrumentation': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/redis-common': 0.38.2 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-nestjs-core@0.54.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.207.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.37.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-nestjs-core@0.51.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-pg@0.60.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.205.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.207.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.37.0 - transitivePeerDependencies: - - supports-color - - '@opentelemetry/instrumentation-pg@0.58.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 - '@opentelemetry/sql-common': 0.41.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.0) '@types/pg': 8.15.5 '@types/pg-pool': 2.0.6 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation@0.205.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation@0.207.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/api-logs': 0.205.0 - import-in-the-middle: 1.14.2 - require-in-the-middle: 7.5.2 + '@opentelemetry/api-logs': 0.207.0 + import-in-the-middle: 2.0.0 + require-in-the-middle: 8.0.0 transitivePeerDependencies: - supports-color - '@opentelemetry/otlp-exporter-base@0.205.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/otlp-exporter-base@0.207.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.205.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.207.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-grpc-exporter-base@0.205.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/otlp-grpc-exporter-base@0.207.0(@opentelemetry/api@1.9.0)': dependencies: - '@grpc/grpc-js': 1.13.4 + '@grpc/grpc-js': 1.14.0 '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.205.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.207.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer@0.205.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/otlp-transformer@0.207.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/api-logs': 0.205.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-logs': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-metrics': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api-logs': 0.207.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.0) protobufjs: 7.5.4 - '@opentelemetry/propagator-b3@2.1.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/propagator-b3@2.2.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) - '@opentelemetry/propagator-jaeger@2.1.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/propagator-jaeger@2.2.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) - '@opentelemetry/redis-common@0.38.0': {} + '@opentelemetry/redis-common@0.38.2': {} - '@opentelemetry/resources@2.1.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/resources@2.2.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.37.0 - '@opentelemetry/sdk-logs@0.205.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/sdk-logs@0.207.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/api-logs': 0.205.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api-logs': 0.207.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-metrics@2.1.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/sdk-metrics@2.2.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-node@0.205.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/sdk-node@0.207.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/api-logs': 0.205.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-logs-otlp-grpc': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-logs-otlp-http': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-logs-otlp-proto': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-metrics-otlp-grpc': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-metrics-otlp-http': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-metrics-otlp-proto': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-prometheus': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-trace-otlp-grpc': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-trace-otlp-http': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-trace-otlp-proto': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-zipkin': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/propagator-b3': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/propagator-jaeger': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-logs': 0.205.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-metrics': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-node': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api-logs': 0.207.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-grpc': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-http': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-proto': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-grpc': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-http': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-proto': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-prometheus': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-grpc': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-http': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-proto': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-zipkin': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-b3': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-jaeger': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-node': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.37.0 transitivePeerDependencies: - supports-color - '@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.37.0 - '@opentelemetry/sdk-trace-node@2.1.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/sdk-trace-node@2.2.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/context-async-hooks': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/context-async-hooks': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions@1.37.0': {} - '@opentelemetry/sql-common@0.41.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/sql-common@0.41.2(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) '@paralleldrive/cuid2@2.2.2': dependencies: @@ -15276,16 +15379,16 @@ snapshots: '@photo-sphere-viewer/core': 5.14.0 three: 0.180.0 - '@photostructure/tz-lookup@11.2.0': {} + '@photostructure/tz-lookup@11.3.0': {} '@pkgjs/parseargs@0.11.0': optional: true '@pkgr/core@0.2.9': {} - '@playwright/test@1.55.0': + '@playwright/test@1.56.1': dependencies: - playwright: 1.55.0 + playwright: 1.56.1 '@pnpm/config.env-replace@1.1.0': {} @@ -15324,197 +15427,190 @@ snapshots: '@protobufjs/utf8@1.1.0': {} - '@react-email/body@0.1.0(react@19.1.1)': + '@react-email/body@0.1.0(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 - '@react-email/button@0.2.0(react@19.1.1)': + '@react-email/button@0.2.0(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 - '@react-email/code-block@0.1.0(react@19.1.1)': + '@react-email/code-block@0.1.0(react@19.2.0)': dependencies: prismjs: 1.30.0 - react: 19.1.1 + react: 19.2.0 - '@react-email/code-inline@0.0.5(react@19.1.1)': + '@react-email/code-inline@0.0.5(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 - '@react-email/column@0.0.13(react@19.1.1)': + '@react-email/column@0.0.13(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 - '@react-email/components@0.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-email/components@0.5.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@react-email/body': 0.1.0(react@19.1.1) - '@react-email/button': 0.2.0(react@19.1.1) - '@react-email/code-block': 0.1.0(react@19.1.1) - '@react-email/code-inline': 0.0.5(react@19.1.1) - '@react-email/column': 0.0.13(react@19.1.1) - '@react-email/container': 0.0.15(react@19.1.1) - '@react-email/font': 0.0.9(react@19.1.1) - '@react-email/head': 0.0.12(react@19.1.1) - '@react-email/heading': 0.0.15(react@19.1.1) - '@react-email/hr': 0.0.11(react@19.1.1) - '@react-email/html': 0.0.11(react@19.1.1) - '@react-email/img': 0.0.11(react@19.1.1) - '@react-email/link': 0.0.12(react@19.1.1) - '@react-email/markdown': 0.0.15(react@19.1.1) - '@react-email/preview': 0.0.13(react@19.1.1) - '@react-email/render': 1.2.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-email/row': 0.0.12(react@19.1.1) - '@react-email/section': 0.0.16(react@19.1.1) - '@react-email/tailwind': 1.2.2(react@19.1.1) - '@react-email/text': 0.1.5(react@19.1.1) - react: 19.1.1 + '@react-email/body': 0.1.0(react@19.2.0) + '@react-email/button': 0.2.0(react@19.2.0) + '@react-email/code-block': 0.1.0(react@19.2.0) + '@react-email/code-inline': 0.0.5(react@19.2.0) + '@react-email/column': 0.0.13(react@19.2.0) + '@react-email/container': 0.0.15(react@19.2.0) + '@react-email/font': 0.0.9(react@19.2.0) + '@react-email/head': 0.0.12(react@19.2.0) + '@react-email/heading': 0.0.15(react@19.2.0) + '@react-email/hr': 0.0.11(react@19.2.0) + '@react-email/html': 0.0.11(react@19.2.0) + '@react-email/img': 0.0.11(react@19.2.0) + '@react-email/link': 0.0.12(react@19.2.0) + '@react-email/markdown': 0.0.16(react@19.2.0) + '@react-email/preview': 0.0.13(react@19.2.0) + '@react-email/render': 1.4.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@react-email/row': 0.0.12(react@19.2.0) + '@react-email/section': 0.0.16(react@19.2.0) + '@react-email/tailwind': 1.2.2(react@19.2.0) + '@react-email/text': 0.1.5(react@19.2.0) + react: 19.2.0 transitivePeerDependencies: - react-dom - '@react-email/container@0.0.15(react@19.1.1)': + '@react-email/container@0.0.15(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 - '@react-email/font@0.0.9(react@19.1.1)': + '@react-email/font@0.0.9(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 - '@react-email/head@0.0.12(react@19.1.1)': + '@react-email/head@0.0.12(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 - '@react-email/heading@0.0.15(react@19.1.1)': + '@react-email/heading@0.0.15(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 - '@react-email/hr@0.0.11(react@19.1.1)': + '@react-email/hr@0.0.11(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 - '@react-email/html@0.0.11(react@19.1.1)': + '@react-email/html@0.0.11(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 - '@react-email/img@0.0.11(react@19.1.1)': + '@react-email/img@0.0.11(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 - '@react-email/link@0.0.12(react@19.1.1)': + '@react-email/link@0.0.12(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 - '@react-email/markdown@0.0.15(react@19.1.1)': + '@react-email/markdown@0.0.16(react@19.2.0)': dependencies: - md-to-react-email: 5.0.5(react@19.1.1) - react: 19.1.1 + marked: 15.0.12 + react: 19.2.0 - '@react-email/preview@0.0.13(react@19.1.1)': + '@react-email/preview@0.0.13(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 - '@react-email/render@1.2.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-email/render@1.4.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: html-to-text: 9.0.5 prettier: 3.6.2 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) react-promise-suspense: 0.3.4 - '@react-email/row@0.0.12(react@19.1.1)': + '@react-email/row@0.0.12(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 - '@react-email/section@0.0.16(react@19.1.1)': + '@react-email/section@0.0.16(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 - '@react-email/tailwind@1.2.2(react@19.1.1)': + '@react-email/tailwind@1.2.2(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 - '@react-email/text@0.1.5(react@19.1.1)': + '@react-email/text@0.1.5(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 - '@reduxjs/toolkit@1.9.7(react-redux@7.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': - dependencies: - immer: 9.0.21 - redux: 4.2.1 - redux-thunk: 2.4.2(redux@4.2.1) - reselect: 4.1.8 - optionalDependencies: - react: 18.3.1 - react-redux: 7.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - - '@rollup/pluginutils@5.3.0(rollup@4.50.1)': + '@rollup/pluginutils@5.3.0(rollup@4.52.5)': dependencies: '@types/estree': 1.0.8 estree-walker: 2.0.2 picomatch: 4.0.3 optionalDependencies: - rollup: 4.50.1 + rollup: 4.52.5 - '@rollup/rollup-android-arm-eabi@4.50.1': + '@rollup/rollup-android-arm-eabi@4.52.5': optional: true - '@rollup/rollup-android-arm64@4.50.1': + '@rollup/rollup-android-arm64@4.52.5': optional: true - '@rollup/rollup-darwin-arm64@4.50.1': + '@rollup/rollup-darwin-arm64@4.52.5': optional: true - '@rollup/rollup-darwin-x64@4.50.1': + '@rollup/rollup-darwin-x64@4.52.5': optional: true - '@rollup/rollup-freebsd-arm64@4.50.1': + '@rollup/rollup-freebsd-arm64@4.52.5': optional: true - '@rollup/rollup-freebsd-x64@4.50.1': + '@rollup/rollup-freebsd-x64@4.52.5': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.50.1': + '@rollup/rollup-linux-arm-gnueabihf@4.52.5': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.50.1': + '@rollup/rollup-linux-arm-musleabihf@4.52.5': optional: true - '@rollup/rollup-linux-arm64-gnu@4.50.1': + '@rollup/rollup-linux-arm64-gnu@4.52.5': optional: true - '@rollup/rollup-linux-arm64-musl@4.50.1': + '@rollup/rollup-linux-arm64-musl@4.52.5': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.50.1': + '@rollup/rollup-linux-loong64-gnu@4.52.5': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.50.1': + '@rollup/rollup-linux-ppc64-gnu@4.52.5': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.50.1': + '@rollup/rollup-linux-riscv64-gnu@4.52.5': optional: true - '@rollup/rollup-linux-riscv64-musl@4.50.1': + '@rollup/rollup-linux-riscv64-musl@4.52.5': optional: true - '@rollup/rollup-linux-s390x-gnu@4.50.1': + '@rollup/rollup-linux-s390x-gnu@4.52.5': optional: true - '@rollup/rollup-linux-x64-gnu@4.50.1': + '@rollup/rollup-linux-x64-gnu@4.52.5': optional: true - '@rollup/rollup-linux-x64-musl@4.50.1': + '@rollup/rollup-linux-x64-musl@4.52.5': optional: true - '@rollup/rollup-openharmony-arm64@4.50.1': + '@rollup/rollup-openharmony-arm64@4.52.5': optional: true - '@rollup/rollup-win32-arm64-msvc@4.50.1': + '@rollup/rollup-win32-arm64-msvc@4.52.5': optional: true - '@rollup/rollup-win32-ia32-msvc@4.50.1': + '@rollup/rollup-win32-ia32-msvc@4.52.5': optional: true - '@rollup/rollup-win32-x64-msvc@4.50.1': + '@rollup/rollup-win32-x64-gnu@4.52.5': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.52.5': optional: true '@scarf/scarf@1.4.0': {} @@ -15554,197 +15650,196 @@ snapshots: micromark-util-character: 1.2.0 micromark-util-symbol: 1.1.0 - '@smithy/abort-controller@4.1.1': + '@smithy/abort-controller@4.2.3': dependencies: - '@smithy/types': 4.5.0 + '@smithy/types': 4.8.0 tslib: 2.8.1 - '@smithy/config-resolver@4.2.2': + '@smithy/config-resolver@4.4.0': dependencies: - '@smithy/node-config-provider': 4.2.2 - '@smithy/types': 4.5.0 - '@smithy/util-config-provider': 4.1.0 - '@smithy/util-middleware': 4.1.1 + '@smithy/node-config-provider': 4.3.3 + '@smithy/types': 4.8.0 + '@smithy/util-config-provider': 4.2.0 + '@smithy/util-endpoints': 3.2.3 + '@smithy/util-middleware': 4.2.3 tslib: 2.8.1 - '@smithy/core@3.11.0': + '@smithy/core@3.17.1': dependencies: - '@smithy/middleware-serde': 4.1.1 - '@smithy/protocol-http': 5.2.1 - '@smithy/types': 4.5.0 - '@smithy/util-base64': 4.1.0 - '@smithy/util-body-length-browser': 4.1.0 - '@smithy/util-middleware': 4.1.1 - '@smithy/util-stream': 4.3.1 - '@smithy/util-utf8': 4.1.0 - '@types/uuid': 9.0.8 - tslib: 2.8.1 - uuid: 9.0.1 - - '@smithy/credential-provider-imds@4.1.2': - dependencies: - '@smithy/node-config-provider': 4.2.2 - '@smithy/property-provider': 4.1.1 - '@smithy/types': 4.5.0 - '@smithy/url-parser': 4.1.1 + '@smithy/middleware-serde': 4.2.3 + '@smithy/protocol-http': 5.3.3 + '@smithy/types': 4.8.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-middleware': 4.2.3 + '@smithy/util-stream': 4.5.4 + '@smithy/util-utf8': 4.2.0 + '@smithy/uuid': 1.1.0 tslib: 2.8.1 - '@smithy/fetch-http-handler@5.2.1': + '@smithy/credential-provider-imds@4.2.3': dependencies: - '@smithy/protocol-http': 5.2.1 - '@smithy/querystring-builder': 4.1.1 - '@smithy/types': 4.5.0 - '@smithy/util-base64': 4.1.0 + '@smithy/node-config-provider': 4.3.3 + '@smithy/property-provider': 4.2.3 + '@smithy/types': 4.8.0 + '@smithy/url-parser': 4.2.3 tslib: 2.8.1 - '@smithy/hash-node@4.1.1': + '@smithy/fetch-http-handler@5.3.4': dependencies: - '@smithy/types': 4.5.0 - '@smithy/util-buffer-from': 4.1.0 - '@smithy/util-utf8': 4.1.0 + '@smithy/protocol-http': 5.3.3 + '@smithy/querystring-builder': 4.2.3 + '@smithy/types': 4.8.0 + '@smithy/util-base64': 4.3.0 tslib: 2.8.1 - '@smithy/invalid-dependency@4.1.1': + '@smithy/hash-node@4.2.3': dependencies: - '@smithy/types': 4.5.0 + '@smithy/types': 4.8.0 + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@smithy/invalid-dependency@4.2.3': + dependencies: + '@smithy/types': 4.8.0 tslib: 2.8.1 '@smithy/is-array-buffer@2.2.0': dependencies: tslib: 2.8.1 - '@smithy/is-array-buffer@4.1.0': + '@smithy/is-array-buffer@4.2.0': dependencies: tslib: 2.8.1 - '@smithy/middleware-content-length@4.1.1': + '@smithy/middleware-content-length@4.2.3': dependencies: - '@smithy/protocol-http': 5.2.1 - '@smithy/types': 4.5.0 + '@smithy/protocol-http': 5.3.3 + '@smithy/types': 4.8.0 tslib: 2.8.1 - '@smithy/middleware-endpoint@4.2.2': + '@smithy/middleware-endpoint@4.3.5': dependencies: - '@smithy/core': 3.11.0 - '@smithy/middleware-serde': 4.1.1 - '@smithy/node-config-provider': 4.2.2 - '@smithy/shared-ini-file-loader': 4.2.0 - '@smithy/types': 4.5.0 - '@smithy/url-parser': 4.1.1 - '@smithy/util-middleware': 4.1.1 + '@smithy/core': 3.17.1 + '@smithy/middleware-serde': 4.2.3 + '@smithy/node-config-provider': 4.3.3 + '@smithy/shared-ini-file-loader': 4.3.3 + '@smithy/types': 4.8.0 + '@smithy/url-parser': 4.2.3 + '@smithy/util-middleware': 4.2.3 tslib: 2.8.1 - '@smithy/middleware-retry@4.2.2': + '@smithy/middleware-retry@4.4.5': dependencies: - '@smithy/node-config-provider': 4.2.2 - '@smithy/protocol-http': 5.2.1 - '@smithy/service-error-classification': 4.1.1 - '@smithy/smithy-client': 4.6.2 - '@smithy/types': 4.5.0 - '@smithy/util-middleware': 4.1.1 - '@smithy/util-retry': 4.1.1 - '@types/uuid': 9.0.8 - tslib: 2.8.1 - uuid: 9.0.1 - - '@smithy/middleware-serde@4.1.1': - dependencies: - '@smithy/protocol-http': 5.2.1 - '@smithy/types': 4.5.0 + '@smithy/node-config-provider': 4.3.3 + '@smithy/protocol-http': 5.3.3 + '@smithy/service-error-classification': 4.2.3 + '@smithy/smithy-client': 4.9.1 + '@smithy/types': 4.8.0 + '@smithy/util-middleware': 4.2.3 + '@smithy/util-retry': 4.2.3 + '@smithy/uuid': 1.1.0 tslib: 2.8.1 - '@smithy/middleware-stack@4.1.1': + '@smithy/middleware-serde@4.2.3': dependencies: - '@smithy/types': 4.5.0 + '@smithy/protocol-http': 5.3.3 + '@smithy/types': 4.8.0 tslib: 2.8.1 - '@smithy/node-config-provider@4.2.2': + '@smithy/middleware-stack@4.2.3': dependencies: - '@smithy/property-provider': 4.1.1 - '@smithy/shared-ini-file-loader': 4.2.0 - '@smithy/types': 4.5.0 + '@smithy/types': 4.8.0 tslib: 2.8.1 - '@smithy/node-http-handler@4.2.1': + '@smithy/node-config-provider@4.3.3': dependencies: - '@smithy/abort-controller': 4.1.1 - '@smithy/protocol-http': 5.2.1 - '@smithy/querystring-builder': 4.1.1 - '@smithy/types': 4.5.0 + '@smithy/property-provider': 4.2.3 + '@smithy/shared-ini-file-loader': 4.3.3 + '@smithy/types': 4.8.0 tslib: 2.8.1 - '@smithy/property-provider@4.1.1': + '@smithy/node-http-handler@4.4.3': dependencies: - '@smithy/types': 4.5.0 + '@smithy/abort-controller': 4.2.3 + '@smithy/protocol-http': 5.3.3 + '@smithy/querystring-builder': 4.2.3 + '@smithy/types': 4.8.0 tslib: 2.8.1 - '@smithy/protocol-http@5.2.1': + '@smithy/property-provider@4.2.3': dependencies: - '@smithy/types': 4.5.0 + '@smithy/types': 4.8.0 tslib: 2.8.1 - '@smithy/querystring-builder@4.1.1': + '@smithy/protocol-http@5.3.3': dependencies: - '@smithy/types': 4.5.0 - '@smithy/util-uri-escape': 4.1.0 + '@smithy/types': 4.8.0 tslib: 2.8.1 - '@smithy/querystring-parser@4.1.1': + '@smithy/querystring-builder@4.2.3': dependencies: - '@smithy/types': 4.5.0 + '@smithy/types': 4.8.0 + '@smithy/util-uri-escape': 4.2.0 tslib: 2.8.1 - '@smithy/service-error-classification@4.1.1': + '@smithy/querystring-parser@4.2.3': dependencies: - '@smithy/types': 4.5.0 - - '@smithy/shared-ini-file-loader@4.2.0': - dependencies: - '@smithy/types': 4.5.0 + '@smithy/types': 4.8.0 tslib: 2.8.1 - '@smithy/signature-v4@5.2.1': + '@smithy/service-error-classification@4.2.3': dependencies: - '@smithy/is-array-buffer': 4.1.0 - '@smithy/protocol-http': 5.2.1 - '@smithy/types': 4.5.0 - '@smithy/util-hex-encoding': 4.1.0 - '@smithy/util-middleware': 4.1.1 - '@smithy/util-uri-escape': 4.1.0 - '@smithy/util-utf8': 4.1.0 + '@smithy/types': 4.8.0 + + '@smithy/shared-ini-file-loader@4.3.3': + dependencies: + '@smithy/types': 4.8.0 tslib: 2.8.1 - '@smithy/smithy-client@4.6.2': + '@smithy/signature-v4@5.3.3': dependencies: - '@smithy/core': 3.11.0 - '@smithy/middleware-endpoint': 4.2.2 - '@smithy/middleware-stack': 4.1.1 - '@smithy/protocol-http': 5.2.1 - '@smithy/types': 4.5.0 - '@smithy/util-stream': 4.3.1 + '@smithy/is-array-buffer': 4.2.0 + '@smithy/protocol-http': 5.3.3 + '@smithy/types': 4.8.0 + '@smithy/util-hex-encoding': 4.2.0 + '@smithy/util-middleware': 4.2.3 + '@smithy/util-uri-escape': 4.2.0 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@smithy/types@4.5.0': + '@smithy/smithy-client@4.9.1': + dependencies: + '@smithy/core': 3.17.1 + '@smithy/middleware-endpoint': 4.3.5 + '@smithy/middleware-stack': 4.2.3 + '@smithy/protocol-http': 5.3.3 + '@smithy/types': 4.8.0 + '@smithy/util-stream': 4.5.4 + tslib: 2.8.1 + + '@smithy/types@4.8.0': dependencies: tslib: 2.8.1 - '@smithy/url-parser@4.1.1': + '@smithy/url-parser@4.2.3': dependencies: - '@smithy/querystring-parser': 4.1.1 - '@smithy/types': 4.5.0 + '@smithy/querystring-parser': 4.2.3 + '@smithy/types': 4.8.0 tslib: 2.8.1 - '@smithy/util-base64@4.1.0': + '@smithy/util-base64@4.3.0': dependencies: - '@smithy/util-buffer-from': 4.1.0 - '@smithy/util-utf8': 4.1.0 + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@smithy/util-body-length-browser@4.1.0': + '@smithy/util-body-length-browser@4.2.0': dependencies: tslib: 2.8.1 - '@smithy/util-body-length-node@4.1.0': + '@smithy/util-body-length-node@4.2.1': dependencies: tslib: 2.8.1 @@ -15753,66 +15848,65 @@ snapshots: '@smithy/is-array-buffer': 2.2.0 tslib: 2.8.1 - '@smithy/util-buffer-from@4.1.0': + '@smithy/util-buffer-from@4.2.0': dependencies: - '@smithy/is-array-buffer': 4.1.0 + '@smithy/is-array-buffer': 4.2.0 tslib: 2.8.1 - '@smithy/util-config-provider@4.1.0': + '@smithy/util-config-provider@4.2.0': dependencies: tslib: 2.8.1 - '@smithy/util-defaults-mode-browser@4.1.2': + '@smithy/util-defaults-mode-browser@4.3.4': dependencies: - '@smithy/property-provider': 4.1.1 - '@smithy/smithy-client': 4.6.2 - '@smithy/types': 4.5.0 - bowser: 2.12.1 + '@smithy/property-provider': 4.2.3 + '@smithy/smithy-client': 4.9.1 + '@smithy/types': 4.8.0 tslib: 2.8.1 - '@smithy/util-defaults-mode-node@4.1.2': + '@smithy/util-defaults-mode-node@4.2.6': dependencies: - '@smithy/config-resolver': 4.2.2 - '@smithy/credential-provider-imds': 4.1.2 - '@smithy/node-config-provider': 4.2.2 - '@smithy/property-provider': 4.1.1 - '@smithy/smithy-client': 4.6.2 - '@smithy/types': 4.5.0 + '@smithy/config-resolver': 4.4.0 + '@smithy/credential-provider-imds': 4.2.3 + '@smithy/node-config-provider': 4.3.3 + '@smithy/property-provider': 4.2.3 + '@smithy/smithy-client': 4.9.1 + '@smithy/types': 4.8.0 tslib: 2.8.1 - '@smithy/util-endpoints@3.1.2': + '@smithy/util-endpoints@3.2.3': dependencies: - '@smithy/node-config-provider': 4.2.2 - '@smithy/types': 4.5.0 + '@smithy/node-config-provider': 4.3.3 + '@smithy/types': 4.8.0 tslib: 2.8.1 - '@smithy/util-hex-encoding@4.1.0': + '@smithy/util-hex-encoding@4.2.0': dependencies: tslib: 2.8.1 - '@smithy/util-middleware@4.1.1': + '@smithy/util-middleware@4.2.3': dependencies: - '@smithy/types': 4.5.0 + '@smithy/types': 4.8.0 tslib: 2.8.1 - '@smithy/util-retry@4.1.1': + '@smithy/util-retry@4.2.3': dependencies: - '@smithy/service-error-classification': 4.1.1 - '@smithy/types': 4.5.0 + '@smithy/service-error-classification': 4.2.3 + '@smithy/types': 4.8.0 tslib: 2.8.1 - '@smithy/util-stream@4.3.1': + '@smithy/util-stream@4.5.4': dependencies: - '@smithy/fetch-http-handler': 5.2.1 - '@smithy/node-http-handler': 4.2.1 - '@smithy/types': 4.5.0 - '@smithy/util-base64': 4.1.0 - '@smithy/util-buffer-from': 4.1.0 - '@smithy/util-hex-encoding': 4.1.0 - '@smithy/util-utf8': 4.1.0 + '@smithy/fetch-http-handler': 5.3.4 + '@smithy/node-http-handler': 4.4.3 + '@smithy/types': 4.8.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-hex-encoding': 4.2.0 + '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@smithy/util-uri-escape@4.1.0': + '@smithy/util-uri-escape@4.2.0': dependencies: tslib: 2.8.1 @@ -15821,9 +15915,13 @@ snapshots: '@smithy/util-buffer-from': 2.2.0 tslib: 2.8.1 - '@smithy/util-utf8@4.1.0': + '@smithy/util-utf8@4.2.0': + dependencies: + '@smithy/util-buffer-from': 4.2.0 + tslib: 2.8.1 + + '@smithy/uuid@1.1.0': dependencies: - '@smithy/util-buffer-from': 4.1.0 tslib: 2.8.1 '@socket.io/component-emitter@3.1.2': {} @@ -15839,120 +15937,120 @@ snapshots: '@standard-schema/spec@1.0.0': {} - '@sveltejs/acorn-typescript@1.0.5(acorn@8.15.0)': + '@sveltejs/acorn-typescript@1.0.6(acorn@8.15.0)': dependencies: acorn: 8.15.0 - '@sveltejs/adapter-static@3.0.9(@sveltejs/kit@2.38.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.0(svelte@5.38.10)(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)))(svelte@5.38.10)(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)))': + '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.48.3(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.0)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))(svelte@5.43.0)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))': dependencies: - '@sveltejs/kit': 2.38.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.0(svelte@5.38.10)(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)))(svelte@5.38.10)(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)) + '@sveltejs/kit': 2.48.3(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.0)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))(svelte@5.43.0)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) - '@sveltejs/enhanced-img@0.8.1(@sveltejs/vite-plugin-svelte@6.2.0(svelte@5.38.10)(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)))(rollup@4.50.1)(svelte@5.38.10)(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1))': + '@sveltejs/enhanced-img@0.8.4(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.0)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))(rollup@4.52.5)(svelte@5.43.0)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))': dependencies: - '@sveltejs/vite-plugin-svelte': 6.2.0(svelte@5.38.10)(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)) - magic-string: 0.30.19 - sharp: 0.34.3 - svelte: 5.38.10 - svelte-parse-markup: 0.1.5(svelte@5.38.10) - vite: 7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) - vite-imagetools: 8.0.0(rollup@4.50.1) - zimmerframe: 1.1.2 + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.43.0)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) + magic-string: 0.30.21 + sharp: 0.34.4 + svelte: 5.43.0 + svelte-parse-markup: 0.1.5(svelte@5.43.0) + vite: 7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) + vite-imagetools: 8.0.0(rollup@4.52.5) + zimmerframe: 1.1.4 transitivePeerDependencies: - rollup - supports-color - '@sveltejs/kit@2.38.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.0(svelte@5.38.10)(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)))(svelte@5.38.10)(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1))': + '@sveltejs/kit@2.48.3(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.0)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))(svelte@5.43.0)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))': dependencies: '@standard-schema/spec': 1.0.0 - '@sveltejs/acorn-typescript': 1.0.5(acorn@8.15.0) - '@sveltejs/vite-plugin-svelte': 6.2.0(svelte@5.38.10)(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)) + '@sveltejs/acorn-typescript': 1.0.6(acorn@8.15.0) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.43.0)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) '@types/cookie': 0.6.0 acorn: 8.15.0 cookie: 0.6.0 - devalue: 5.3.2 + devalue: 5.4.2 esm-env: 1.2.2 kleur: 4.1.5 - magic-string: 0.30.19 + magic-string: 0.30.21 mrmime: 2.0.1 sade: 1.8.1 - set-cookie-parser: 2.7.1 + set-cookie-parser: 2.7.2 sirv: 3.0.2 - svelte: 5.38.10 - vite: 7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) + svelte: 5.43.0 + vite: 7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) optionalDependencies: '@opentelemetry/api': 1.9.0 - '@sveltejs/vite-plugin-svelte-inspector@5.0.0(@sveltejs/vite-plugin-svelte@6.2.0(svelte@5.38.10)(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)))(svelte@5.38.10)(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1))': + '@sveltejs/vite-plugin-svelte-inspector@5.0.0(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.0)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))(svelte@5.43.0)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))': dependencies: - '@sveltejs/vite-plugin-svelte': 6.2.0(svelte@5.38.10)(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.43.0)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) debug: 4.4.3 - svelte: 5.38.10 - vite: 7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) + svelte: 5.43.0 + vite: 7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@6.2.0(svelte@5.38.10)(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1))': + '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.0)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 5.0.0(@sveltejs/vite-plugin-svelte@6.2.0(svelte@5.38.10)(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)))(svelte@5.38.10)(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)) + '@sveltejs/vite-plugin-svelte-inspector': 5.0.0(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.0)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)))(svelte@5.43.0)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) debug: 4.4.3 deepmerge: 4.3.1 - magic-string: 0.30.19 - svelte: 5.38.10 - vite: 7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) - vitefu: 1.1.1(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)) + magic-string: 0.30.21 + svelte: 5.43.0 + vite: 7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) + vitefu: 1.1.1(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) transitivePeerDependencies: - supports-color - '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.27.7)': + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 - '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.27.7)': + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 - '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.27.7)': + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 - '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.27.7)': + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 - '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.27.7)': + '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 - '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.27.7)': + '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 - '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.27.7)': + '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 - '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.27.7)': + '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 - '@svgr/babel-preset@8.1.0(@babel/core@7.27.7)': + '@svgr/babel-preset@8.1.0(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.27.7 - '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.27.7) - '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.27.7) - '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.27.7) - '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.27.7) - '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.27.7) - '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.27.7) - '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.27.7) - '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.27.7) + '@babel/core': 7.28.5 + '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.28.5) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.28.5) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.28.5) + '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.28.5) + '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.28.5) + '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.28.5) + '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.28.5) + '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.28.5) - '@svgr/core@8.1.0(typescript@5.9.2)': + '@svgr/core@8.1.0(typescript@5.9.3)': dependencies: - '@babel/core': 7.27.7 - '@svgr/babel-preset': 8.1.0(@babel/core@7.27.7) + '@babel/core': 7.28.5 + '@svgr/babel-preset': 8.1.0(@babel/core@7.28.5) camelcase: 6.3.0 - cosmiconfig: 8.3.6(typescript@5.9.2) + cosmiconfig: 8.3.6(typescript@5.9.3) snake-case: 3.0.4 transitivePeerDependencies: - supports-color @@ -15960,87 +16058,87 @@ snapshots: '@svgr/hast-util-to-babel-ast@8.0.0': dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 entities: 4.5.0 - '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.9.2))': + '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.9.3))': dependencies: - '@babel/core': 7.27.7 - '@svgr/babel-preset': 8.1.0(@babel/core@7.27.7) - '@svgr/core': 8.1.0(typescript@5.9.2) + '@babel/core': 7.28.5 + '@svgr/babel-preset': 8.1.0(@babel/core@7.28.5) + '@svgr/core': 8.1.0(typescript@5.9.3) '@svgr/hast-util-to-babel-ast': 8.0.0 svg-parser: 2.0.4 transitivePeerDependencies: - supports-color - '@svgr/plugin-svgo@8.1.0(@svgr/core@8.1.0(typescript@5.9.2))(typescript@5.9.2)': + '@svgr/plugin-svgo@8.1.0(@svgr/core@8.1.0(typescript@5.9.3))(typescript@5.9.3)': dependencies: - '@svgr/core': 8.1.0(typescript@5.9.2) - cosmiconfig: 8.3.6(typescript@5.9.2) + '@svgr/core': 8.1.0(typescript@5.9.3) + cosmiconfig: 8.3.6(typescript@5.9.3) deepmerge: 4.3.1 svgo: 3.3.2 transitivePeerDependencies: - typescript - '@svgr/webpack@8.1.0(typescript@5.9.2)': + '@svgr/webpack@8.1.0(typescript@5.9.3)': dependencies: - '@babel/core': 7.27.7 - '@babel/plugin-transform-react-constant-elements': 7.27.1(@babel/core@7.27.7) - '@babel/preset-env': 7.27.2(@babel/core@7.27.7) - '@babel/preset-react': 7.27.1(@babel/core@7.27.7) - '@babel/preset-typescript': 7.27.1(@babel/core@7.27.7) - '@svgr/core': 8.1.0(typescript@5.9.2) - '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.9.2)) - '@svgr/plugin-svgo': 8.1.0(@svgr/core@8.1.0(typescript@5.9.2))(typescript@5.9.2) + '@babel/core': 7.28.5 + '@babel/plugin-transform-react-constant-elements': 7.27.1(@babel/core@7.28.5) + '@babel/preset-env': 7.28.5(@babel/core@7.28.5) + '@babel/preset-react': 7.28.5(@babel/core@7.28.5) + '@babel/preset-typescript': 7.28.5(@babel/core@7.28.5) + '@svgr/core': 8.1.0(typescript@5.9.3) + '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.9.3)) + '@svgr/plugin-svgo': 8.1.0(@svgr/core@8.1.0(typescript@5.9.3))(typescript@5.9.3) transitivePeerDependencies: - supports-color - typescript - '@swc/core-darwin-arm64@1.13.5': + '@swc/core-darwin-arm64@1.14.0': optional: true - '@swc/core-darwin-x64@1.13.5': + '@swc/core-darwin-x64@1.14.0': optional: true - '@swc/core-linux-arm-gnueabihf@1.13.5': + '@swc/core-linux-arm-gnueabihf@1.14.0': optional: true - '@swc/core-linux-arm64-gnu@1.13.5': + '@swc/core-linux-arm64-gnu@1.14.0': optional: true - '@swc/core-linux-arm64-musl@1.13.5': + '@swc/core-linux-arm64-musl@1.14.0': optional: true - '@swc/core-linux-x64-gnu@1.13.5': + '@swc/core-linux-x64-gnu@1.14.0': optional: true - '@swc/core-linux-x64-musl@1.13.5': + '@swc/core-linux-x64-musl@1.14.0': optional: true - '@swc/core-win32-arm64-msvc@1.13.5': + '@swc/core-win32-arm64-msvc@1.14.0': optional: true - '@swc/core-win32-ia32-msvc@1.13.5': + '@swc/core-win32-ia32-msvc@1.14.0': optional: true - '@swc/core-win32-x64-msvc@1.13.5': + '@swc/core-win32-x64-msvc@1.14.0': optional: true - '@swc/core@1.13.5(@swc/helpers@0.5.17)': + '@swc/core@1.14.0(@swc/helpers@0.5.17)': dependencies: '@swc/counter': 0.1.3 '@swc/types': 0.1.25 optionalDependencies: - '@swc/core-darwin-arm64': 1.13.5 - '@swc/core-darwin-x64': 1.13.5 - '@swc/core-linux-arm-gnueabihf': 1.13.5 - '@swc/core-linux-arm64-gnu': 1.13.5 - '@swc/core-linux-arm64-musl': 1.13.5 - '@swc/core-linux-x64-gnu': 1.13.5 - '@swc/core-linux-x64-musl': 1.13.5 - '@swc/core-win32-arm64-msvc': 1.13.5 - '@swc/core-win32-ia32-msvc': 1.13.5 - '@swc/core-win32-x64-msvc': 1.13.5 + '@swc/core-darwin-arm64': 1.14.0 + '@swc/core-darwin-x64': 1.14.0 + '@swc/core-linux-arm-gnueabihf': 1.14.0 + '@swc/core-linux-arm64-gnu': 1.14.0 + '@swc/core-linux-arm64-musl': 1.14.0 + '@swc/core-linux-x64-gnu': 1.14.0 + '@swc/core-linux-x64-musl': 1.14.0 + '@swc/core-win32-arm64-msvc': 1.14.0 + '@swc/core-win32-ia32-msvc': 1.14.0 + '@swc/core-win32-x64-msvc': 1.14.0 '@swc/helpers': 0.5.17 '@swc/counter@0.1.3': {} @@ -16057,76 +16155,73 @@ snapshots: dependencies: defer-to-connect: 2.0.1 - '@tailwindcss/node@4.1.13': + '@tailwindcss/node@4.1.16': dependencies: '@jridgewell/remapping': 2.3.5 enhanced-resolve: 5.18.3 - jiti: 2.5.1 - lightningcss: 1.30.1 - magic-string: 0.30.19 + jiti: 2.6.1 + lightningcss: 1.30.2 + magic-string: 0.30.21 source-map-js: 1.2.1 - tailwindcss: 4.1.13 + tailwindcss: 4.1.16 - '@tailwindcss/oxide-android-arm64@4.1.13': + '@tailwindcss/oxide-android-arm64@4.1.16': optional: true - '@tailwindcss/oxide-darwin-arm64@4.1.13': + '@tailwindcss/oxide-darwin-arm64@4.1.16': optional: true - '@tailwindcss/oxide-darwin-x64@4.1.13': + '@tailwindcss/oxide-darwin-x64@4.1.16': optional: true - '@tailwindcss/oxide-freebsd-x64@4.1.13': + '@tailwindcss/oxide-freebsd-x64@4.1.16': optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.13': + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.16': optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.1.13': + '@tailwindcss/oxide-linux-arm64-gnu@4.1.16': optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.1.13': + '@tailwindcss/oxide-linux-arm64-musl@4.1.16': optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.1.13': + '@tailwindcss/oxide-linux-x64-gnu@4.1.16': optional: true - '@tailwindcss/oxide-linux-x64-musl@4.1.13': + '@tailwindcss/oxide-linux-x64-musl@4.1.16': optional: true - '@tailwindcss/oxide-wasm32-wasi@4.1.13': + '@tailwindcss/oxide-wasm32-wasi@4.1.16': optional: true - '@tailwindcss/oxide-win32-arm64-msvc@4.1.13': + '@tailwindcss/oxide-win32-arm64-msvc@4.1.16': optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.1.13': + '@tailwindcss/oxide-win32-x64-msvc@4.1.16': optional: true - '@tailwindcss/oxide@4.1.13': - dependencies: - detect-libc: 2.1.0 - tar: 7.4.3 + '@tailwindcss/oxide@4.1.16': optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.1.13 - '@tailwindcss/oxide-darwin-arm64': 4.1.13 - '@tailwindcss/oxide-darwin-x64': 4.1.13 - '@tailwindcss/oxide-freebsd-x64': 4.1.13 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.13 - '@tailwindcss/oxide-linux-arm64-gnu': 4.1.13 - '@tailwindcss/oxide-linux-arm64-musl': 4.1.13 - '@tailwindcss/oxide-linux-x64-gnu': 4.1.13 - '@tailwindcss/oxide-linux-x64-musl': 4.1.13 - '@tailwindcss/oxide-wasm32-wasi': 4.1.13 - '@tailwindcss/oxide-win32-arm64-msvc': 4.1.13 - '@tailwindcss/oxide-win32-x64-msvc': 4.1.13 + '@tailwindcss/oxide-android-arm64': 4.1.16 + '@tailwindcss/oxide-darwin-arm64': 4.1.16 + '@tailwindcss/oxide-darwin-x64': 4.1.16 + '@tailwindcss/oxide-freebsd-x64': 4.1.16 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.16 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.16 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.16 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.16 + '@tailwindcss/oxide-linux-x64-musl': 4.1.16 + '@tailwindcss/oxide-wasm32-wasi': 4.1.16 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.16 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.16 - '@tailwindcss/vite@4.1.13(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1))': + '@tailwindcss/vite@4.1.16(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))': dependencies: - '@tailwindcss/node': 4.1.13 - '@tailwindcss/oxide': 4.1.13 - tailwindcss: 4.1.13 - vite: 7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) + '@tailwindcss/node': 4.1.16 + '@tailwindcss/oxide': 4.1.16 + tailwindcss: 4.1.16 + vite: 7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) '@testing-library/dom@10.4.0': dependencies: @@ -16139,7 +16234,7 @@ snapshots: lz-string: 1.5.0 pretty-format: 27.5.1 - '@testing-library/jest-dom@6.8.0': + '@testing-library/jest-dom@6.9.1': dependencies: '@adobe/css-tools': 4.4.4 aria-query: 5.3.2 @@ -16148,13 +16243,13 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/svelte@5.2.8(svelte@5.38.10)(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.5.1)(happy-dom@18.0.1)(jiti@2.5.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1))': + '@testing-library/svelte@5.2.8(svelte@5.43.0)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.0)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))': dependencies: '@testing-library/dom': 10.4.0 - svelte: 5.38.10 + svelte: 5.43.0 optionalDependencies: - vite: 7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.5.1)(happy-dom@18.0.1)(jiti@2.5.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) + vite: 7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.0)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.0)': dependencies: @@ -16196,9 +16291,9 @@ snapshots: '@types/accepts@1.3.7': dependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 - '@types/archiver@6.0.3': + '@types/archiver@6.0.4': dependencies: '@types/readdir-glob': 1.1.5 @@ -16208,16 +16303,16 @@ snapshots: '@types/bcrypt@6.0.0': dependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 - '@types/node': 22.18.5 + '@types/node': 22.19.0 '@types/bonjour@3.5.13': dependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 '@types/braces@3.0.5': {} @@ -16238,27 +16333,27 @@ snapshots: '@types/cli-progress@3.11.6': dependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 '@types/compression@1.8.1': dependencies: - '@types/express': 5.0.3 - '@types/node': 22.18.5 + '@types/express': 5.0.5 + '@types/node': 22.19.0 '@types/connect-history-api-fallback@1.5.4': dependencies: - '@types/express-serve-static-core': 5.0.6 - '@types/node': 22.18.5 + '@types/express-serve-static-core': 5.1.0 + '@types/node': 22.19.0 '@types/connect@3.4.38': dependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 '@types/content-disposition@0.5.9': {} - '@types/cookie-parser@1.4.9(@types/express@5.0.3)': + '@types/cookie-parser@1.4.10(@types/express@5.0.5)': dependencies: - '@types/express': 5.0.3 + '@types/express': 5.0.5 '@types/cookie@0.6.0': {} @@ -16267,13 +16362,13 @@ snapshots: '@types/cookies@0.9.1': dependencies: '@types/connect': 3.4.38 - '@types/express': 5.0.3 + '@types/express': 5.0.5 '@types/keygrip': 1.0.6 - '@types/node': 22.18.5 + '@types/node': 22.19.0 '@types/cors@2.8.19': dependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 '@types/debug@4.1.12': dependencies: @@ -16283,13 +16378,13 @@ snapshots: '@types/docker-modem@3.0.6': dependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 '@types/ssh2': 1.15.5 - '@types/dockerode@3.3.42': + '@types/dockerode@3.3.45': dependencies: '@types/docker-modem': 3.0.6 - '@types/node': 22.18.5 + '@types/node': 22.19.0 '@types/ssh2': 1.15.5 '@types/dom-to-image@2.6.7': {} @@ -16310,32 +16405,32 @@ snapshots: '@types/estree@1.0.8': {} - '@types/express-serve-static-core@4.19.6': + '@types/express-serve-static-core@4.19.7': dependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 '@types/qs': 6.14.0 '@types/range-parser': 1.2.7 - '@types/send': 0.17.5 + '@types/send': 1.2.1 - '@types/express-serve-static-core@5.0.6': + '@types/express-serve-static-core@5.1.0': dependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 '@types/qs': 6.14.0 '@types/range-parser': 1.2.7 - '@types/send': 0.17.5 + '@types/send': 1.2.1 - '@types/express@4.17.23': + '@types/express@4.17.25': dependencies: '@types/body-parser': 1.19.6 - '@types/express-serve-static-core': 4.19.6 + '@types/express-serve-static-core': 4.19.7 '@types/qs': 6.14.0 - '@types/serve-static': 1.15.8 + '@types/serve-static': 1.15.10 - '@types/express@5.0.3': + '@types/express@5.0.5': dependencies: '@types/body-parser': 1.19.6 - '@types/express-serve-static-core': 5.0.6 - '@types/serve-static': 1.15.8 + '@types/express-serve-static-core': 5.1.0 + '@types/serve-static': 1.15.10 '@types/filesystem@0.0.36': dependencies: @@ -16343,9 +16438,9 @@ snapshots: '@types/filewriter@0.0.33': {} - '@types/fluent-ffmpeg@2.1.27': + '@types/fluent-ffmpeg@2.1.28': dependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 '@types/geojson-vt@3.2.5': dependencies: @@ -16367,11 +16462,6 @@ snapshots: '@types/history@4.7.11': {} - '@types/hoist-non-react-statics@3.3.6': - dependencies: - '@types/react': 19.1.13 - hoist-non-react-statics: 3.3.2 - '@types/html-minifier-terser@6.1.0': {} '@types/http-assert@1.5.6': {} @@ -16380,9 +16470,9 @@ snapshots: '@types/http-errors@2.0.5': {} - '@types/http-proxy@1.17.16': + '@types/http-proxy@1.17.17': dependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 '@types/inquirer@8.2.11': dependencies: @@ -16403,6 +16493,11 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/jsonwebtoken@9.0.10': + dependencies: + '@types/ms': 2.1.0 + '@types/node': 22.19.0 + '@types/justified-layout@4.1.4': {} '@types/keygrip@1.0.6': {} @@ -16420,9 +16515,9 @@ snapshots: '@types/http-errors': 2.0.5 '@types/keygrip': 1.0.6 '@types/koa-compose': 3.2.8 - '@types/node': 22.18.5 + '@types/node': 22.19.0 - '@types/leaflet@1.9.20': + '@types/leaflet@1.9.21': dependencies: '@types/geojson': 7946.0.16 @@ -16432,8 +16527,6 @@ snapshots: '@types/lodash@4.17.20': {} - '@types/luxon@3.6.2': {} - '@types/luxon@3.7.1': {} '@types/mdast@4.0.4': @@ -16444,7 +16537,7 @@ snapshots: '@types/methods@1.1.4': {} - '@types/micromatch@4.0.9': + '@types/micromatch@4.0.10': dependencies: '@types/braces': 3.0.5 @@ -16452,41 +16545,41 @@ snapshots: '@types/mock-fs@4.13.4': dependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 '@types/ms@2.1.0': {} '@types/multer@2.0.0': dependencies: - '@types/express': 5.0.3 + '@types/express': 5.0.5 - '@types/node-forge@1.3.11': + '@types/node-forge@1.3.14': dependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 '@types/node@17.0.45': {} - '@types/node@18.19.126': + '@types/node@18.19.130': dependencies: undici-types: 5.26.5 - '@types/node@20.19.2': + '@types/node@20.19.24': dependencies: undici-types: 6.21.0 - '@types/node@22.18.5': + '@types/node@22.19.0': dependencies: undici-types: 6.21.0 - '@types/node@24.5.1': + '@types/node@24.10.0': dependencies: - undici-types: 7.12.0 + undici-types: 7.16.0 optional: true - '@types/nodemailer@7.0.1': + '@types/nodemailer@7.0.3': dependencies: - '@aws-sdk/client-sesv2': 3.890.0 - '@types/node': 22.18.5 + '@aws-sdk/client-sesv2': 3.919.0 + '@types/node': 22.19.0 transitivePeerDependencies: - aws-crt @@ -16494,17 +16587,23 @@ snapshots: dependencies: '@types/keygrip': 1.0.6 '@types/koa': 3.0.0 - '@types/node': 22.18.5 + '@types/node': 22.19.0 '@types/parse5@5.0.3': {} '@types/pg-pool@2.0.6': dependencies: - '@types/pg': 8.15.5 + '@types/pg': 8.15.6 '@types/pg@8.15.5': dependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 + pg-protocol: 1.10.3 + pg-types: 2.2.0 + + '@types/pg@8.15.6': + dependencies: + '@types/node': 22.19.0 pg-protocol: 1.10.3 pg-types: 2.2.0 @@ -16512,51 +16611,44 @@ snapshots: '@types/pngjs@6.0.5': dependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 '@types/prismjs@1.26.5': {} - '@types/qrcode@1.5.5': + '@types/qrcode@1.5.6': dependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 '@types/qs@6.14.0': {} '@types/range-parser@1.2.7': {} - '@types/react-redux@7.1.34': - dependencies: - '@types/hoist-non-react-statics': 3.3.6 - '@types/react': 19.1.13 - hoist-non-react-statics: 3.3.2 - redux: 4.2.1 - '@types/react-router-config@5.0.11': dependencies: '@types/history': 4.7.11 - '@types/react': 19.1.13 + '@types/react': 19.2.2 '@types/react-router': 5.1.20 '@types/react-router-dom@5.3.3': dependencies: '@types/history': 4.7.11 - '@types/react': 19.1.13 + '@types/react': 19.2.2 '@types/react-router': 5.1.20 '@types/react-router@5.1.20': dependencies: '@types/history': 4.7.11 - '@types/react': 19.1.13 + '@types/react': 19.2.2 - '@types/react@19.1.13': + '@types/react@19.2.2': dependencies: csstype: 3.1.3 '@types/readdir-glob@1.1.5': dependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 - '@types/retry@0.12.0': {} + '@types/retry@0.12.2': {} '@types/sanitize-html@2.16.0': dependencies: @@ -16564,47 +16656,51 @@ snapshots: '@types/sax@1.2.7': dependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 '@types/semver@7.7.1': {} - '@types/send@0.17.5': + '@types/send@0.17.6': dependencies: '@types/mime': 1.3.5 - '@types/node': 22.18.5 + '@types/node': 22.19.0 + + '@types/send@1.2.1': + dependencies: + '@types/node': 22.19.0 '@types/serve-index@1.9.4': dependencies: - '@types/express': 5.0.3 + '@types/express': 5.0.5 - '@types/serve-static@1.15.8': + '@types/serve-static@1.15.10': dependencies: '@types/http-errors': 2.0.5 - '@types/node': 22.18.5 - '@types/send': 0.17.5 + '@types/node': 22.19.0 + '@types/send': 0.17.6 '@types/sockjs@0.3.36': dependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 - '@types/ssh2-streams@0.1.12': + '@types/ssh2-streams@0.1.13': dependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 '@types/ssh2@0.5.52': dependencies: - '@types/node': 22.18.5 - '@types/ssh2-streams': 0.1.12 + '@types/node': 22.19.0 + '@types/ssh2-streams': 0.1.13 '@types/ssh2@1.15.5': dependencies: - '@types/node': 18.19.126 + '@types/node': 18.19.130 '@types/superagent@8.1.9': dependencies: '@types/cookiejar': 2.1.5 '@types/methods': 1.1.4 - '@types/node': 22.18.5 + '@types/node': 22.19.0 form-data: 4.0.4 '@types/supercluster@7.1.3': @@ -16618,7 +16714,7 @@ snapshots: '@types/through@0.0.33': dependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 '@types/ua-parser-js@0.7.39': {} @@ -16626,118 +16722,118 @@ snapshots: '@types/unist@3.0.3': {} - '@types/uuid@9.0.8': {} - - '@types/validator@13.15.3': {} + '@types/validator@13.15.4': {} '@types/whatwg-mimetype@3.0.2': {} '@types/ws@8.18.1': dependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 '@types/yargs-parser@21.0.3': {} - '@types/yargs@17.0.33': + '@types/yargs@17.0.34': dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.43.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2)': + '@typescript-eslint/eslint-plugin@8.46.2(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/scope-manager': 8.43.0 - '@typescript-eslint/type-utils': 8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/utils': 8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 8.43.0 - eslint: 9.35.0(jiti@2.5.1) + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.46.2 + '@typescript-eslint/type-utils': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.46.2 + eslint: 9.38.0(jiti@2.6.1) graphemer: 1.4.0 ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.1.0(typescript@5.9.2) - typescript: 5.9.2 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2)': + '@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.43.0 - '@typescript-eslint/types': 8.43.0 - '@typescript-eslint/typescript-estree': 8.43.0(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 8.43.0 + '@typescript-eslint/scope-manager': 8.46.2 + '@typescript-eslint/types': 8.46.2 + '@typescript-eslint/typescript-estree': 8.46.2(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.46.2 debug: 4.4.3 - eslint: 9.35.0(jiti@2.5.1) - typescript: 5.9.2 + eslint: 9.38.0(jiti@2.6.1) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.43.0(typescript@5.9.2)': + '@typescript-eslint/project-service@8.46.2(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.43.0(typescript@5.9.2) - '@typescript-eslint/types': 8.43.0 + '@typescript-eslint/tsconfig-utils': 8.46.2(typescript@5.9.3) + '@typescript-eslint/types': 8.46.2 debug: 4.4.3 - typescript: 5.9.2 + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.43.0': + '@typescript-eslint/scope-manager@8.46.2': dependencies: - '@typescript-eslint/types': 8.43.0 - '@typescript-eslint/visitor-keys': 8.43.0 + '@typescript-eslint/types': 8.46.2 + '@typescript-eslint/visitor-keys': 8.46.2 - '@typescript-eslint/tsconfig-utils@8.43.0(typescript@5.9.2)': + '@typescript-eslint/tsconfig-utils@8.46.2(typescript@5.9.3)': dependencies: - typescript: 5.9.2 + typescript: 5.9.3 - '@typescript-eslint/type-utils@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2)': + '@typescript-eslint/type-utils@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.43.0 - '@typescript-eslint/typescript-estree': 8.43.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/types': 8.46.2 + '@typescript-eslint/typescript-estree': 8.46.2(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.3 - eslint: 9.35.0(jiti@2.5.1) - ts-api-utils: 2.1.0(typescript@5.9.2) - typescript: 5.9.2 + eslint: 9.38.0(jiti@2.6.1) + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.43.0': {} + '@typescript-eslint/types@8.46.2': {} - '@typescript-eslint/typescript-estree@8.43.0(typescript@5.9.2)': + '@typescript-eslint/typescript-estree@8.46.2(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.43.0(typescript@5.9.2) - '@typescript-eslint/tsconfig-utils': 8.43.0(typescript@5.9.2) - '@typescript-eslint/types': 8.43.0 - '@typescript-eslint/visitor-keys': 8.43.0 + '@typescript-eslint/project-service': 8.46.2(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.46.2(typescript@5.9.3) + '@typescript-eslint/types': 8.46.2 + '@typescript-eslint/visitor-keys': 8.46.2 debug: 4.4.3 fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.7.2 - ts-api-utils: 2.1.0(typescript@5.9.2) - typescript: 5.9.2 + semver: 7.7.3 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2)': + '@typescript-eslint/utils@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.35.0(jiti@2.5.1)) - '@typescript-eslint/scope-manager': 8.43.0 - '@typescript-eslint/types': 8.43.0 - '@typescript-eslint/typescript-estree': 8.43.0(typescript@5.9.2) - eslint: 9.35.0(jiti@2.5.1) - typescript: 5.9.2 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.38.0(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.46.2 + '@typescript-eslint/types': 8.46.2 + '@typescript-eslint/typescript-estree': 8.46.2(typescript@5.9.3) + eslint: 9.38.0(jiti@2.6.1) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.43.0': + '@typescript-eslint/visitor-keys@8.46.2': dependencies: - '@typescript-eslint/types': 8.43.0 + '@typescript-eslint/types': 8.46.2 eslint-visitor-keys: 4.2.1 '@ungap/structured-clone@1.3.0': {} - '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.18.5)(happy-dom@18.0.1)(jiti@2.5.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1))': + '@vercel/oidc@3.0.3': {} + + '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.0)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 @@ -16747,16 +16843,16 @@ snapshots: istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 istanbul-reports: 3.1.7 - magic-string: 0.30.19 + magic-string: 0.30.21 magicast: 0.3.5 - std-env: 3.9.0 + std-env: 3.10.0 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.5)(happy-dom@18.0.1)(jiti@2.5.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.0)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.5.1)(happy-dom@18.0.1)(jiti@2.5.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1))': + '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.0)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 @@ -16766,12 +16862,12 @@ snapshots: istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 istanbul-reports: 3.1.7 - magic-string: 0.30.19 + magic-string: 0.30.21 magicast: 0.3.5 - std-env: 3.9.0 + std-env: 3.10.0 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.5.1)(happy-dom@18.0.1)(jiti@2.5.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.0)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) transitivePeerDependencies: - supports-color @@ -16783,21 +16879,21 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.1.5(@types/node@22.18.5)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1))': + '@vitest/mocker@3.2.4(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 - magic-string: 0.30.19 + magic-string: 0.30.21 optionalDependencies: - vite: 7.1.5(@types/node@22.18.5)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) + vite: 7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) - '@vitest/mocker@3.2.4(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1))': + '@vitest/mocker@3.2.4(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 - magic-string: 0.30.19 + magic-string: 0.30.21 optionalDependencies: - vite: 7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) + vite: 7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) '@vitest/pretty-format@3.2.4': dependencies: @@ -16812,7 +16908,7 @@ snapshots: '@vitest/snapshot@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 - magic-string: 0.30.19 + magic-string: 0.30.21 pathe: 2.0.3 '@vitest/spy@3.2.4': @@ -16905,14 +17001,14 @@ snapshots: '@xtuc/long@4.2.2': {} - '@zoom-image/core@0.41.0': + '@zoom-image/core@0.41.3': dependencies: '@namnode/store': 0.1.0 - '@zoom-image/svelte@0.3.4(svelte@5.38.10)': + '@zoom-image/svelte@0.3.7(svelte@5.43.0)': dependencies: - '@zoom-image/core': 0.41.0 - svelte: 5.38.10 + '@zoom-image/core': 0.41.3 + svelte: 5.43.0 abab@2.0.6: optional: true @@ -16974,13 +17070,13 @@ snapshots: clean-stack: 2.2.0 indent-string: 4.0.0 - ajv-draft-04@1.0.0(ajv@8.11.0): - optionalDependencies: - ajv: 8.11.0 - - ajv-formats@2.1.1(ajv@8.11.0): - optionalDependencies: - ajv: 8.11.0 + ai@5.0.82(zod@4.1.12): + dependencies: + '@ai-sdk/gateway': 2.0.3(zod@4.1.12) + '@ai-sdk/provider': 2.0.0 + '@ai-sdk/provider-utils': 3.0.14(zod@4.1.12) + '@opentelemetry/api': 1.9.0 + zod: 4.1.12 ajv-formats@2.1.1(ajv@8.17.1): optionalDependencies: @@ -17006,40 +17102,34 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.11.0: - dependencies: - fast-deep-equal: 3.1.3 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - uri-js: 4.4.1 - ajv@8.17.1: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.0.6 + fast-uri: 3.1.0 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - algoliasearch-helper@3.26.0(algoliasearch@5.29.0): + algoliasearch-helper@3.26.0(algoliasearch@5.41.0): dependencies: '@algolia/events': 4.0.1 - algoliasearch: 5.29.0 + algoliasearch: 5.41.0 - algoliasearch@5.29.0: + algoliasearch@5.41.0: dependencies: - '@algolia/client-abtesting': 5.29.0 - '@algolia/client-analytics': 5.29.0 - '@algolia/client-common': 5.29.0 - '@algolia/client-insights': 5.29.0 - '@algolia/client-personalization': 5.29.0 - '@algolia/client-query-suggestions': 5.29.0 - '@algolia/client-search': 5.29.0 - '@algolia/ingestion': 1.29.0 - '@algolia/monitoring': 1.29.0 - '@algolia/recommend': 5.29.0 - '@algolia/requester-browser-xhr': 5.29.0 - '@algolia/requester-fetch': 5.29.0 - '@algolia/requester-node-http': 5.29.0 + '@algolia/abtesting': 1.7.0 + '@algolia/client-abtesting': 5.41.0 + '@algolia/client-analytics': 5.41.0 + '@algolia/client-common': 5.41.0 + '@algolia/client-insights': 5.41.0 + '@algolia/client-personalization': 5.41.0 + '@algolia/client-query-suggestions': 5.41.0 + '@algolia/client-search': 5.41.0 + '@algolia/ingestion': 1.41.0 + '@algolia/monitoring': 1.41.0 + '@algolia/recommend': 5.41.0 + '@algolia/requester-browser-xhr': 5.41.0 + '@algolia/requester-fetch': 5.41.0 + '@algolia/requester-node-http': 5.41.0 ansi-align@3.0.1: dependencies: @@ -17097,6 +17187,8 @@ snapshots: readdir-glob: 1.1.3 tar-stream: 3.1.7 zip-stream: 6.0.1 + transitivePeerDependencies: + - bare-abort-controller are-we-there-yet@2.0.0: dependencies: @@ -17139,7 +17231,7 @@ snapshots: ast-v8-to-istanbul@0.3.3: dependencies: - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 estree-walker: 3.0.3 js-tokens: 9.0.1 @@ -17153,10 +17245,6 @@ snapshots: async@0.2.10: {} - async@3.2.2: {} - - async@3.2.4: {} - async@3.2.6: {} asynckit@0.4.0: {} @@ -17167,8 +17255,8 @@ snapshots: autoprefixer@10.4.21(postcss@8.5.6): dependencies: - browserslist: 4.25.3 - caniuse-lite: 1.0.30001735 + browserslist: 4.27.0 + caniuse-lite: 1.0.30001751 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -17179,38 +17267,38 @@ snapshots: b4a@1.6.7: {} - babel-loader@9.2.1(@babel/core@7.27.7)(webpack@5.100.2): + babel-loader@9.2.1(@babel/core@7.28.5)(webpack@5.102.1): dependencies: - '@babel/core': 7.27.7 + '@babel/core': 7.28.5 find-cache-dir: 4.0.0 - schema-utils: 4.3.2 - webpack: 5.100.2 + schema-utils: 4.3.3 + webpack: 5.102.1 babel-plugin-dynamic-import-node@2.3.3: dependencies: object.assign: 4.1.7 - babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.27.7): + babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.28.5): dependencies: - '@babel/compat-data': 7.27.7 - '@babel/core': 7.27.7 - '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.27.7) + '@babel/compat-data': 7.28.5 + '@babel/core': 7.28.5 + '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.5) semver: 6.3.1 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.11.1(@babel/core@7.27.7): + babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.28.5): dependencies: - '@babel/core': 7.27.7 - '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.27.7) - core-js-compat: 3.45.0 + '@babel/core': 7.28.5 + '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.5) + core-js-compat: 3.46.0 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-regenerator@0.6.5(@babel/core@7.27.7): + babel-plugin-polyfill-regenerator@0.6.5(@babel/core@7.28.5): dependencies: - '@babel/core': 7.27.7 - '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.27.7) + '@babel/core': 7.28.5 + '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.5) transitivePeerDependencies: - supports-color @@ -17220,36 +17308,48 @@ snapshots: balanced-match@1.0.2: {} - bare-events@2.6.1: - optional: true + bare-events@2.8.1: {} - bare-fs@4.2.0: + bare-fs@4.5.0: dependencies: - bare-events: 2.6.1 + bare-events: 2.8.1 bare-path: 3.0.0 - bare-stream: 2.7.0(bare-events@2.6.1) + bare-stream: 2.7.0(bare-events@2.8.1) + bare-url: 2.3.1 + fast-fifo: 1.3.2 + transitivePeerDependencies: + - bare-abort-controller optional: true - bare-os@3.6.1: + bare-os@3.6.2: optional: true bare-path@3.0.0: dependencies: - bare-os: 3.6.1 + bare-os: 3.6.2 optional: true - bare-stream@2.7.0(bare-events@2.6.1): + bare-stream@2.7.0(bare-events@2.8.1): dependencies: - streamx: 2.22.1 + streamx: 2.23.0 optionalDependencies: - bare-events: 2.6.1 + bare-events: 2.8.1 + transitivePeerDependencies: + - bare-abort-controller + optional: true + + bare-url@2.3.1: + dependencies: + bare-path: 3.0.0 optional: true base64-js@1.5.1: {} base64id@2.0.0: {} - batch-cluster@13.0.0: {} + baseline-browser-mapping@2.8.20: {} + + batch-cluster@15.0.1: {} batch@0.6.1: {} @@ -17262,22 +17362,25 @@ snapshots: bcrypt@6.0.0: dependencies: node-addon-api: 8.5.0 + node-gyp: 11.5.0 node-gyp-build: 4.8.4 + transitivePeerDependencies: + - supports-color big.js@5.2.2: {} binary-extensions@2.3.0: {} - bits-ui@2.9.8(@internationalized/date@3.8.2)(svelte@5.38.10): + bits-ui@2.9.8(@internationalized/date@3.8.2)(svelte@5.43.0): dependencies: '@floating-ui/core': 1.7.3 '@floating-ui/dom': 1.7.4 '@internationalized/date': 3.8.2 esm-env: 1.2.2 - runed: 0.29.2(svelte@5.38.10) - svelte: 5.38.10 - svelte-toolbelt: 0.9.3(svelte@5.38.10) - tabbable: 6.2.0 + runed: 0.29.2(svelte@5.43.0) + svelte: 5.43.0 + svelte-toolbelt: 0.9.3(svelte@5.43.0) + tabbable: 6.3.0 bl@4.1.0: dependencies: @@ -17360,15 +17463,18 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.25.3: + browserslist@4.27.0: dependencies: - caniuse-lite: 1.0.30001735 - electron-to-chromium: 1.5.207 - node-releases: 2.0.19 - update-browserslist-db: 1.1.3(browserslist@4.25.3) + baseline-browser-mapping: 2.8.20 + caniuse-lite: 1.0.30001751 + electron-to-chromium: 1.5.243 + node-releases: 2.0.26 + update-browserslist-db: 1.1.4(browserslist@4.27.0) buffer-crc32@1.0.0: {} + buffer-equal-constant-time@1.0.1: {} + buffer-from@1.1.2: {} buffer@5.7.1: @@ -17386,18 +17492,22 @@ snapshots: builtin-modules@5.0.0: {} - bullmq@5.58.5: + bullmq@5.62.1: dependencies: cron-parser: 4.9.0 - ioredis: 5.7.0 + ioredis: 5.8.2 msgpackr: 1.11.5 node-abort-controller: 3.1.1 - semver: 7.7.2 + semver: 7.7.3 tslib: 2.8.1 - uuid: 9.0.1 + uuid: 11.1.0 transitivePeerDependencies: - supports-color + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + busboy@1.6.0: dependencies: streamsearch: 1.1.0 @@ -17424,7 +17534,7 @@ snapshots: minipass-pipeline: 1.2.4 p-map: 7.0.3 ssri: 12.0.0 - tar: 7.4.3 + tar: 7.5.1 unique-filename: 4.0.0 cacheable-lookup@7.0.0: {} @@ -17436,7 +17546,7 @@ snapshots: http-cache-semantics: 4.2.0 keyv: 4.5.4 mimic-response: 4.0.0 - normalize-url: 8.0.2 + normalize-url: 8.1.0 responselike: 3.0.0 call-bind-apply-helpers@1.0.2: @@ -17456,8 +17566,6 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 - call-me-maybe@1.0.2: {} - callsites@3.1.0: {} camel-case@4.1.2: @@ -17475,12 +17583,12 @@ snapshots: caniuse-api@3.0.0: dependencies: - browserslist: 4.25.3 - caniuse-lite: 1.0.30001735 + browserslist: 4.27.0 + caniuse-lite: 1.0.30001751 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 - caniuse-lite@1.0.30001735: {} + caniuse-lite@1.0.30001751: {} canvas@2.11.2: dependencies: @@ -17533,8 +17641,6 @@ snapshots: chardet@2.1.0: {} - charset@1.0.1: {} - check-error@2.1.1: {} cheerio-select@2.1.0: @@ -17594,9 +17700,9 @@ snapshots: class-validator@0.14.2: dependencies: - '@types/validator': 13.15.3 + '@types/validator': 13.15.4 libphonenumber-js: 1.12.9 - validator: 13.15.15 + validator: 13.15.20 clean-css@5.3.3: dependencies: @@ -17662,8 +17768,6 @@ snapshots: clone@1.0.4: {} - clsx@1.2.1: {} - clsx@2.1.1: {} cluster-key-slot@1.1.2: {} @@ -17676,18 +17780,8 @@ snapshots: color-name@1.1.4: {} - color-string@1.9.1: - dependencies: - color-name: 1.1.4 - simple-swizzle: 0.2.2 - color-support@1.1.3: {} - color@4.2.3: - dependencies: - color-convert: 2.0.1 - color-string: 1.9.1 - colord@2.9.3: {} colorette@2.0.20: {} @@ -17720,13 +17814,11 @@ snapshots: commander@8.3.0: {} - comment-json@4.2.5: + comment-json@4.4.1: dependencies: array-timsort: 1.0.3 core-util-is: 1.0.3 esprima: 4.0.1 - has-own-prop: 2.0.0 - repeat-string: 1.6.1 common-path-prefix@3.0.0: {} @@ -17756,19 +17848,6 @@ snapshots: transitivePeerDependencies: - supports-color - compute-gcd@1.2.1: - dependencies: - validate.io-array: 1.0.6 - validate.io-function: 1.0.2 - validate.io-integer-array: 1.0.0 - - compute-lcm@1.1.2: - dependencies: - compute-gcd: 1.2.1 - validate.io-array: 1.0.6 - validate.io-function: 1.0.2 - validate.io-integer-array: 1.0.0 - concat-map@0.0.1: {} concat-stream@2.0.0: @@ -17837,25 +17916,23 @@ snapshots: depd: 2.0.0 keygrip: 1.1.0 - copy-text-to-clipboard@3.2.0: {} - - copy-webpack-plugin@11.0.0(webpack@5.100.2): + copy-webpack-plugin@11.0.0(webpack@5.102.1): dependencies: fast-glob: 3.3.3 glob-parent: 6.0.2 globby: 13.2.2 normalize-path: 3.0.0 - schema-utils: 4.3.2 + schema-utils: 4.3.3 serialize-javascript: 6.0.2 - webpack: 5.100.2 + webpack: 5.102.1 - core-js-compat@3.45.0: + core-js-compat@3.46.0: dependencies: - browserslist: 4.25.3 + browserslist: 4.27.0 - core-js-pure@3.43.0: {} + core-js-pure@3.46.0: {} - core-js@3.43.0: {} + core-js@3.46.0: {} core-util-is@1.0.3: {} @@ -17873,14 +17950,14 @@ snapshots: optionalDependencies: typescript: 5.8.3 - cosmiconfig@8.3.6(typescript@5.9.2): + cosmiconfig@8.3.6(typescript@5.9.3): dependencies: import-fresh: 3.3.1 js-yaml: 4.1.0 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: - typescript: 5.9.2 + typescript: 5.9.3 cpu-features@0.0.10: dependencies: @@ -17899,10 +17976,10 @@ snapshots: dependencies: luxon: 3.7.2 - cron@4.3.0: + cron@4.3.3: dependencies: - '@types/luxon': 3.6.2 - luxon: 3.6.1 + '@types/luxon': 3.7.1 + luxon: 3.7.2 cross-spawn@7.0.6: dependencies: @@ -17910,8 +17987,6 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - crypto-js@4.2.0: {} - crypto-random-string@4.0.0: dependencies: type-fest: 1.4.0 @@ -17921,18 +17996,18 @@ snapshots: postcss: 8.5.6 postcss-selector-parser: 7.1.0 - css-declaration-sorter@7.2.0(postcss@8.5.6): + css-declaration-sorter@7.3.0(postcss@8.5.6): dependencies: postcss: 8.5.6 - css-has-pseudo@7.0.2(postcss@8.5.6): + css-has-pseudo@7.0.3(postcss@8.5.6): dependencies: '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) postcss: 8.5.6 postcss-selector-parser: 7.1.0 postcss-value-parser: 4.2.0 - css-loader@6.11.0(webpack@5.100.2): + css-loader@6.11.0(webpack@5.102.1): dependencies: icss-utils: 5.1.0(postcss@8.5.6) postcss: 8.5.6 @@ -17941,19 +18016,19 @@ snapshots: postcss-modules-scope: 3.2.1(postcss@8.5.6) postcss-modules-values: 4.0.0(postcss@8.5.6) postcss-value-parser: 4.2.0 - semver: 7.7.2 + semver: 7.7.3 optionalDependencies: - webpack: 5.100.2 + webpack: 5.102.1 - css-minimizer-webpack-plugin@5.0.1(clean-css@5.3.3)(webpack@5.100.2): + css-minimizer-webpack-plugin@5.0.1(clean-css@5.3.3)(webpack@5.102.1): dependencies: - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 cssnano: 6.1.2(postcss@8.5.6) jest-worker: 29.7.0 postcss: 8.5.6 - schema-utils: 4.3.2 + schema-utils: 4.3.3 serialize-javascript: 6.0.2 - webpack: 5.100.2 + webpack: 5.102.1 optionalDependencies: clean-css: 5.3.3 @@ -17995,14 +18070,14 @@ snapshots: csscolorparser@1.0.3: {} - cssdb@8.3.1: {} + cssdb@8.4.2: {} cssesc@3.0.0: {} cssnano-preset-advanced@6.1.2(postcss@8.5.6): dependencies: autoprefixer: 10.4.21(postcss@8.5.6) - browserslist: 4.25.3 + browserslist: 4.27.0 cssnano-preset-default: 6.1.2(postcss@8.5.6) postcss: 8.5.6 postcss-discard-unused: 6.0.5(postcss@8.5.6) @@ -18012,8 +18087,8 @@ snapshots: cssnano-preset-default@6.1.2(postcss@8.5.6): dependencies: - browserslist: 4.25.3 - css-declaration-sorter: 7.2.0(postcss@8.5.6) + browserslist: 4.27.0 + css-declaration-sorter: 7.3.0(postcss@8.5.6) cssnano-utils: 4.0.2(postcss@8.5.6) postcss: 8.5.6 postcss-calc: 9.0.1(postcss@8.5.6) @@ -18146,9 +18221,12 @@ snapshots: deepmerge@4.3.1: {} - default-gateway@6.0.3: + default-browser-id@5.0.0: {} + + default-browser@5.2.1: dependencies: - execa: 5.1.1 + bundle-name: 4.1.0 + default-browser-id: 5.0.0 defaults@1.0.4: dependencies: @@ -18164,6 +18242,8 @@ snapshots: define-lazy-prop@2.0.0: {} + define-lazy-prop@3.0.0: {} + define-properties@1.2.1: dependencies: define-data-property: 1.1.4 @@ -18186,14 +18266,10 @@ snapshots: detect-europe-js@0.1.2: {} - detect-libc@2.1.0: {} + detect-libc@2.1.2: {} detect-node@2.1.0: {} - detect-package-manager@3.0.2: - dependencies: - execa: 5.1.1 - detect-port@1.6.1: dependencies: address: 1.2.2 @@ -18201,7 +18277,7 @@ snapshots: transitivePeerDependencies: - supports-color - devalue@5.3.2: {} + devalue@5.4.2: {} devlop@1.1.0: dependencies: @@ -18232,7 +18308,7 @@ snapshots: dependencies: '@leichtgewicht/ip-codec': 2.0.5 - docker-compose@1.2.0: + docker-compose@1.3.0: dependencies: yaml: 2.8.1 @@ -18241,25 +18317,25 @@ snapshots: debug: 4.4.3 readable-stream: 3.6.2 split-ca: 1.0.1 - ssh2: 1.16.0 + ssh2: 1.17.0 transitivePeerDependencies: - supports-color - dockerode@4.0.7: + dockerode@4.0.9: dependencies: '@balena/dockerignore': 1.0.2 - '@grpc/grpc-js': 1.13.4 + '@grpc/grpc-js': 1.14.0 '@grpc/proto-loader': 0.7.15 docker-modem: 5.0.6 protobufjs: 7.5.4 - tar-fs: 2.1.3 + tar-fs: 2.1.4 uuid: 10.0.0 transitivePeerDependencies: - supports-color - docusaurus-lunr-search@3.6.0(@docusaurus/core@3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + docusaurus-lunr-search@3.6.0(@docusaurus/core@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.2)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) autocomplete.js: 0.37.1 clsx: 2.1.1 gauge: 3.0.2 @@ -18277,129 +18353,6 @@ snapshots: unified: 9.2.2 unist-util-is: 4.1.0 - docusaurus-plugin-openapi@0.7.6(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2): - dependencies: - '@docusaurus/mdx-loader': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/plugin-content-docs': 3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - '@docusaurus/utils': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-common': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.8.1(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - chalk: 4.1.2 - clsx: 1.2.1 - js-yaml: 4.1.0 - json-refs: 3.0.15 - json-schema-resolve-allof: 1.5.0 - lodash: 4.17.21 - openapi-to-postmanv2: 4.25.0(encoding@0.1.13) - postman-collection: 4.5.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - webpack: 5.100.2 - transitivePeerDependencies: - - '@docusaurus/faster' - - '@mdx-js/react' - - '@parcel/css' - - '@rspack/core' - - '@swc/core' - - '@swc/css' - - acorn - - bufferutil - - csso - - debug - - encoding - - esbuild - - lightningcss - - supports-color - - typescript - - uglify-js - - utf-8-validate - - webpack-cli - - docusaurus-plugin-proxy@0.7.6: {} - - docusaurus-preset-openapi@0.7.6(@algolia/client-search@5.29.0)(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(@types/react@19.1.13)(acorn@8.15.0)(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(redux@4.2.1)(search-insights@2.17.3)(typescript@5.9.2): - dependencies: - '@docusaurus/preset-classic': 3.8.1(@algolia/client-search@5.29.0)(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(@types/react@19.1.13)(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.9.2) - docusaurus-plugin-openapi: 0.7.6(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - docusaurus-plugin-proxy: 0.7.6 - docusaurus-theme-openapi: 0.7.6(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(@types/react@19.1.13)(acorn@8.15.0)(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(redux@4.2.1)(typescript@5.9.2) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - transitivePeerDependencies: - - '@algolia/client-search' - - '@docusaurus/faster' - - '@docusaurus/plugin-content-docs' - - '@mdx-js/react' - - '@parcel/css' - - '@rspack/core' - - '@swc/core' - - '@swc/css' - - '@types/react' - - acorn - - bufferutil - - csso - - debug - - encoding - - esbuild - - lightningcss - - react-native - - redux - - search-insights - - supports-color - - typescript - - uglify-js - - utf-8-validate - - webpack-cli - - docusaurus-theme-openapi@0.7.6(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(@types/react@19.1.13)(acorn@8.15.0)(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(redux@4.2.1)(typescript@5.9.2): - dependencies: - '@docusaurus/theme-common': 3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2))(acorn@8.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mdx-js/react': 3.1.1(@types/react@19.1.13)(react@18.3.1) - '@monaco-editor/react': 4.7.0(monaco-editor@0.31.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@reduxjs/toolkit': 1.9.7(react-redux@7.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) - buffer: 6.0.3 - clsx: 1.2.1 - crypto-js: 4.2.0 - docusaurus-plugin-openapi: 0.7.6(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@18.3.1))(acorn@8.15.0)(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) - immer: 9.0.21 - lodash: 4.17.21 - marked: 11.2.0 - monaco-editor: 0.31.1 - postman-code-generators: 1.14.2 - postman-collection: 4.5.0 - prism-react-renderer: 2.4.1(react@18.3.1) - process: 0.11.10 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-magic-dropzone: 1.0.1 - react-redux: 7.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - redux-devtools-extension: 2.13.9(redux@4.2.1) - refractor: 4.9.0 - striptags: 3.2.0 - webpack: 5.100.2 - transitivePeerDependencies: - - '@docusaurus/faster' - - '@docusaurus/plugin-content-docs' - - '@parcel/css' - - '@rspack/core' - - '@swc/core' - - '@swc/css' - - '@types/react' - - acorn - - bufferutil - - csso - - debug - - encoding - - esbuild - - lightningcss - - react-native - - redux - - supports-color - - typescript - - uglify-js - - utf-8-validate - - webpack-cli - dom-accessibility-api@0.5.16: {} dom-accessibility-api@0.6.3: {} @@ -18458,7 +18411,7 @@ snapshots: dependencies: is-obj: 2.0.0 - dotenv@17.2.2: {} + dotenv@17.2.3: {} dunder-proto@1.0.1: dependencies: @@ -18474,11 +18427,15 @@ snapshots: eastasianwidth@0.2.0: {} + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + ee-first@1.1.1: {} - electron-to-chromium@1.5.207: {} + electron-to-chromium@1.5.243: {} - emoji-regex@10.5.0: {} + emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} @@ -18520,7 +18477,7 @@ snapshots: engine.io@6.6.4: dependencies: '@types/cors': 2.8.19 - '@types/node': 22.18.5 + '@types/node': 22.19.0 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.7.2 @@ -18536,7 +18493,7 @@ snapshots: enhanced-resolve@5.18.3: dependencies: graceful-fs: 4.2.11 - tapable: 2.2.2 + tapable: 2.3.0 entities@2.2.0: {} @@ -18582,8 +18539,6 @@ snapshots: es5-ext: 0.10.64 es6-symbol: 3.1.4 - es6-promise@3.3.1: {} - es6-symbol@3.1.4: dependencies: d: 1.0.2 @@ -18608,7 +18563,7 @@ snapshots: '@types/estree-jsx': 1.0.5 acorn: 8.15.0 esast-util-from-estree: 2.0.0 - vfile-message: 4.0.2 + vfile-message: 4.0.3 esbuild@0.19.12: optionalDependencies: @@ -18636,34 +18591,34 @@ snapshots: '@esbuild/win32-ia32': 0.19.12 '@esbuild/win32-x64': 0.19.12 - esbuild@0.25.9: + esbuild@0.25.12: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.9 - '@esbuild/android-arm': 0.25.9 - '@esbuild/android-arm64': 0.25.9 - '@esbuild/android-x64': 0.25.9 - '@esbuild/darwin-arm64': 0.25.9 - '@esbuild/darwin-x64': 0.25.9 - '@esbuild/freebsd-arm64': 0.25.9 - '@esbuild/freebsd-x64': 0.25.9 - '@esbuild/linux-arm': 0.25.9 - '@esbuild/linux-arm64': 0.25.9 - '@esbuild/linux-ia32': 0.25.9 - '@esbuild/linux-loong64': 0.25.9 - '@esbuild/linux-mips64el': 0.25.9 - '@esbuild/linux-ppc64': 0.25.9 - '@esbuild/linux-riscv64': 0.25.9 - '@esbuild/linux-s390x': 0.25.9 - '@esbuild/linux-x64': 0.25.9 - '@esbuild/netbsd-arm64': 0.25.9 - '@esbuild/netbsd-x64': 0.25.9 - '@esbuild/openbsd-arm64': 0.25.9 - '@esbuild/openbsd-x64': 0.25.9 - '@esbuild/openharmony-arm64': 0.25.9 - '@esbuild/sunos-x64': 0.25.9 - '@esbuild/win32-arm64': 0.25.9 - '@esbuild/win32-ia32': 0.25.9 - '@esbuild/win32-x64': 0.25.9 + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 escalade@3.2.0: {} @@ -18686,67 +18641,60 @@ snapshots: source-map: 0.6.1 optional: true - eslint-config-prettier@10.1.8(eslint@9.35.0(jiti@2.5.1)): + eslint-config-prettier@10.1.8(eslint@9.38.0(jiti@2.6.1)): dependencies: - eslint: 9.35.0(jiti@2.5.1) + eslint: 9.38.0(jiti@2.6.1) - eslint-p@0.26.0(jiti@2.5.1): - dependencies: - eslint: 9.35.0(jiti@2.5.1) - transitivePeerDependencies: - - jiti - - supports-color - - eslint-plugin-compat@6.0.2(eslint@9.35.0(jiti@2.5.1)): + eslint-plugin-compat@6.0.2(eslint@9.38.0(jiti@2.6.1)): dependencies: '@mdn/browser-compat-data': 5.7.6 ast-metadata-inferer: 0.8.1 - browserslist: 4.25.3 - caniuse-lite: 1.0.30001735 - eslint: 9.35.0(jiti@2.5.1) + browserslist: 4.27.0 + caniuse-lite: 1.0.30001751 + eslint: 9.38.0(jiti@2.6.1) find-up: 5.0.0 globals: 15.15.0 lodash.memoize: 4.1.2 - semver: 7.7.2 + semver: 7.7.3 - eslint-plugin-prettier@5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1))(prettier@3.6.2): + eslint-plugin-prettier@5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1))(prettier@3.6.2): dependencies: - eslint: 9.35.0(jiti@2.5.1) + eslint: 9.38.0(jiti@2.6.1) prettier: 3.6.2 prettier-linter-helpers: 1.0.0 synckit: 0.11.11 optionalDependencies: '@types/eslint': 9.6.1 - eslint-config-prettier: 10.1.8(eslint@9.35.0(jiti@2.5.1)) + eslint-config-prettier: 10.1.8(eslint@9.38.0(jiti@2.6.1)) - eslint-plugin-svelte@3.12.3(eslint@9.35.0(jiti@2.5.1))(svelte@5.38.10): + eslint-plugin-svelte@3.12.5(eslint@9.38.0(jiti@2.6.1))(svelte@5.43.0): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.35.0(jiti@2.5.1)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.38.0(jiti@2.6.1)) '@jridgewell/sourcemap-codec': 1.5.5 - eslint: 9.35.0(jiti@2.5.1) + eslint: 9.38.0(jiti@2.6.1) esutils: 2.0.3 globals: 16.4.0 known-css-properties: 0.37.0 postcss: 8.5.6 postcss-load-config: 3.1.4(postcss@8.5.6) postcss-safe-parser: 7.0.1(postcss@8.5.6) - semver: 7.7.2 - svelte-eslint-parser: 1.3.2(svelte@5.38.10) + semver: 7.7.3 + svelte-eslint-parser: 1.4.0(svelte@5.43.0) optionalDependencies: - svelte: 5.38.10 + svelte: 5.43.0 transitivePeerDependencies: - ts-node - eslint-plugin-unicorn@60.0.0(eslint@9.35.0(jiti@2.5.1)): + eslint-plugin-unicorn@60.0.0(eslint@9.38.0(jiti@2.6.1)): dependencies: - '@babel/helper-validator-identifier': 7.27.1 - '@eslint-community/eslint-utils': 4.9.0(eslint@9.35.0(jiti@2.5.1)) + '@babel/helper-validator-identifier': 7.28.5 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.38.0(jiti@2.6.1)) '@eslint/plugin-kit': 0.3.5 change-case: 5.4.4 ci-info: 4.3.0 clean-regexp: 1.0.0 - core-js-compat: 3.45.0 - eslint: 9.35.0(jiti@2.5.1) + core-js-compat: 3.46.0 + eslint: 9.38.0(jiti@2.6.1) esquery: 1.6.0 find-up-simple: 1.0.1 globals: 16.4.0 @@ -18756,7 +18704,29 @@ snapshots: pluralize: 8.0.0 regexp-tree: 0.1.27 regjsparser: 0.12.0 - semver: 7.7.2 + semver: 7.7.3 + strip-indent: 4.0.0 + + eslint-plugin-unicorn@61.0.2(eslint@9.38.0(jiti@2.6.1)): + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.38.0(jiti@2.6.1)) + '@eslint/plugin-kit': 0.3.5 + change-case: 5.4.4 + ci-info: 4.3.0 + clean-regexp: 1.0.0 + core-js-compat: 3.46.0 + eslint: 9.38.0(jiti@2.6.1) + esquery: 1.6.0 + find-up-simple: 1.0.1 + globals: 16.4.0 + indent-string: 5.0.0 + is-builtin-module: 5.0.0 + jsesc: 3.1.0 + pluralize: 8.0.0 + regexp-tree: 0.1.27 + regjsparser: 0.12.0 + semver: 7.7.3 strip-indent: 4.0.0 eslint-scope@5.1.1: @@ -18773,21 +18743,20 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.35.0(jiti@2.5.1): + eslint@9.38.0(jiti@2.6.1): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.35.0(jiti@2.5.1)) - '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.21.0 - '@eslint/config-helpers': 0.3.1 - '@eslint/core': 0.15.2 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.38.0(jiti@2.6.1)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.1 + '@eslint/core': 0.16.0 '@eslint/eslintrc': 3.3.1 - '@eslint/js': 9.35.0 - '@eslint/plugin-kit': 0.3.5 + '@eslint/js': 9.38.0 + '@eslint/plugin-kit': 0.4.0 '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 '@types/estree': 1.0.8 - '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 @@ -18811,7 +18780,7 @@ snapshots: natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: - jiti: 2.5.1 + jiti: 2.6.1 transitivePeerDependencies: - supports-color @@ -18870,9 +18839,9 @@ snapshots: dependencies: '@types/estree-jsx': 1.0.5 astring: 1.9.0 - source-map: 0.7.4 + source-map: 0.7.6 - estree-util-value-to-estree@3.4.0: + estree-util-value-to-estree@3.5.0: dependencies: '@types/estree': 1.0.8 @@ -18891,13 +18860,13 @@ snapshots: eta@2.2.0: {} - eta@3.5.0: {} + eta@4.0.1: {} etag@1.8.1: {} eval@0.1.8: dependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 require-like: 0.1.2 event-emitter@0.3.5: @@ -18909,8 +18878,16 @@ snapshots: eventemitter3@4.0.7: {} + events-universal@1.0.1: + dependencies: + bare-events: 2.8.1 + transitivePeerDependencies: + - bare-abort-controller + events@3.3.0: {} + eventsource-parser@3.0.6: {} + execa@5.1.1: dependencies: cross-spawn: 7.0.6 @@ -18923,25 +18900,25 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 - exiftool-vendored.exe@13.0.0: + exiftool-vendored.exe@13.41.0: optional: true - exiftool-vendored.pl@13.0.1: {} + exiftool-vendored.pl@13.41.0: {} - exiftool-vendored@28.8.0: + exiftool-vendored@31.3.0: dependencies: - '@photostructure/tz-lookup': 11.2.0 + '@photostructure/tz-lookup': 11.3.0 '@types/luxon': 3.7.1 - batch-cluster: 13.0.0 - exiftool-vendored.pl: 13.0.1 + batch-cluster: 15.0.1 + exiftool-vendored.pl: 13.41.0 he: 1.2.0 luxon: 3.7.2 optionalDependencies: - exiftool-vendored.exe: 13.0.0 + exiftool-vendored.exe: 13.41.0 expect-type@1.2.1: {} - exponential-backoff@3.1.2: {} + exponential-backoff@3.1.3: {} express@4.21.2: dependencies: @@ -19060,7 +19037,7 @@ snapshots: fast-safe-stringify@2.1.1: {} - fast-uri@3.0.6: {} + fast-uri@3.1.0: {} fast-xml-parser@5.2.5: dependencies: @@ -19096,11 +19073,11 @@ snapshots: dependencies: flat-cache: 4.0.1 - file-loader@6.2.0(webpack@5.100.2): + file-loader@6.2.0(webpack@5.102.1): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.100.2 + webpack: 5.102.1 file-source@0.6.1: dependencies: @@ -19111,12 +19088,10 @@ snapshots: '@tokenizer/inflate': 0.2.7 strtok3: 10.3.4 token-types: 6.1.1 - uint8array-extras: 1.4.1 + uint8array-extras: 1.5.0 transitivePeerDependencies: - supports-color - file-type@3.9.0: {} - fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -19180,16 +19155,14 @@ snapshots: async: 0.2.10 which: 1.3.1 - follow-redirects@1.15.9: {} - - foreach@2.0.6: {} + follow-redirects@1.15.11: {} foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 signal-exit: 4.1.0 - fork-ts-checker-webpack-plugin@9.1.0(typescript@5.8.3)(webpack@5.100.2(@swc/core@1.13.5(@swc/helpers@0.5.17))): + fork-ts-checker-webpack-plugin@9.1.0(typescript@5.8.3)(webpack@5.100.2(@swc/core@1.14.0(@swc/helpers@0.5.17))): dependencies: '@babel/code-frame': 7.27.1 chalk: 4.1.2 @@ -19201,10 +19174,10 @@ snapshots: minimatch: 3.1.2 node-abort-controller: 3.1.1 schema-utils: 3.3.0 - semver: 7.7.2 - tapable: 2.2.2 + semver: 7.7.3 + tapable: 2.3.0 typescript: 5.8.3 - webpack: 5.100.2(@swc/core@1.13.5(@swc/helpers@0.5.17)) + webpack: 5.100.2(@swc/core@1.14.0(@swc/helpers@0.5.17)) form-data-encoder@2.1.4: {} @@ -19218,13 +19191,6 @@ snapshots: format@0.2.2: {} - formidable@2.1.5: - dependencies: - '@paralleldrive/cuid2': 2.2.2 - dezalgo: 1.0.4 - once: 1.4.0 - qs: 6.14.0 - formidable@3.5.4: dependencies: '@paralleldrive/cuid2': 2.2.2 @@ -19249,7 +19215,7 @@ snapshots: jsonfile: 6.2.0 universalify: 2.0.1 - fs-extra@11.3.0: + fs-extra@11.3.2: dependencies: graceful-fs: 4.2.11 jsonfile: 6.2.0 @@ -19336,8 +19302,6 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 - get-stdin@5.0.1: {} - get-stream@6.0.1: {} github-slugger@1.5.0: {} @@ -19352,6 +19316,10 @@ snapshots: dependencies: is-glob: 4.0.3 + glob-to-regex.js@1.2.0(tslib@2.8.1): + dependencies: + tslib: 2.8.1 + glob-to-regexp@0.4.1: {} glob@10.4.5: @@ -19367,7 +19335,7 @@ snapshots: dependencies: foreground-child: 3.3.1 jackspeak: 4.1.1 - minimatch: 10.0.3 + minimatch: 10.1.1 minipass: 7.1.2 package-json-from-dist: 1.0.1 path-scurry: 2.0.0 @@ -19385,8 +19353,6 @@ snapshots: dependencies: ini: 2.0.0 - globals@11.12.0: {} - globals@14.0.0: {} globals@15.15.0: {} @@ -19436,10 +19402,6 @@ snapshots: graphemer@1.4.0: {} - graphlib@2.1.8: - dependencies: - lodash: 4.17.21 - gray-matter@4.0.3: dependencies: js-yaml: 3.14.1 @@ -19464,16 +19426,14 @@ snapshots: optionalDependencies: uglify-js: 3.19.3 - happy-dom@18.0.1: + happy-dom@20.0.10: dependencies: - '@types/node': 20.19.2 + '@types/node': 20.19.24 '@types/whatwg-mimetype': 3.0.2 whatwg-mimetype: 3.0.0 has-flag@4.0.0: {} - has-own-prop@2.0.0: {} - has-property-descriptors@1.0.2: dependencies: es-define-property: 1.0.1 @@ -19518,10 +19478,6 @@ snapshots: hast-util-parse-selector@2.2.5: {} - hast-util-parse-selector@3.1.1: - dependencies: - '@types/hast': 2.3.10 - hast-util-parse-selector@4.0.0: dependencies: '@types/hast': 3.0.4 @@ -19574,7 +19530,7 @@ snapshots: mdast-util-mdxjs-esm: 2.0.1 property-information: 7.1.0 space-separated-tokens: 2.0.2 - style-to-js: 1.1.17 + style-to-js: 1.1.18 unist-util-position: 5.0.0 zwitch: 2.0.4 transitivePeerDependencies: @@ -19594,9 +19550,9 @@ snapshots: mdast-util-mdxjs-esm: 2.0.1 property-information: 7.1.0 space-separated-tokens: 2.0.2 - style-to-js: 1.1.17 + style-to-js: 1.1.18 unist-util-position: 5.0.0 - vfile-message: 4.0.2 + vfile-message: 4.0.3 transitivePeerDependencies: - supports-color @@ -19632,14 +19588,6 @@ snapshots: property-information: 5.6.0 space-separated-tokens: 1.1.5 - hastscript@7.2.0: - dependencies: - '@types/hast': 2.3.10 - comma-separated-tokens: 2.0.3 - hast-util-parse-selector: 3.1.1 - property-information: 6.5.0 - space-separated-tokens: 2.0.2 - hastscript@9.0.1: dependencies: '@types/hast': 3.0.4 @@ -19650,6 +19598,8 @@ snapshots: he@1.2.0: {} + highlight.js@11.11.1: {} + history@4.10.1: dependencies: '@babel/runtime': 7.28.4 @@ -19685,8 +19635,6 @@ snapshots: whatwg-encoding: 3.1.1 optional: true - html-entities@2.6.0: {} - html-escaper@2.0.2: {} html-minifier-terser@6.1.0: @@ -19697,7 +19645,7 @@ snapshots: he: 1.2.0 param-case: 3.0.4 relateurl: 0.2.7 - terser: 5.43.1 + terser: 5.44.0 html-minifier-terser@7.2.0: dependencies: @@ -19707,7 +19655,7 @@ snapshots: entities: 4.5.0 param-case: 3.0.4 relateurl: 0.2.7 - terser: 5.43.1 + terser: 5.44.0 html-tags@3.3.1: {} @@ -19721,15 +19669,15 @@ snapshots: html-void-elements@3.0.0: {} - html-webpack-plugin@5.6.3(webpack@5.100.2): + html-webpack-plugin@5.6.4(webpack@5.102.1): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 lodash: 4.17.21 pretty-error: 4.0.0 - tapable: 2.2.2 + tapable: 2.3.0 optionalDependencies: - webpack: 5.100.2 + webpack: 5.102.1 htmlparser2@6.1.0: dependencies: @@ -19795,30 +19743,26 @@ snapshots: transitivePeerDependencies: - supports-color - http-proxy-middleware@2.0.9(@types/express@4.17.23): + http-proxy-middleware@2.0.9(@types/express@4.17.25): dependencies: - '@types/http-proxy': 1.17.16 + '@types/http-proxy': 1.17.17 http-proxy: 1.18.1 is-glob: 4.0.3 is-plain-obj: 3.0.0 micromatch: 4.0.8 optionalDependencies: - '@types/express': 4.17.23 + '@types/express': 4.17.25 transitivePeerDependencies: - debug http-proxy@1.18.1: dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.15.9 + follow-redirects: 1.15.11 requires-port: 1.0.0 transitivePeerDependencies: - debug - http-reasons@0.1.0: {} - - http2-client@1.3.5: {} - http2-wrapper@2.2.1: dependencies: quick-lru: 5.1.1 @@ -19840,6 +19784,8 @@ snapshots: human-signals@2.1.0: {} + hyperdyperid@1.2.0: {} + i18n-iso-countries@7.14.0: dependencies: diacritics: 1.3.0 @@ -19872,14 +19818,12 @@ snapshots: immediate@3.3.0: {} - immer@9.0.21: {} - import-fresh@3.3.1: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - import-in-the-middle@1.14.2: + import-in-the-middle@2.0.0: dependencies: acorn: 8.15.0 acorn-import-attributes: 1.9.5(acorn@8.15.0) @@ -19911,9 +19855,9 @@ snapshots: inline-style-parser@0.2.4: {} - inquirer@8.2.7(@types/node@22.18.5): + inquirer@8.2.7(@types/node@22.19.0): dependencies: - '@inquirer/external-editor': 1.0.2(@types/node@22.18.5) + '@inquirer/external-editor': 1.0.2(@types/node@22.19.0) ansi-escapes: 4.3.2 chalk: 4.1.2 cli-cursor: 3.1.0 @@ -19933,22 +19877,20 @@ snapshots: internmap@2.0.3: {} - interpret@1.4.0: {} - - intl-messageformat@10.7.16: + intl-messageformat@10.7.18: dependencies: - '@formatjs/ecma402-abstract': 2.3.4 + '@formatjs/ecma402-abstract': 2.3.6 '@formatjs/fast-memoize': 2.2.7 - '@formatjs/icu-messageformat-parser': 2.11.2 + '@formatjs/icu-messageformat-parser': 2.11.4 tslib: 2.8.1 invariant@2.2.4: dependencies: loose-envify: 1.4.0 - ioredis@5.7.0: + ioredis@5.8.2: dependencies: - '@ioredis/commands': 1.3.0 + '@ioredis/commands': 1.4.0 cluster-key-slot: 1.1.2 debug: 4.4.3 denque: 2.1.0 @@ -19975,8 +19917,6 @@ snapshots: is-arrayish@0.2.1: {} - is-arrayish@0.3.2: {} - is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 @@ -19999,6 +19939,8 @@ snapshots: is-docker@2.2.1: {} + is-docker@3.0.0: {} + is-extendable@0.1.1: {} is-extglob@2.1.1: {} @@ -20011,6 +19953,10 @@ snapshots: is-hexadecimal@2.0.1: {} + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + is-installed-globally@0.4.0: dependencies: global-dirs: 3.0.1 @@ -20020,7 +19966,9 @@ snapshots: is-interactive@2.0.0: {} - is-npm@6.0.0: {} + is-network-error@1.3.0: {} + + is-npm@6.1.0: {} is-number@7.0.0: {} @@ -20071,6 +20019,10 @@ snapshots: dependencies: is-docker: 2.2.1 + is-wsl@3.1.0: + dependencies: + is-inside-container: 1.0.0 + is-yarn-global@0.4.1: {} isarray@0.0.1: {} @@ -20093,7 +20045,7 @@ snapshots: istanbul-lib-source-maps@5.0.6: dependencies: - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 debug: 4.4.3 istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: @@ -20119,7 +20071,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.18.5 + '@types/node': 22.19.0 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -20127,13 +20079,13 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 merge-stream: 2.0.0 supports-color: 8.1.1 jest-worker@29.7.0: dependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -20142,7 +20094,7 @@ snapshots: jiti@2.4.2: {} - jiti@2.5.1: {} + jiti@2.6.1: {} joi@17.13.3: dependencies: @@ -20273,42 +20225,12 @@ snapshots: json-parse-even-better-errors@2.3.1: {} - json-pointer@0.6.2: - dependencies: - foreach: 2.0.6 - - json-refs@3.0.15: - dependencies: - commander: 4.1.1 - graphlib: 2.1.8 - js-yaml: 3.14.1 - lodash: 4.17.21 - native-promise-only: 0.8.1 - path-loader: 1.0.12 - slash: 3.0.0 - uri-js: 4.4.1 - transitivePeerDependencies: - - supports-color - - json-schema-compare@0.2.2: - dependencies: - lodash: 4.17.21 - - json-schema-merge-allof@0.8.1: - dependencies: - compute-lcm: 1.1.2 - json-schema-compare: 0.2.2 - lodash: 4.17.21 - - json-schema-resolve-allof@1.5.0: - dependencies: - get-stdin: 5.0.1 - lodash: 4.17.21 - json-schema-traverse@0.4.1: {} json-schema-traverse@1.0.0: {} + json-schema@0.4.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} json-stringify-pretty-compact@4.0.0: {} @@ -20323,10 +20245,34 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jsonwebtoken@9.0.2: + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.7.3 + just-compare@2.3.0: {} justified-layout@4.1.0: {} + jwa@1.4.2: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@3.2.2: + dependencies: + jwa: 1.4.2 + safe-buffer: 5.2.1 + kdbush@3.0.0: {} kdbush@4.0.2: {} @@ -20349,7 +20295,7 @@ snapshots: koa-compose@4.1.0: {} - koa@3.0.1: + koa@3.1.1: dependencies: accepts: 1.3.8 content-disposition: 0.5.4 @@ -20370,9 +20316,10 @@ snapshots: type-is: 2.0.1 vary: 1.1.2 - kysely-postgres-js@2.0.0(kysely@0.28.2)(postgres@3.4.7): + kysely-postgres-js@3.0.0(kysely@0.28.2)(postgres@3.4.7): dependencies: kysely: 0.28.2 + optionalDependencies: postgres: 3.4.7 kysely@0.28.2: {} @@ -20381,7 +20328,7 @@ snapshots: dependencies: package-json: 8.1.1 - launch-editor@2.10.0: + launch-editor@2.12.0: dependencies: picocolors: 1.1.1 shell-quote: 1.8.3 @@ -20401,50 +20348,54 @@ snapshots: libphonenumber-js@1.12.9: {} - lightningcss-darwin-arm64@1.30.1: + lightningcss-android-arm64@1.30.2: optional: true - lightningcss-darwin-x64@1.30.1: + lightningcss-darwin-arm64@1.30.2: optional: true - lightningcss-freebsd-x64@1.30.1: + lightningcss-darwin-x64@1.30.2: optional: true - lightningcss-linux-arm-gnueabihf@1.30.1: + lightningcss-freebsd-x64@1.30.2: optional: true - lightningcss-linux-arm64-gnu@1.30.1: + lightningcss-linux-arm-gnueabihf@1.30.2: optional: true - lightningcss-linux-arm64-musl@1.30.1: + lightningcss-linux-arm64-gnu@1.30.2: optional: true - lightningcss-linux-x64-gnu@1.30.1: + lightningcss-linux-arm64-musl@1.30.2: optional: true - lightningcss-linux-x64-musl@1.30.1: + lightningcss-linux-x64-gnu@1.30.2: optional: true - lightningcss-win32-arm64-msvc@1.30.1: + lightningcss-linux-x64-musl@1.30.2: optional: true - lightningcss-win32-x64-msvc@1.30.1: + lightningcss-win32-arm64-msvc@1.30.2: optional: true - lightningcss@1.30.1: + lightningcss-win32-x64-msvc@1.30.2: + optional: true + + lightningcss@1.30.2: dependencies: - detect-libc: 2.1.0 + detect-libc: 2.1.2 optionalDependencies: - lightningcss-darwin-arm64: 1.30.1 - lightningcss-darwin-x64: 1.30.1 - lightningcss-freebsd-x64: 1.30.1 - lightningcss-linux-arm-gnueabihf: 1.30.1 - lightningcss-linux-arm64-gnu: 1.30.1 - lightningcss-linux-arm64-musl: 1.30.1 - lightningcss-linux-x64-gnu: 1.30.1 - lightningcss-linux-x64-musl: 1.30.1 - lightningcss-win32-arm64-msvc: 1.30.1 - lightningcss-win32-x64-msvc: 1.30.1 + lightningcss-android-arm64: 1.30.2 + lightningcss-darwin-arm64: 1.30.2 + lightningcss-darwin-x64: 1.30.2 + lightningcss-freebsd-x64: 1.30.2 + lightningcss-linux-arm-gnueabihf: 1.30.2 + lightningcss-linux-arm64-gnu: 1.30.2 + lightningcss-linux-arm64-musl: 1.30.2 + lightningcss-linux-x64-gnu: 1.30.2 + lightningcss-linux-x64-musl: 1.30.2 + lightningcss-win32-arm64-msvc: 1.30.2 + lightningcss-win32-x64-msvc: 1.30.2 lilconfig@2.1.0: {} @@ -20452,13 +20403,11 @@ snapshots: lines-and-columns@1.2.4: {} - liquid-json@0.3.1: {} - - load-esm@1.0.2: {} + load-esm@1.0.3: {} load-tsconfig@0.2.5: {} - loader-runner@4.3.0: {} + loader-runner@4.3.1: {} loader-utils@2.0.4: dependencies: @@ -20488,12 +20437,26 @@ snapshots: lodash.defaults@4.2.0: {} + lodash.includes@4.3.0: {} + lodash.isarguments@3.1.0: {} + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + lodash.memoize@4.1.2: {} lodash.merge@4.6.2: {} + lodash.once@4.1.1: {} + lodash.uniq@4.5.0: {} lodash@4.17.21: {} @@ -20545,8 +20508,6 @@ snapshots: lunr@2.3.9: {} - luxon@3.6.1: {} - luxon@3.7.2: {} lz-string@1.5.0: {} @@ -20555,14 +20516,14 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - magic-string@0.30.19: + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 magicast@0.3.5: dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 source-map-js: 1.2.1 make-dir@3.1.0: @@ -20571,7 +20532,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.7.2 + semver: 7.7.3 make-fetch-happen@14.0.3: dependencies: @@ -20614,7 +20575,7 @@ snapshots: tinyqueue: 2.0.3 vt-pbf: 3.1.3 - maplibre-gl@5.7.1: + maplibre-gl@5.10.0: dependencies: '@mapbox/geojson-rewind': 0.5.2 '@mapbox/jsonlint-lines-primitives': 2.0.2 @@ -20623,7 +20584,7 @@ snapshots: '@mapbox/unitbezier': 0.0.1 '@mapbox/vector-tile': 2.0.4 '@mapbox/whoots-js': 3.1.0 - '@maplibre/maplibre-gl-style-spec': 23.3.0 + '@maplibre/maplibre-gl-style-spec': 24.3.1 '@maplibre/vt-pbf': 4.0.3 '@types/geojson': 7946.0.16 '@types/geojson-vt': 3.2.5 @@ -20649,17 +20610,12 @@ snapshots: markdown-table@3.0.4: {} - marked@11.2.0: {} + marked@15.0.12: {} - marked@7.0.4: {} + marked@16.4.1: {} math-intrinsics@1.1.0: {} - md-to-react-email@5.0.5(react@19.1.1): - dependencies: - marked: 7.0.4 - react: 19.1.1 - mdast-util-directive@3.1.0: dependencies: '@types/mdast': 4.0.4 @@ -20670,7 +20626,7 @@ snapshots: mdast-util-to-markdown: 2.1.2 parse-entities: 4.0.2 stringify-entities: 4.0.4 - unist-util-visit-parents: 6.0.1 + unist-util-visit-parents: 6.0.2 transitivePeerDependencies: - supports-color @@ -20678,8 +20634,8 @@ snapshots: dependencies: '@types/mdast': 4.0.4 escape-string-regexp: 5.0.0 - unist-util-is: 6.0.0 - unist-util-visit-parents: 6.0.1 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 mdast-util-from-markdown@2.0.2: dependencies: @@ -20790,7 +20746,7 @@ snapshots: parse-entities: 4.0.2 stringify-entities: 4.0.4 unist-util-stringify-position: 4.0.0 - vfile-message: 4.0.2 + vfile-message: 4.0.3 transitivePeerDependencies: - supports-color @@ -20818,7 +20774,7 @@ snapshots: mdast-util-phrasing@4.1.0: dependencies: '@types/mdast': 4.0.4 - unist-util-is: 6.0.0 + unist-util-is: 6.0.1 mdast-util-to-hast@13.2.0: dependencies: @@ -20860,6 +20816,15 @@ snapshots: dependencies: fs-monkey: 1.1.0 + memfs@4.50.0: + dependencies: + '@jsonjoy.com/json-pack': 1.21.0(tslib@2.8.1) + '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) + glob-to-regex.js: 1.2.0(tslib@2.8.1) + thingies: 2.5.0(tslib@2.8.1) + tree-dump: 1.1.0(tslib@2.8.1) + tslib: 2.8.1 + memoizee@0.4.17: dependencies: d: 1.0.2 @@ -20997,7 +20962,7 @@ snapshots: micromark-util-events-to-acorn: 2.0.3 micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 - vfile-message: 4.0.2 + vfile-message: 4.0.3 micromark-extension-mdx-md@2.0.0: dependencies: @@ -21013,7 +20978,7 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 unist-util-position-from-estree: 2.0.0 - vfile-message: 4.0.2 + vfile-message: 4.0.3 micromark-extension-mdxjs@3.0.0: dependencies: @@ -21049,7 +21014,7 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 unist-util-position-from-estree: 2.0.0 - vfile-message: 4.0.2 + vfile-message: 4.0.3 micromark-factory-space@1.1.0: dependencies: @@ -21121,7 +21086,7 @@ snapshots: estree-util-visit: 2.0.0 micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 - vfile-message: 4.0.2 + vfile-message: 4.0.3 micromark-util-html-tag-name@2.0.1: {} @@ -21187,10 +21152,6 @@ snapshots: mime-db@1.54.0: {} - mime-format@2.0.1: - dependencies: - charset: 1.0.1 - mime-types@2.1.18: dependencies: mime-db: 1.33.0 @@ -21220,15 +21181,15 @@ snapshots: min-indent@1.0.1: {} - mini-css-extract-plugin@2.9.2(webpack@5.100.2): + mini-css-extract-plugin@2.9.4(webpack@5.102.1): dependencies: - schema-utils: 4.3.2 - tapable: 2.2.2 - webpack: 5.100.2 + schema-utils: 4.3.3 + tapable: 2.3.0 + webpack: 5.102.1 minimalistic-assert@1.0.1: {} - minimatch@10.0.3: + minimatch@10.1.1: dependencies: '@isaacs/brace-expansion': 5.0.0 @@ -21254,7 +21215,7 @@ snapshots: dependencies: minipass: 7.1.2 minipass-sized: 1.0.3 - minizlib: 3.0.2 + minizlib: 3.1.0 optionalDependencies: encoding: 0.1.13 @@ -21283,7 +21244,7 @@ snapshots: minipass: 3.3.6 yallist: 4.0.0 - minizlib@3.0.2: + minizlib@3.1.0: dependencies: minipass: 7.1.2 @@ -21297,8 +21258,6 @@ snapshots: mkdirp@1.0.4: {} - mkdirp@3.0.1: {} - mnemonist@0.40.3: dependencies: obliterator: 2.0.5 @@ -21307,8 +21266,6 @@ snapshots: module-details-from-path@1.0.4: {} - monaco-editor@0.31.1: {} - moo@0.5.2: {} mri@1.2.0: {} @@ -21367,9 +21324,7 @@ snapshots: nanoid@3.3.11: {} - nanoid@5.1.5: {} - - native-promise-only@0.8.1: {} + nanoid@5.1.6: {} natural-compare@1.4.0: {} @@ -21388,41 +21343,39 @@ snapshots: neo-async@2.6.2: {} - neotraverse@0.6.15: {} - - nest-commander@3.19.1(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6)(@types/inquirer@8.2.11)(@types/node@22.18.5)(typescript@5.9.2): + nest-commander@3.20.1(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8)(@types/inquirer@8.2.11)(@types/node@22.19.0)(typescript@5.9.3): dependencies: '@fig/complete-commander': 3.2.0(commander@11.1.0) - '@golevelup/nestjs-discovery': 4.0.3(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6) - '@nestjs/common': 11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.6)(@nestjs/websockets@11.1.6)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@golevelup/nestjs-discovery': 5.0.0(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8) + '@nestjs/common': 11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.8(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.8)(@nestjs/websockets@11.1.8)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@types/inquirer': 8.2.11 commander: 11.1.0 - cosmiconfig: 8.3.6(typescript@5.9.2) - inquirer: 8.2.7(@types/node@22.18.5) + cosmiconfig: 8.3.6(typescript@5.9.3) + inquirer: 8.2.7(@types/node@22.19.0) transitivePeerDependencies: - '@types/node' - typescript - nestjs-cls@5.4.3(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6)(reflect-metadata@0.2.2)(rxjs@7.8.2): + nestjs-cls@5.4.3(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8)(reflect-metadata@0.2.2)(rxjs@7.8.2): dependencies: - '@nestjs/common': 11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.6)(@nestjs/websockets@11.1.6)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.8(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.8)(@nestjs/websockets@11.1.8)(reflect-metadata@0.2.2)(rxjs@7.8.2) reflect-metadata: 0.2.2 rxjs: 7.8.2 - nestjs-kysely@3.0.0(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6)(kysely@0.28.2)(reflect-metadata@0.2.2): + nestjs-kysely@3.1.2(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8)(kysely@0.28.2)(reflect-metadata@0.2.2): dependencies: - '@nestjs/common': 11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.6)(@nestjs/websockets@11.1.6)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.8(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.8)(@nestjs/websockets@11.1.8)(reflect-metadata@0.2.2)(rxjs@7.8.2) kysely: 0.28.2 reflect-metadata: 0.2.2 tslib: 2.8.1 - nestjs-otel@7.0.1(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6): + nestjs-otel@7.0.1(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.8): dependencies: - '@nestjs/common': 11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.6)(@nestjs/websockets@11.1.6)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.8(@nestjs/common@11.1.8(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.8)(@nestjs/websockets@11.1.8)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@opentelemetry/api': 1.9.0 '@opentelemetry/host-metrics': 0.36.0(@opentelemetry/api@1.9.0) response-time: 2.3.4 @@ -21452,10 +21405,6 @@ snapshots: emojilib: 2.4.0 skin-tone: 2.0.0 - node-fetch-h2@2.3.0: - dependencies: - http2-client: 1.3.5 - node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 @@ -21471,33 +21420,29 @@ snapshots: node-gyp-build-optional-packages@5.2.2: dependencies: - detect-libc: 2.1.0 + detect-libc: 2.1.2 optional: true node-gyp-build@4.8.4: {} - node-gyp@11.4.2: + node-gyp@11.5.0: dependencies: env-paths: 2.2.1 - exponential-backoff: 3.1.2 + exponential-backoff: 3.1.3 graceful-fs: 4.2.11 make-fetch-happen: 14.0.3 nopt: 8.1.0 proc-log: 5.0.0 - semver: 7.7.2 - tar: 7.4.3 + semver: 7.7.3 + tar: 7.5.1 tinyglobby: 0.2.15 which: 5.0.0 transitivePeerDependencies: - supports-color - node-readfiles@0.2.0: - dependencies: - es6-promise: 3.3.1 + node-releases@2.0.26: {} - node-releases@2.0.19: {} - - nodemailer@7.0.6: {} + nodemailer@7.0.10: {} nopt@1.0.10: dependencies: @@ -21515,7 +21460,7 @@ snapshots: normalize-range@0.1.2: {} - normalize-url@8.0.2: {} + normalize-url@8.1.0: {} not@0.1.0: {} @@ -21538,11 +21483,11 @@ snapshots: dependencies: boolbase: 1.0.0 - null-loader@4.0.1(webpack@5.100.2): + null-loader@4.0.1(webpack@5.102.1): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.100.2 + webpack: 5.102.1 nwsapi@2.2.22: optional: true @@ -21555,47 +21500,7 @@ snapshots: pkg-types: 2.3.0 tinyexec: 0.3.2 - oas-kit-common@1.0.8: - dependencies: - fast-safe-stringify: 2.1.1 - - oas-linter@3.2.2: - dependencies: - '@exodus/schemasafe': 1.3.0 - should: 13.2.3 - yaml: 1.10.2 - - oas-resolver-browser@2.5.6: - dependencies: - node-fetch-h2: 2.3.0 - oas-kit-common: 1.0.8 - path-browserify: 1.0.1 - reftools: 1.1.9 - yaml: 1.10.2 - yargs: 17.7.2 - - oas-resolver@2.5.6: - dependencies: - node-fetch-h2: 2.3.0 - oas-kit-common: 1.0.8 - reftools: 1.1.9 - yaml: 1.10.2 - yargs: 17.7.2 - - oas-schema-walker@1.1.5: {} - - oas-validator@5.0.8: - dependencies: - call-me-maybe: 1.0.2 - oas-kit-common: 1.0.8 - oas-linter: 3.2.2 - oas-resolver: 2.5.6 - oas-schema-walker: 1.1.5 - reftools: 1.1.9 - should: 13.2.3 - yaml: 1.10.2 - - oauth4webapi@3.8.1: {} + oauth4webapi@3.8.2: {} object-assign@4.1.1: {} @@ -21618,17 +21523,17 @@ snapshots: obuf@1.1.2: {} - oidc-provider@9.5.1: + oidc-provider@9.5.2: dependencies: '@koa/cors': 5.0.0 '@koa/router': 14.0.0 debug: 4.4.3 - eta: 3.5.0 + eta: 4.0.1 jose: 6.1.0 jsesc: 3.1.0 - koa: 3.0.1 - nanoid: 5.1.5 - quick-lru: 7.2.0 + koa: 3.1.1 + nanoid: 5.1.6 + quick-lru: 7.3.0 raw-body: 3.0.1 transitivePeerDependencies: - supports-color @@ -21651,40 +21556,25 @@ snapshots: dependencies: mimic-function: 5.0.1 + open@10.2.0: + dependencies: + default-browser: 5.2.1 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + wsl-utils: 0.1.0 + open@8.4.2: dependencies: define-lazy-prop: 2.0.0 is-docker: 2.2.1 is-wsl: 2.2.0 - openapi-to-postmanv2@4.25.0(encoding@0.1.13): - dependencies: - ajv: 8.11.0 - ajv-draft-04: 1.0.0(ajv@8.11.0) - ajv-formats: 2.1.1(ajv@8.11.0) - async: 3.2.4 - commander: 2.20.3 - graphlib: 2.1.8 - js-yaml: 4.1.0 - json-pointer: 0.6.2 - json-schema-merge-allof: 0.8.1 - lodash: 4.17.21 - neotraverse: 0.6.15 - oas-resolver-browser: 2.5.6 - object-hash: 3.0.0 - path-browserify: 1.0.1 - postman-collection: 4.5.0 - swagger2openapi: 7.0.8(encoding@0.1.13) - yaml: 1.10.2 - transitivePeerDependencies: - - encoding - opener@1.5.2: {} - openid-client@6.8.0: + openid-client@6.8.1: dependencies: jose: 6.1.0 - oauth4webapi: 3.8.1 + oauth4webapi: 3.8.2 optionator@0.9.4: dependencies: @@ -21758,9 +21648,10 @@ snapshots: eventemitter3: 4.0.7 p-timeout: 3.2.0 - p-retry@4.6.2: + p-retry@6.2.1: dependencies: - '@types/retry': 0.12.0 + '@types/retry': 0.12.2 + is-network-error: 1.3.0 retry: 0.13.1 p-timeout@3.2.0: @@ -21776,7 +21667,7 @@ snapshots: got: 12.6.1 registry-auth-token: 5.1.0 registry-url: 6.0.1 - semver: 7.7.2 + semver: 7.7.3 param-case@3.0.4: dependencies: @@ -21831,8 +21722,6 @@ snapshots: no-case: 3.0.4 tslib: 2.8.1 - path-browserify@1.0.1: {} - path-exists@4.0.0: {} path-exists@5.0.0: {} @@ -21843,13 +21732,6 @@ snapshots: path-key@3.1.1: {} - path-loader@1.0.12: - dependencies: - native-promise-only: 0.8.1 - superagent: 7.1.6 - transitivePeerDependencies: - - supports-color - path-parse@1.0.7: {} path-scurry@1.11.1: @@ -21875,17 +21757,10 @@ snapshots: path-to-regexp@3.3.0: {} - path-to-regexp@8.2.0: {} - path-to-regexp@8.3.0: {} path-type@4.0.0: {} - path@0.12.7: - dependencies: - process: 0.11.10 - util: 0.10.4 - pathe@2.0.3: {} pathval@2.0.1: {} @@ -21958,11 +21833,11 @@ snapshots: exsolve: 1.0.7 pathe: 2.0.3 - playwright-core@1.55.0: {} + playwright-core@1.56.1: {} - playwright@1.55.0: + playwright@1.56.1: dependencies: - playwright-core: 1.55.0 + playwright-core: 1.56.1 optionalDependencies: fsevents: 2.3.2 @@ -21970,7 +21845,7 @@ snapshots: pmtiles@3.2.1: dependencies: - '@types/leaflet': 1.9.20 + '@types/leaflet': 1.9.21 fflate: 0.8.2 pmtiles@4.3.0: @@ -22001,12 +21876,12 @@ snapshots: postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-color-functional-notation@7.0.10(postcss@8.5.6): + postcss-color-functional-notation@7.0.12(postcss@8.5.6): dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6) '@csstools/utilities': 2.0.0(postcss@8.5.6) postcss: 8.5.6 @@ -22024,7 +21899,7 @@ snapshots: postcss-colormin@6.1.0(postcss@8.5.6): dependencies: - browserslist: 4.25.3 + browserslist: 4.27.0 caniuse-api: 3.0.0 colord: 2.9.3 postcss: 8.5.6 @@ -22032,7 +21907,7 @@ snapshots: postcss-convert-values@6.1.0(postcss@8.5.6): dependencies: - browserslist: 4.25.3 + browserslist: 4.27.0 postcss: 8.5.6 postcss-value-parser: 4.2.0 @@ -22087,9 +21962,9 @@ snapshots: postcss: 8.5.6 postcss-selector-parser: 6.1.2 - postcss-double-position-gradients@6.0.2(postcss@8.5.6): + postcss-double-position-gradients@6.0.4(postcss@8.5.6): dependencies: - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6) '@csstools/utilities': 2.0.0(postcss@8.5.6) postcss: 8.5.6 postcss-value-parser: 4.2.0 @@ -22123,19 +21998,19 @@ snapshots: postcss: 8.5.6 postcss-value-parser: 4.2.0 read-cache: 1.0.0 - resolve: 1.22.10 + resolve: 1.22.11 postcss-js@4.1.0(postcss@8.5.6): dependencies: camelcase-css: 2.0.1 postcss: 8.5.6 - postcss-lab-function@7.0.10(postcss@8.5.6): + postcss-lab-function@7.0.12(postcss@8.5.6): dependencies: '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6) '@csstools/utilities': 2.0.0(postcss@8.5.6) postcss: 8.5.6 @@ -22146,20 +22021,21 @@ snapshots: optionalDependencies: postcss: 8.5.6 - postcss-load-config@4.0.2(postcss@8.5.6): + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6)(yaml@2.8.1): dependencies: lilconfig: 3.1.3 - yaml: 2.8.1 optionalDependencies: - postcss: 8.5.6 - - postcss-loader@7.3.4(postcss@8.5.6)(typescript@5.9.2)(webpack@5.100.2): - dependencies: - cosmiconfig: 8.3.6(typescript@5.9.2) jiti: 1.21.7 postcss: 8.5.6 - semver: 7.7.2 - webpack: 5.100.2 + yaml: 2.8.1 + + postcss-loader@7.3.4(postcss@8.5.6)(typescript@5.9.3)(webpack@5.102.1): + dependencies: + cosmiconfig: 8.3.6(typescript@5.9.3) + jiti: 1.21.7 + postcss: 8.5.6 + semver: 7.7.3 + webpack: 5.102.1 transitivePeerDependencies: - typescript @@ -22182,7 +22058,7 @@ snapshots: postcss-merge-rules@6.1.1(postcss@8.5.6): dependencies: - browserslist: 4.25.3 + browserslist: 4.27.0 caniuse-api: 3.0.0 cssnano-utils: 4.0.2(postcss@8.5.6) postcss: 8.5.6 @@ -22202,7 +22078,7 @@ snapshots: postcss-minify-params@6.1.0(postcss@8.5.6): dependencies: - browserslist: 4.25.3 + browserslist: 4.27.0 cssnano-utils: 4.0.2(postcss@8.5.6) postcss: 8.5.6 postcss-value-parser: 4.2.0 @@ -22276,7 +22152,7 @@ snapshots: postcss-normalize-unicode@6.1.0(postcss@8.5.6): dependencies: - browserslist: 4.25.3 + browserslist: 4.27.0 postcss: 8.5.6 postcss-value-parser: 4.2.0 @@ -22314,22 +22190,25 @@ snapshots: postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-preset-env@10.2.4(postcss@8.5.6): + postcss-preset-env@10.4.0(postcss@8.5.6): dependencies: + '@csstools/postcss-alpha-function': 1.0.1(postcss@8.5.6) '@csstools/postcss-cascade-layers': 5.0.2(postcss@8.5.6) - '@csstools/postcss-color-function': 4.0.10(postcss@8.5.6) - '@csstools/postcss-color-mix-function': 3.0.10(postcss@8.5.6) - '@csstools/postcss-color-mix-variadic-function-arguments': 1.0.0(postcss@8.5.6) - '@csstools/postcss-content-alt-text': 2.0.6(postcss@8.5.6) + '@csstools/postcss-color-function': 4.0.12(postcss@8.5.6) + '@csstools/postcss-color-function-display-p3-linear': 1.0.1(postcss@8.5.6) + '@csstools/postcss-color-mix-function': 3.0.12(postcss@8.5.6) + '@csstools/postcss-color-mix-variadic-function-arguments': 1.0.2(postcss@8.5.6) + '@csstools/postcss-content-alt-text': 2.0.8(postcss@8.5.6) + '@csstools/postcss-contrast-color-function': 2.0.12(postcss@8.5.6) '@csstools/postcss-exponential-functions': 2.0.9(postcss@8.5.6) '@csstools/postcss-font-format-keywords': 4.0.0(postcss@8.5.6) - '@csstools/postcss-gamut-mapping': 2.0.10(postcss@8.5.6) - '@csstools/postcss-gradients-interpolation-method': 5.0.10(postcss@8.5.6) - '@csstools/postcss-hwb-function': 4.0.10(postcss@8.5.6) - '@csstools/postcss-ic-unit': 4.0.2(postcss@8.5.6) + '@csstools/postcss-gamut-mapping': 2.0.11(postcss@8.5.6) + '@csstools/postcss-gradients-interpolation-method': 5.0.12(postcss@8.5.6) + '@csstools/postcss-hwb-function': 4.0.12(postcss@8.5.6) + '@csstools/postcss-ic-unit': 4.0.4(postcss@8.5.6) '@csstools/postcss-initial': 2.0.1(postcss@8.5.6) '@csstools/postcss-is-pseudo-class': 5.0.3(postcss@8.5.6) - '@csstools/postcss-light-dark-function': 2.0.9(postcss@8.5.6) + '@csstools/postcss-light-dark-function': 2.0.11(postcss@8.5.6) '@csstools/postcss-logical-float-and-clear': 3.0.0(postcss@8.5.6) '@csstools/postcss-logical-overflow': 2.0.0(postcss@8.5.6) '@csstools/postcss-logical-overscroll-behavior': 2.0.0(postcss@8.5.6) @@ -22339,39 +22218,39 @@ snapshots: '@csstools/postcss-media-queries-aspect-ratio-number-values': 3.0.5(postcss@8.5.6) '@csstools/postcss-nested-calc': 4.0.0(postcss@8.5.6) '@csstools/postcss-normalize-display-values': 4.0.0(postcss@8.5.6) - '@csstools/postcss-oklab-function': 4.0.10(postcss@8.5.6) - '@csstools/postcss-progressive-custom-properties': 4.1.0(postcss@8.5.6) + '@csstools/postcss-oklab-function': 4.0.12(postcss@8.5.6) + '@csstools/postcss-progressive-custom-properties': 4.2.1(postcss@8.5.6) '@csstools/postcss-random-function': 2.0.1(postcss@8.5.6) - '@csstools/postcss-relative-color-syntax': 3.0.10(postcss@8.5.6) + '@csstools/postcss-relative-color-syntax': 3.0.12(postcss@8.5.6) '@csstools/postcss-scope-pseudo-class': 4.0.1(postcss@8.5.6) '@csstools/postcss-sign-functions': 1.1.4(postcss@8.5.6) '@csstools/postcss-stepped-value-functions': 4.0.9(postcss@8.5.6) - '@csstools/postcss-text-decoration-shorthand': 4.0.2(postcss@8.5.6) + '@csstools/postcss-text-decoration-shorthand': 4.0.3(postcss@8.5.6) '@csstools/postcss-trigonometric-functions': 4.0.9(postcss@8.5.6) '@csstools/postcss-unset-value': 4.0.0(postcss@8.5.6) autoprefixer: 10.4.21(postcss@8.5.6) - browserslist: 4.25.3 + browserslist: 4.27.0 css-blank-pseudo: 7.0.1(postcss@8.5.6) - css-has-pseudo: 7.0.2(postcss@8.5.6) + css-has-pseudo: 7.0.3(postcss@8.5.6) css-prefers-color-scheme: 10.0.0(postcss@8.5.6) - cssdb: 8.3.1 + cssdb: 8.4.2 postcss: 8.5.6 postcss-attribute-case-insensitive: 7.0.1(postcss@8.5.6) postcss-clamp: 4.1.0(postcss@8.5.6) - postcss-color-functional-notation: 7.0.10(postcss@8.5.6) + postcss-color-functional-notation: 7.0.12(postcss@8.5.6) postcss-color-hex-alpha: 10.0.0(postcss@8.5.6) postcss-color-rebeccapurple: 10.0.0(postcss@8.5.6) postcss-custom-media: 11.0.6(postcss@8.5.6) postcss-custom-properties: 14.0.6(postcss@8.5.6) postcss-custom-selectors: 8.0.5(postcss@8.5.6) postcss-dir-pseudo-class: 9.0.1(postcss@8.5.6) - postcss-double-position-gradients: 6.0.2(postcss@8.5.6) + postcss-double-position-gradients: 6.0.4(postcss@8.5.6) postcss-focus-visible: 10.0.1(postcss@8.5.6) postcss-focus-within: 9.0.1(postcss@8.5.6) postcss-font-variant: 5.0.0(postcss@8.5.6) postcss-gap-properties: 6.0.0(postcss@8.5.6) postcss-image-set-function: 7.0.0(postcss@8.5.6) - postcss-lab-function: 7.0.10(postcss@8.5.6) + postcss-lab-function: 7.0.12(postcss@8.5.6) postcss-logical: 8.1.0(postcss@8.5.6) postcss-nesting: 13.0.2(postcss@8.5.6) postcss-opacity-percentage: 3.0.0(postcss@8.5.6) @@ -22394,7 +22273,7 @@ snapshots: postcss-reduce-initial@6.1.0(postcss@8.5.6): dependencies: - browserslist: 4.25.3 + browserslist: 4.27.0 caniuse-api: 3.0.0 postcss: 8.5.6 @@ -22470,33 +22349,6 @@ snapshots: postgres@3.4.7: {} - postman-code-generators@1.14.2: - dependencies: - async: 3.2.2 - detect-package-manager: 3.0.2 - lodash: 4.17.21 - path: 0.12.7 - postman-collection: 4.5.0 - shelljs: 0.8.5 - - postman-collection@4.5.0: - dependencies: - '@faker-js/faker': 5.5.3 - file-type: 3.9.0 - http-reasons: 0.1.0 - iconv-lite: 0.6.3 - liquid-json: 0.3.1 - lodash: 4.17.21 - mime-format: 2.0.1 - mime-types: 2.1.35 - postman-url-encoder: 3.0.5 - semver: 7.6.3 - uuid: 8.3.2 - - postman-url-encoder@3.0.5: - dependencies: - punycode: 2.3.1 - potpack@1.0.2: {} potpack@2.1.0: {} @@ -22507,19 +22359,19 @@ snapshots: dependencies: fast-diff: 1.3.0 - prettier-plugin-organize-imports@4.2.0(prettier@3.6.2)(typescript@5.9.2): + prettier-plugin-organize-imports@4.3.0(prettier@3.6.2)(typescript@5.9.3): dependencies: prettier: 3.6.2 - typescript: 5.9.2 + typescript: 5.9.3 prettier-plugin-sort-json@4.1.1(prettier@3.6.2): dependencies: prettier: 3.6.2 - prettier-plugin-svelte@3.4.0(prettier@3.6.2)(svelte@5.38.10): + prettier-plugin-svelte@3.4.0(prettier@3.6.2)(svelte@5.43.0): dependencies: prettier: 3.6.2 - svelte: 5.38.10 + svelte: 5.43.0 prettier@3.6.2: {} @@ -22598,7 +22450,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 22.18.5 + '@types/node': 22.19.0 long: 5.3.2 protocol-buffers-schema@3.6.0: {} @@ -22622,7 +22474,7 @@ snapshots: punycode@2.3.1: {} - pupa@3.1.0: + pupa@3.3.0: dependencies: escape-goat: 4.0.0 @@ -22647,7 +22499,7 @@ snapshots: quick-lru@5.1.1: {} - quick-lru@7.2.0: {} + quick-lru@7.3.0: {} quickselect@2.0.0: {} @@ -22682,11 +22534,11 @@ snapshots: iconv-lite: 0.7.0 unpipe: 1.0.0 - raw-loader@4.0.2(webpack@5.100.2): + raw-loader@4.0.2(webpack@5.102.1): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.100.2 + webpack: 5.102.1 rc@1.2.8: dependencies: @@ -22701,19 +22553,19 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 - react-dom@19.1.1(react@19.1.1): + react-dom@19.2.0(react@19.2.0): dependencies: - react: 19.1.1 - scheduler: 0.26.0 + react: 19.2.0 + scheduler: 0.27.0 - react-email@4.2.11: + react-email@4.3.2: dependencies: - '@babel/parser': 7.28.4 - '@babel/traverse': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/traverse': 7.28.5 chokidar: 4.0.3 commander: 13.1.0 debounce: 2.2.0 - esbuild: 0.25.9 + esbuild: 0.25.12 glob: 11.0.3 jiti: 2.4.2 log-symbols: 7.0.1 @@ -22735,34 +22587,20 @@ snapshots: react-is@17.0.2: {} - react-json-view-lite@2.4.1(react@18.3.1): + react-json-view-lite@2.5.0(react@18.3.1): dependencies: react: 18.3.1 - react-loadable-ssr-addon-v5-slorber@1.0.1(@docusaurus/react-loadable@6.0.0(react@18.3.1))(webpack@5.100.2): + react-loadable-ssr-addon-v5-slorber@1.0.1(@docusaurus/react-loadable@6.0.0(react@18.3.1))(webpack@5.102.1): dependencies: '@babel/runtime': 7.28.4 react-loadable: '@docusaurus/react-loadable@6.0.0(react@18.3.1)' - webpack: 5.100.2 - - react-magic-dropzone@1.0.1: {} + webpack: 5.102.1 react-promise-suspense@0.3.4: dependencies: fast-deep-equal: 2.0.1 - react-redux@7.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.28.4 - '@types/react-redux': 7.1.34 - hoist-non-react-statics: 3.3.2 - loose-envify: 1.4.0 - prop-types: 15.8.1 - react: 18.3.1 - react-is: 17.0.2 - optionalDependencies: - react-dom: 18.3.1(react@18.3.1) - react-router-config@5.1.1(react-router@5.3.4(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.28.4 @@ -22797,7 +22635,7 @@ snapshots: dependencies: loose-envify: 1.4.0 - react@19.1.1: {} + react@19.2.0: {} read-cache@1.0.0: dependencies: @@ -22837,25 +22675,20 @@ snapshots: readdirp@4.1.2: {} - rechoir@0.6.2: - dependencies: - resolve: 1.22.10 - recma-build-jsx@1.0.0: dependencies: '@types/estree': 1.0.8 estree-util-build-jsx: 3.0.1 vfile: 6.0.3 - recma-jsx@1.0.0(acorn@8.15.0): + recma-jsx@1.0.1(acorn@8.15.0): dependencies: + acorn: 8.15.0 acorn-jsx: 5.3.2(acorn@8.15.0) estree-util-to-js: 2.0.0 recma-parse: 1.0.0 recma-stringify: 1.0.0 unified: 11.0.5 - transitivePeerDependencies: - - acorn recma-parse@1.0.0: dependencies: @@ -22882,30 +22715,9 @@ snapshots: dependencies: redis-errors: 1.2.0 - redux-devtools-extension@2.13.9(redux@4.2.1): - dependencies: - redux: 4.2.1 - - redux-thunk@2.4.2(redux@4.2.1): - dependencies: - redux: 4.2.1 - - redux@4.2.1: - dependencies: - '@babel/runtime': 7.28.4 - reflect-metadata@0.2.2: {} - refractor@4.9.0: - dependencies: - '@types/hast': 2.3.10 - '@types/prismjs': 1.26.5 - hastscript: 7.2.0 - parse-entities: 4.0.2 - - reftools@1.1.9: {} - - regenerate-unicode-properties@10.2.0: + regenerate-unicode-properties@10.2.2: dependencies: regenerate: 1.4.2 @@ -22913,14 +22725,14 @@ snapshots: regexp-tree@0.1.27: {} - regexpu-core@6.2.0: + regexpu-core@6.4.0: dependencies: regenerate: 1.4.2 - regenerate-unicode-properties: 10.2.0 + regenerate-unicode-properties: 10.2.2 regjsgen: 0.8.0 - regjsparser: 0.12.0 + regjsparser: 0.13.0 unicode-match-property-ecmascript: 2.0.0 - unicode-match-property-value-ecmascript: 2.2.0 + unicode-match-property-value-ecmascript: 2.2.1 registry-auth-token@5.1.0: dependencies: @@ -22936,6 +22748,10 @@ snapshots: dependencies: jsesc: 3.0.2 + regjsparser@0.13.0: + dependencies: + jsesc: 3.1.0 + rehype-parse@7.0.1: dependencies: hast-util-from-parse5: 6.0.1 @@ -22994,7 +22810,7 @@ snapshots: transitivePeerDependencies: - supports-color - remark-mdx@3.1.0: + remark-mdx@3.1.1: dependencies: mdast-util-mdx: 3.0.0 micromark-extension-mdxjs: 3.0.0 @@ -23038,11 +22854,10 @@ snapshots: require-from-string@2.0.2: {} - require-in-the-middle@7.5.2: + require-in-the-middle@8.0.0: dependencies: debug: 4.4.3 module-details-from-path: 1.0.4 - resolve: 1.22.10 transitivePeerDependencies: - supports-color @@ -23052,8 +22867,6 @@ snapshots: requires-port@1.0.0: {} - reselect@4.1.8: {} - resolve-alpn@1.2.1: {} resolve-from@4.0.0: {} @@ -23064,7 +22877,7 @@ snapshots: dependencies: protocol-buffers-schema: 3.6.0 - resolve@1.22.10: + resolve@1.22.11: dependencies: is-core-module: 2.16.1 path-parse: 1.0.7 @@ -23103,40 +22916,41 @@ snapshots: robust-predicates@3.0.2: {} - rollup-plugin-visualizer@6.0.3(rollup@4.50.1): + rollup-plugin-visualizer@6.0.5(rollup@4.52.5): dependencies: open: 8.4.2 picomatch: 4.0.3 - source-map: 0.7.4 + source-map: 0.7.6 yargs: 17.7.2 optionalDependencies: - rollup: 4.50.1 + rollup: 4.52.5 - rollup@4.50.1: + rollup@4.52.5: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.50.1 - '@rollup/rollup-android-arm64': 4.50.1 - '@rollup/rollup-darwin-arm64': 4.50.1 - '@rollup/rollup-darwin-x64': 4.50.1 - '@rollup/rollup-freebsd-arm64': 4.50.1 - '@rollup/rollup-freebsd-x64': 4.50.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.50.1 - '@rollup/rollup-linux-arm-musleabihf': 4.50.1 - '@rollup/rollup-linux-arm64-gnu': 4.50.1 - '@rollup/rollup-linux-arm64-musl': 4.50.1 - '@rollup/rollup-linux-loongarch64-gnu': 4.50.1 - '@rollup/rollup-linux-ppc64-gnu': 4.50.1 - '@rollup/rollup-linux-riscv64-gnu': 4.50.1 - '@rollup/rollup-linux-riscv64-musl': 4.50.1 - '@rollup/rollup-linux-s390x-gnu': 4.50.1 - '@rollup/rollup-linux-x64-gnu': 4.50.1 - '@rollup/rollup-linux-x64-musl': 4.50.1 - '@rollup/rollup-openharmony-arm64': 4.50.1 - '@rollup/rollup-win32-arm64-msvc': 4.50.1 - '@rollup/rollup-win32-ia32-msvc': 4.50.1 - '@rollup/rollup-win32-x64-msvc': 4.50.1 + '@rollup/rollup-android-arm-eabi': 4.52.5 + '@rollup/rollup-android-arm64': 4.52.5 + '@rollup/rollup-darwin-arm64': 4.52.5 + '@rollup/rollup-darwin-x64': 4.52.5 + '@rollup/rollup-freebsd-arm64': 4.52.5 + '@rollup/rollup-freebsd-x64': 4.52.5 + '@rollup/rollup-linux-arm-gnueabihf': 4.52.5 + '@rollup/rollup-linux-arm-musleabihf': 4.52.5 + '@rollup/rollup-linux-arm64-gnu': 4.52.5 + '@rollup/rollup-linux-arm64-musl': 4.52.5 + '@rollup/rollup-linux-loong64-gnu': 4.52.5 + '@rollup/rollup-linux-ppc64-gnu': 4.52.5 + '@rollup/rollup-linux-riscv64-gnu': 4.52.5 + '@rollup/rollup-linux-riscv64-musl': 4.52.5 + '@rollup/rollup-linux-s390x-gnu': 4.52.5 + '@rollup/rollup-linux-x64-gnu': 4.52.5 + '@rollup/rollup-linux-x64-musl': 4.52.5 + '@rollup/rollup-openharmony-arm64': 4.52.5 + '@rollup/rollup-win32-arm64-msvc': 4.52.5 + '@rollup/rollup-win32-ia32-msvc': 4.52.5 + '@rollup/rollup-win32-x64-gnu': 4.52.5 + '@rollup/rollup-win32-x64-msvc': 4.52.5 fsevents: 2.3.3 router@2.2.0: @@ -23159,16 +22973,18 @@ snapshots: postcss: 8.5.6 strip-json-comments: 3.1.1 + run-applescript@7.1.0: {} + run-async@2.4.1: {} run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 - runed@0.29.2(svelte@5.38.10): + runed@0.29.2(svelte@5.43.0): dependencies: esm-env: 1.2.2 - svelte: 5.38.10 + svelte: 5.43.0 rw@1.3.3: {} @@ -23214,7 +23030,7 @@ snapshots: dependencies: loose-envify: 1.4.0 - scheduler@0.26.0: {} + scheduler@0.27.0: {} schema-dts@1.1.5: {} @@ -23224,7 +23040,7 @@ snapshots: ajv: 6.12.6 ajv-keywords: 3.5.2(ajv@6.12.6) - schema-utils@4.3.2: + schema-utils@4.3.3: dependencies: '@types/json-schema': 7.0.15 ajv: 8.17.1 @@ -23246,18 +23062,16 @@ snapshots: selfsigned@2.4.1: dependencies: - '@types/node-forge': 1.3.11 + '@types/node-forge': 1.3.14 node-forge: 1.3.1 semver-diff@4.0.0: dependencies: - semver: 7.7.2 + semver: 7.7.3 semver@6.3.1: {} - semver@7.6.3: {} - - semver@7.7.2: {} + semver@7.7.3: {} send@0.19.0: dependencies: @@ -23339,7 +23153,7 @@ snapshots: set-blocking@2.0.0: {} - set-cookie-parser@2.7.1: {} + set-cookie-parser@2.7.2: {} set-function-length@1.2.2: dependencies: @@ -23369,36 +23183,36 @@ snapshots: stream-source: 0.3.5 text-encoding: 0.6.4 - sharp@0.34.3: + sharp@0.34.4: dependencies: - color: 4.2.3 - detect-libc: 2.1.0 + '@img/colour': 1.0.0 + detect-libc: 2.1.2 node-addon-api: 8.5.0 - node-gyp: 11.4.2 - semver: 7.7.2 + node-gyp: 11.5.0 + semver: 7.7.3 optionalDependencies: - '@img/sharp-darwin-arm64': 0.34.3 - '@img/sharp-darwin-x64': 0.34.3 - '@img/sharp-libvips-darwin-arm64': 1.2.0 - '@img/sharp-libvips-darwin-x64': 1.2.0 - '@img/sharp-libvips-linux-arm': 1.2.0 - '@img/sharp-libvips-linux-arm64': 1.2.0 - '@img/sharp-libvips-linux-ppc64': 1.2.0 - '@img/sharp-libvips-linux-s390x': 1.2.0 - '@img/sharp-libvips-linux-x64': 1.2.0 - '@img/sharp-libvips-linuxmusl-arm64': 1.2.0 - '@img/sharp-libvips-linuxmusl-x64': 1.2.0 - '@img/sharp-linux-arm': 0.34.3 - '@img/sharp-linux-arm64': 0.34.3 - '@img/sharp-linux-ppc64': 0.34.3 - '@img/sharp-linux-s390x': 0.34.3 - '@img/sharp-linux-x64': 0.34.3 - '@img/sharp-linuxmusl-arm64': 0.34.3 - '@img/sharp-linuxmusl-x64': 0.34.3 - '@img/sharp-wasm32': 0.34.3 - '@img/sharp-win32-arm64': 0.34.3 - '@img/sharp-win32-ia32': 0.34.3 - '@img/sharp-win32-x64': 0.34.3 + '@img/sharp-darwin-arm64': 0.34.4 + '@img/sharp-darwin-x64': 0.34.4 + '@img/sharp-libvips-darwin-arm64': 1.2.3 + '@img/sharp-libvips-darwin-x64': 1.2.3 + '@img/sharp-libvips-linux-arm': 1.2.3 + '@img/sharp-libvips-linux-arm64': 1.2.3 + '@img/sharp-libvips-linux-ppc64': 1.2.3 + '@img/sharp-libvips-linux-s390x': 1.2.3 + '@img/sharp-libvips-linux-x64': 1.2.3 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.3 + '@img/sharp-libvips-linuxmusl-x64': 1.2.3 + '@img/sharp-linux-arm': 0.34.4 + '@img/sharp-linux-arm64': 0.34.4 + '@img/sharp-linux-ppc64': 0.34.4 + '@img/sharp-linux-s390x': 0.34.4 + '@img/sharp-linux-x64': 0.34.4 + '@img/sharp-linuxmusl-arm64': 0.34.4 + '@img/sharp-linuxmusl-x64': 0.34.4 + '@img/sharp-wasm32': 0.34.4 + '@img/sharp-win32-arm64': 0.34.4 + '@img/sharp-win32-ia32': 0.34.4 + '@img/sharp-win32-x64': 0.34.4 transitivePeerDependencies: - supports-color @@ -23410,38 +23224,6 @@ snapshots: shell-quote@1.8.3: {} - shelljs@0.8.5: - dependencies: - glob: 7.2.3 - interpret: 1.4.0 - rechoir: 0.6.2 - - should-equal@2.0.0: - dependencies: - should-type: 1.4.0 - - should-format@3.0.3: - dependencies: - should-type: 1.4.0 - should-type-adaptors: 1.1.0 - - should-type-adaptors@1.1.0: - dependencies: - should-type: 1.4.0 - should-util: 1.0.1 - - should-type@1.4.0: {} - - should-util@1.0.1: {} - - should@13.2.3: - dependencies: - should-equal: 2.0.0 - should-format: 3.0.3 - should-type: 1.4.0 - should-type-adaptors: 1.1.0 - should-util: 1.0.1 - side-channel-list@1.0.0: dependencies: es-errors: 1.3.0 @@ -23486,11 +23268,7 @@ snapshots: simple-concat: 1.0.1 optional: true - simple-icons@15.15.0: {} - - simple-swizzle@0.2.2: - dependencies: - is-arrayish: 0.3.2 + simple-icons@15.18.0: {} sirv@2.0.4: dependencies: @@ -23603,6 +23381,8 @@ snapshots: source-map@0.7.4: {} + source-map@0.7.6: {} + space-separated-tokens@1.1.5: {} space-separated-tokens@2.0.2: {} @@ -23634,7 +23414,7 @@ snapshots: sprintf-js@1.0.3: {} - sql-formatter@15.6.9: + sql-formatter@15.6.10: dependencies: argparse: 2.0.1 nearley: 2.20.1 @@ -23644,9 +23424,9 @@ snapshots: ssh-remote-port-forward@1.0.4: dependencies: '@types/ssh2': 0.5.52 - ssh2: 1.16.0 + ssh2: 1.17.0 - ssh2@1.16.0: + ssh2@1.17.0: dependencies: asn1: 0.2.6 bcrypt-pbkdf: 1.0.2 @@ -23662,15 +23442,13 @@ snapshots: standard-as-callback@2.1.0: {} - state-local@1.0.7: {} - statuses@1.5.0: {} statuses@2.0.1: {} statuses@2.0.2: {} - std-env@3.9.0: {} + std-env@3.10.0: {} stdin-discarder@0.2.2: {} @@ -23678,12 +23456,13 @@ snapshots: streamsearch@1.1.0: {} - streamx@2.22.1: + streamx@2.23.0: dependencies: + events-universal: 1.0.1 fast-fifo: 1.3.2 text-decoder: 1.2.3 - optionalDependencies: - bare-events: 2.6.1 + transitivePeerDependencies: + - bare-abort-controller string-width@4.2.3: dependencies: @@ -23699,7 +23478,7 @@ snapshots: string-width@7.2.0: dependencies: - emoji-regex: 10.5.0 + emoji-regex: 10.6.0 get-east-asian-width: 1.4.0 strip-ansi: 7.1.2 @@ -23752,25 +23531,23 @@ snapshots: dependencies: js-tokens: 9.0.1 - striptags@3.2.0: {} - strnum@2.1.1: {} strtok3@10.3.4: dependencies: '@tokenizer/token': 0.3.0 - style-to-js@1.1.17: + style-to-js@1.1.18: dependencies: - style-to-object: 1.0.9 + style-to-object: 1.0.11 - style-to-object@1.0.9: + style-to-object@1.0.11: dependencies: inline-style-parser: 0.2.4 stylehacks@6.1.1(postcss@8.5.6): dependencies: - browserslist: 4.25.3 + browserslist: 4.27.0 postcss: 8.5.6 postcss-selector-parser: 6.1.2 @@ -23798,22 +23575,6 @@ snapshots: transitivePeerDependencies: - supports-color - superagent@7.1.6: - dependencies: - component-emitter: 1.3.1 - cookiejar: 2.1.4 - debug: 4.4.3 - fast-safe-stringify: 2.1.1 - form-data: 4.0.4 - formidable: 2.1.5 - methods: 1.1.2 - mime: 2.6.0 - qs: 6.14.0 - readable-stream: 3.6.2 - semver: 7.7.2 - transitivePeerDependencies: - - supports-color - supercluster@7.1.5: dependencies: kdbush: 3.0.0 @@ -23839,19 +23600,19 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@4.3.1(picomatch@4.0.3)(svelte@5.38.10)(typescript@5.9.2): + svelte-check@4.3.3(picomatch@4.0.3)(svelte@5.43.0)(typescript@5.9.3): dependencies: - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 chokidar: 4.0.3 fdir: 6.5.0(picomatch@4.0.3) picocolors: 1.1.1 sade: 1.8.1 - svelte: 5.38.10 - typescript: 5.9.2 + svelte: 5.43.0 + typescript: 5.9.3 transitivePeerDependencies: - picomatch - svelte-eslint-parser@1.3.2(svelte@5.38.10): + svelte-eslint-parser@1.4.0(svelte@5.43.0): dependencies: eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -23860,50 +23621,54 @@ snapshots: postcss-scss: 4.0.9(postcss@8.5.6) postcss-selector-parser: 7.1.0 optionalDependencies: - svelte: 5.38.10 + svelte: 5.43.0 svelte-gestures@5.2.2: {} - svelte-i18n@4.0.1(svelte@5.38.10): + svelte-highlight@7.8.4: + dependencies: + highlight.js: 11.11.1 + + svelte-i18n@4.0.1(svelte@5.43.0): dependencies: cli-color: 2.0.4 deepmerge: 4.3.1 esbuild: 0.19.12 estree-walker: 2.0.2 - intl-messageformat: 10.7.16 + intl-messageformat: 10.7.18 sade: 1.8.1 - svelte: 5.38.10 + svelte: 5.43.0 tiny-glob: 0.2.9 - svelte-maplibre@1.2.1(svelte@5.38.10): + svelte-maplibre@1.2.5(svelte@5.43.0): dependencies: d3-geo: 3.1.1 dequal: 2.0.3 just-compare: 2.3.0 - maplibre-gl: 5.7.1 + maplibre-gl: 5.10.0 pmtiles: 3.2.1 - svelte: 5.38.10 + svelte: 5.43.0 - svelte-parse-markup@0.1.5(svelte@5.38.10): + svelte-parse-markup@0.1.5(svelte@5.43.0): dependencies: - svelte: 5.38.10 + svelte: 5.43.0 - svelte-persisted-store@0.12.0(svelte@5.38.10): + svelte-persisted-store@0.12.0(svelte@5.43.0): dependencies: - svelte: 5.38.10 + svelte: 5.43.0 - svelte-toolbelt@0.9.3(svelte@5.38.10): + svelte-toolbelt@0.9.3(svelte@5.43.0): dependencies: clsx: 2.1.1 - runed: 0.29.2(svelte@5.38.10) - style-to-object: 1.0.9 - svelte: 5.38.10 + runed: 0.29.2(svelte@5.43.0) + style-to-object: 1.0.11 + svelte: 5.43.0 - svelte@5.38.10: + svelte@5.43.0: dependencies: '@jridgewell/remapping': 2.3.5 '@jridgewell/sourcemap-codec': 1.5.5 - '@sveltejs/acorn-typescript': 1.0.5(acorn@8.15.0) + '@sveltejs/acorn-typescript': 1.0.6(acorn@8.15.0) '@types/estree': 1.0.8 acorn: 8.15.0 aria-query: 5.3.2 @@ -23913,8 +23678,8 @@ snapshots: esrap: 2.1.0 is-reference: 3.0.3 locate-character: 3.0.0 - magic-string: 0.30.19 - zimmerframe: 1.1.2 + magic-string: 0.30.21 + zimmerframe: 1.1.4 svg-parser@2.0.4: {} @@ -23928,25 +23693,15 @@ snapshots: csso: 5.0.5 picocolors: 1.1.1 - swagger-ui-dist@5.21.0: + swagger-ui-dist@5.29.4: dependencies: '@scarf/scarf': 1.4.0 - swagger2openapi@7.0.8(encoding@0.1.13): + swr@2.3.6(react@18.3.1): dependencies: - call-me-maybe: 1.0.2 - node-fetch: 2.7.0(encoding@0.1.13) - node-fetch-h2: 2.3.0 - node-readfiles: 0.2.0 - oas-kit-common: 1.0.8 - oas-resolver: 2.5.6 - oas-schema-walker: 1.1.5 - oas-validator: 5.0.8 - reftools: 1.1.9 - yaml: 1.10.2 - yargs: 17.7.2 - transitivePeerDependencies: - - encoding + dequal: 2.0.3 + react: 18.3.1 + use-sync-external-store: 1.6.0(react@18.3.1) symbol-observable@4.0.0: {} @@ -23959,31 +23714,31 @@ snapshots: systeminformation@5.23.8: {} - tabbable@6.2.0: {} + tabbable@6.3.0: {} tailwind-merge@3.3.1: {} - tailwind-variants@3.1.1(tailwind-merge@3.3.1)(tailwindcss@4.1.13): + tailwind-variants@3.1.1(tailwind-merge@3.3.1)(tailwindcss@4.1.16): dependencies: - tailwindcss: 4.1.13 + tailwindcss: 4.1.16 optionalDependencies: tailwind-merge: 3.3.1 - tailwindcss-email-variants@3.0.4(tailwindcss@3.4.17): + tailwindcss-email-variants@3.0.5(tailwindcss@3.4.18(yaml@2.8.1)): dependencies: - tailwindcss: 3.4.17 + tailwindcss: 3.4.18(yaml@2.8.1) - tailwindcss-mso@2.0.2(tailwindcss@3.4.17): + tailwindcss-mso@2.0.3(tailwindcss@3.4.18(yaml@2.8.1)): dependencies: - tailwindcss: 3.4.17 + tailwindcss: 3.4.18(yaml@2.8.1) - tailwindcss-preset-email@1.4.0(tailwindcss@3.4.17): + tailwindcss-preset-email@1.4.1(tailwindcss@3.4.18(yaml@2.8.1)): dependencies: - tailwindcss: 3.4.17 - tailwindcss-email-variants: 3.0.4(tailwindcss@3.4.17) - tailwindcss-mso: 2.0.2(tailwindcss@3.4.17) + tailwindcss: 3.4.18(yaml@2.8.1) + tailwindcss-email-variants: 3.0.5(tailwindcss@3.4.18(yaml@2.8.1)) + tailwindcss-mso: 2.0.3(tailwindcss@3.4.18(yaml@2.8.1)) - tailwindcss@3.4.17: + tailwindcss@3.4.18(yaml@2.8.1): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -24002,33 +23757,35 @@ snapshots: postcss: 8.5.6 postcss-import: 15.1.0(postcss@8.5.6) postcss-js: 4.1.0(postcss@8.5.6) - postcss-load-config: 4.0.2(postcss@8.5.6) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)(yaml@2.8.1) postcss-nested: 6.2.0(postcss@8.5.6) postcss-selector-parser: 6.1.2 - resolve: 1.22.10 + resolve: 1.22.11 sucrase: 3.35.0 transitivePeerDependencies: - - ts-node + - tsx + - yaml - tailwindcss@4.1.13: {} + tailwindcss@4.1.16: {} - tapable@2.2.2: {} + tapable@2.3.0: {} - tar-fs@2.1.3: + tar-fs@2.1.4: dependencies: chownr: 1.1.4 mkdirp-classic: 0.5.3 pump: 3.0.3 tar-stream: 2.2.0 - tar-fs@3.1.0: + tar-fs@3.1.1: dependencies: pump: 3.0.3 tar-stream: 3.1.7 optionalDependencies: - bare-fs: 4.2.0 + bare-fs: 4.5.0 bare-path: 3.0.0 transitivePeerDependencies: + - bare-abort-controller - bare-buffer tar-stream@2.2.0: @@ -24043,7 +23800,9 @@ snapshots: dependencies: b4a: 1.6.7 fast-fifo: 1.3.2 - streamx: 2.22.1 + streamx: 2.23.0 + transitivePeerDependencies: + - bare-abort-controller tar@6.2.1: dependencies: @@ -24054,38 +23813,37 @@ snapshots: mkdirp: 1.0.4 yallist: 4.0.0 - tar@7.4.3: + tar@7.5.1: dependencies: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 minipass: 7.1.2 - minizlib: 3.0.2 - mkdirp: 3.0.1 + minizlib: 3.1.0 yallist: 5.0.0 - terser-webpack-plugin@5.3.14(@swc/core@1.13.5(@swc/helpers@0.5.17))(webpack@5.100.2(@swc/core@1.13.5(@swc/helpers@0.5.17))): + terser-webpack-plugin@5.3.14(@swc/core@1.14.0(@swc/helpers@0.5.17))(webpack@5.100.2(@swc/core@1.14.0(@swc/helpers@0.5.17))): dependencies: - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 - schema-utils: 4.3.2 + schema-utils: 4.3.3 serialize-javascript: 6.0.2 - terser: 5.43.1 - webpack: 5.100.2(@swc/core@1.13.5(@swc/helpers@0.5.17)) + terser: 5.44.0 + webpack: 5.100.2(@swc/core@1.14.0(@swc/helpers@0.5.17)) optionalDependencies: - '@swc/core': 1.13.5(@swc/helpers@0.5.17) + '@swc/core': 1.14.0(@swc/helpers@0.5.17) - terser-webpack-plugin@5.3.14(webpack@5.100.2): + terser-webpack-plugin@5.3.14(webpack@5.102.1): dependencies: - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 - schema-utils: 4.3.2 + schema-utils: 4.3.3 serialize-javascript: 6.0.2 - terser: 5.43.1 - webpack: 5.100.2 + terser: 5.44.0 + webpack: 5.102.1 - terser@5.43.1: + terser@5.44.0: dependencies: - '@jridgewell/source-map': 0.3.6 + '@jridgewell/source-map': 0.3.11 acorn: 8.15.0 commander: 2.20.3 source-map-support: 0.5.21 @@ -24096,24 +23854,25 @@ snapshots: glob: 10.4.5 minimatch: 9.0.5 - testcontainers@11.5.1: + testcontainers@11.7.2: dependencies: '@balena/dockerignore': 1.0.2 - '@types/dockerode': 3.3.42 + '@types/dockerode': 3.3.45 archiver: 7.0.1 async-lock: 1.4.1 byline: 5.0.0 debug: 4.4.3 - docker-compose: 1.2.0 - dockerode: 4.0.7 + docker-compose: 1.3.0 + dockerode: 4.0.9 get-port: 7.1.0 proper-lockfile: 4.1.2 properties-reader: 2.3.0 ssh-remote-port-forward: 1.0.4 - tar-fs: 3.1.0 + tar-fs: 3.1.1 tmp: 0.2.5 undici: 7.16.0 transitivePeerDependencies: + - bare-abort-controller - bare-buffer - supports-color @@ -24131,10 +23890,16 @@ snapshots: dependencies: any-promise: 1.3.0 + thingies@2.5.0(tslib@2.8.1): + dependencies: + tslib: 2.8.1 + three@0.179.1: {} three@0.180.0: {} + throttleit@2.1.0: {} + through@2.3.8: {} thumbhash@0.1.1: {} @@ -24228,6 +23993,10 @@ snapshots: punycode: 2.3.1 optional: true + tree-dump@1.1.0(tslib@2.8.1): + dependencies: + tslib: 2.8.1 + tree-kill@1.2.2: {} trim-lines@3.0.1: {} @@ -24240,21 +24009,21 @@ snapshots: dependencies: utf8-byte-length: 1.0.5 - ts-api-utils@2.1.0(typescript@5.9.2): + ts-api-utils@2.1.0(typescript@5.9.3): dependencies: - typescript: 5.9.2 + typescript: 5.9.3 ts-interface-checker@0.1.13: {} - tsconfck@3.1.6(typescript@5.9.2): + tsconfck@3.1.6(typescript@5.9.3): optionalDependencies: - typescript: 5.9.2 + typescript: 5.9.3 tsconfig-paths-webpack-plugin@4.2.0: dependencies: chalk: 4.1.2 enhanced-resolve: 5.18.3 - tapable: 2.2.2 + tapable: 2.3.0 tsconfig-paths: 4.2.0 tsconfig-paths@4.2.0: @@ -24298,29 +24067,28 @@ snapshots: typedarray@0.0.6: {} - typescript-eslint@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2): + typescript-eslint@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.43.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/parser': 8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/typescript-estree': 8.43.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) - eslint: 9.35.0(jiti@2.5.1) - typescript: 5.9.2 + '@typescript-eslint/eslint-plugin': 8.46.2(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.46.2(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.38.0(jiti@2.6.1) + typescript: 5.9.3 transitivePeerDependencies: - supports-color typescript@5.8.3: {} - typescript@5.9.2: {} + typescript@5.9.3: {} ua-is-frozen@0.1.2: {} - ua-parser-js@2.0.5: + ua-parser-js@2.0.6: dependencies: detect-europe-js: 0.1.2 is-standalone-pwa: 0.1.1 ua-is-frozen: 0.1.2 - undici: 7.16.0 uglify-js@3.19.3: optional: true @@ -24331,13 +24099,13 @@ snapshots: dependencies: '@lukeed/csprng': 1.1.0 - uint8array-extras@1.4.1: {} + uint8array-extras@1.5.0: {} undici-types@5.26.5: {} undici-types@6.21.0: {} - undici-types@7.12.0: + undici-types@7.16.0: optional: true undici@7.16.0: {} @@ -24349,11 +24117,11 @@ snapshots: unicode-match-property-ecmascript@2.0.0: dependencies: unicode-canonical-property-names-ecmascript: 2.0.1 - unicode-property-aliases-ecmascript: 2.1.0 + unicode-property-aliases-ecmascript: 2.2.0 - unicode-match-property-value-ecmascript@2.2.0: {} + unicode-match-property-value-ecmascript@2.2.1: {} - unicode-property-aliases-ecmascript@2.1.0: {} + unicode-property-aliases-ecmascript@2.2.0: {} unified@11.0.5: dependencies: @@ -24393,7 +24161,7 @@ snapshots: unist-util-is@4.1.0: {} - unist-util-is@6.0.0: + unist-util-is@6.0.1: dependencies: '@types/unist': 3.0.3 @@ -24418,10 +24186,10 @@ snapshots: '@types/unist': 2.0.11 unist-util-is: 4.1.0 - unist-util-visit-parents@6.0.1: + unist-util-visit-parents@6.0.2: dependencies: '@types/unist': 3.0.3 - unist-util-is: 6.0.0 + unist-util-is: 6.0.1 unist-util-visit@2.0.3: dependencies: @@ -24432,8 +24200,8 @@ snapshots: unist-util-visit@5.0.0: dependencies: '@types/unist': 3.0.3 - unist-util-is: 6.0.0 - unist-util-visit-parents: 6.0.1 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 universalify@0.2.0: optional: true @@ -24442,10 +24210,10 @@ snapshots: unpipe@1.0.0: {} - unplugin-swc@1.5.7(@swc/core@1.13.5(@swc/helpers@0.5.17))(rollup@4.50.1): + unplugin-swc@1.5.8(@swc/core@1.14.0(@swc/helpers@0.5.17))(rollup@4.52.5): dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.50.1) - '@swc/core': 1.13.5(@swc/helpers@0.5.17) + '@rollup/pluginutils': 5.3.0(rollup@4.52.5) + '@swc/core': 1.14.0(@swc/helpers@0.5.17) load-tsconfig: 0.2.5 unplugin: 2.3.10 transitivePeerDependencies: @@ -24458,9 +24226,9 @@ snapshots: picomatch: 4.0.3 webpack-virtual-modules: 0.6.2 - update-browserslist-db@1.1.3(browserslist@4.25.3): + update-browserslist-db@1.1.4(browserslist@4.27.0): dependencies: - browserslist: 4.25.3 + browserslist: 4.27.0 escalade: 3.2.0 picocolors: 1.1.1 @@ -24473,11 +24241,11 @@ snapshots: import-lazy: 4.0.0 is-ci: 3.0.1 is-installed-globally: 0.4.0 - is-npm: 6.0.0 + is-npm: 6.1.0 is-yarn-global: 0.4.1 latest-version: 7.0.0 - pupa: 3.1.0 - semver: 7.7.2 + pupa: 3.3.0 + semver: 7.7.3 semver-diff: 4.0.0 xdg-basedir: 5.1.0 @@ -24485,14 +24253,14 @@ snapshots: dependencies: punycode: 2.3.1 - url-loader@4.1.1(file-loader@6.2.0(webpack@5.100.2))(webpack@5.100.2): + url-loader@4.1.1(file-loader@6.2.0(webpack@5.102.1))(webpack@5.102.1): dependencies: loader-utils: 2.0.4 mime-types: 2.1.35 schema-utils: 3.3.0 - webpack: 5.100.2 + webpack: 5.102.1 optionalDependencies: - file-loader: 6.2.0(webpack@5.100.2) + file-loader: 6.2.0(webpack@5.102.1) url-parse@1.5.10: dependencies: @@ -24505,14 +24273,16 @@ snapshots: punycode: 1.4.1 qs: 6.14.0 + urlpattern-polyfill@8.0.2: {} + + use-sync-external-store@1.6.0(react@18.3.1): + dependencies: + react: 18.3.1 + utf8-byte-length@1.0.5: {} util-deprecate@1.0.2: {} - util@0.10.4: - dependencies: - inherits: 2.0.3 - utila@0.4.0: {} utility-types@3.11.0: {} @@ -24533,24 +24303,7 @@ snapshots: uuid@8.3.2: {} - uuid@9.0.1: {} - - validate.io-array@1.0.6: {} - - validate.io-function@1.0.2: {} - - validate.io-integer-array@1.0.0: - dependencies: - validate.io-array: 1.0.6 - validate.io-integer: 1.0.5 - - validate.io-integer@1.0.5: - dependencies: - validate.io-number: 1.0.3 - - validate.io-number@1.0.3: {} - - validator@13.15.15: {} + validator@13.15.20: {} value-equal@1.0.1: {} @@ -24568,7 +24321,7 @@ snapshots: '@types/unist': 2.0.11 unist-util-stringify-position: 2.0.3 - vfile-message@4.0.2: + vfile-message@4.0.3: dependencies: '@types/unist': 3.0.3 unist-util-stringify-position: 4.0.0 @@ -24583,24 +24336,24 @@ snapshots: vfile@6.0.3: dependencies: '@types/unist': 3.0.3 - vfile-message: 4.0.2 + vfile-message: 4.0.3 - vite-imagetools@8.0.0(rollup@4.50.1): + vite-imagetools@8.0.0(rollup@4.52.5): dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.50.1) + '@rollup/pluginutils': 5.3.0(rollup@4.52.5) imagetools-core: 8.0.0 - sharp: 0.34.3 + sharp: 0.34.4 transitivePeerDependencies: - rollup - supports-color - vite-node@3.2.4(@types/node@22.18.5)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1): + vite-node@3.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.1.5(@types/node@22.18.5)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) + vite: 7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) transitivePeerDependencies: - '@types/node' - jiti @@ -24615,13 +24368,13 @@ snapshots: - tsx - yaml - vite-node@3.2.4(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1): + vite-node@3.2.4(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) + vite: 7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) transitivePeerDependencies: - '@types/node' - jiti @@ -24636,62 +24389,62 @@ snapshots: - tsx - yaml - vite-tsconfig-paths@5.1.4(typescript@5.9.2)(vite@7.1.5(@types/node@22.18.5)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)): + vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)): dependencies: debug: 4.4.3 globrex: 0.1.2 - tsconfck: 3.1.6(typescript@5.9.2) + tsconfck: 3.1.6(typescript@5.9.3) optionalDependencies: - vite: 7.1.5(@types/node@22.18.5)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) + vite: 7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) transitivePeerDependencies: - supports-color - typescript - vite@7.1.5(@types/node@22.18.5)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1): + vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1): dependencies: - esbuild: 0.25.9 + esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.50.1 + rollup: 4.52.5 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 22.18.5 + '@types/node': 22.19.0 fsevents: 2.3.3 - jiti: 2.5.1 - lightningcss: 1.30.1 - terser: 5.43.1 + jiti: 2.6.1 + lightningcss: 1.30.2 + terser: 5.44.0 yaml: 2.8.1 - vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1): + vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1): dependencies: - esbuild: 0.25.9 + esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.50.1 + rollup: 4.52.5 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 24.5.1 + '@types/node': 24.10.0 fsevents: 2.3.3 - jiti: 2.5.1 - lightningcss: 1.30.1 - terser: 5.43.1 + jiti: 2.6.1 + lightningcss: 1.30.2 + terser: 5.44.0 yaml: 2.8.1 - vitefu@1.1.1(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)): + vitefu@1.1.1(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)): optionalDependencies: - vite: 7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) + vite: 7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) - vitest-fetch-mock@0.4.5(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.18.5)(happy-dom@18.0.1)(jiti@2.5.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)): + vitest-fetch-mock@0.4.5(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.0)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)): dependencies: - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.5)(happy-dom@18.0.1)(jiti@2.5.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.0)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) - vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.18.5)(happy-dom@18.0.1)(jiti@2.5.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.0)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2(encoding@0.1.13)))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.1.5(@types/node@22.18.5)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)) + '@vitest/mocker': 3.2.4(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -24700,22 +24453,22 @@ snapshots: chai: 5.2.0 debug: 4.4.3 expect-type: 1.2.1 - magic-string: 0.30.19 + magic-string: 0.30.21 pathe: 2.0.3 picomatch: 4.0.3 - std-env: 3.9.0 + std-env: 3.10.0 tinybench: 2.9.0 tinyexec: 0.3.2 tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.1.5(@types/node@22.18.5)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) - vite-node: 3.2.4(@types/node@22.18.5)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) + vite: 7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) + vite-node: 3.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 - '@types/node': 22.18.5 - happy-dom: 18.0.1 + '@types/node': 22.19.0 + happy-dom: 20.0.10 jsdom: 26.1.0(canvas@2.11.2(encoding@0.1.13)) transitivePeerDependencies: - jiti @@ -24731,11 +24484,11 @@ snapshots: - tsx - yaml - vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.18.5)(happy-dom@18.0.1)(jiti@2.5.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.0)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.1.5(@types/node@22.18.5)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)) + '@vitest/mocker': 3.2.4(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -24744,22 +24497,22 @@ snapshots: chai: 5.2.0 debug: 4.4.3 expect-type: 1.2.1 - magic-string: 0.30.19 + magic-string: 0.30.21 pathe: 2.0.3 picomatch: 4.0.3 - std-env: 3.9.0 + std-env: 3.10.0 tinybench: 2.9.0 tinyexec: 0.3.2 tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.1.5(@types/node@22.18.5)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) - vite-node: 3.2.4(@types/node@22.18.5)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) + vite: 7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) + vite-node: 3.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 - '@types/node': 22.18.5 - happy-dom: 18.0.1 + '@types/node': 22.19.0 + happy-dom: 20.0.10 jsdom: 26.1.0(canvas@2.11.2) transitivePeerDependencies: - jiti @@ -24775,11 +24528,11 @@ snapshots: - tsx - yaml - vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.5.1)(happy-dom@18.0.1)(jiti@2.5.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.0)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)) + '@vitest/mocker': 3.2.4(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -24788,22 +24541,22 @@ snapshots: chai: 5.2.0 debug: 4.4.3 expect-type: 1.2.1 - magic-string: 0.30.19 + magic-string: 0.30.21 pathe: 2.0.3 picomatch: 4.0.3 - std-env: 3.9.0 + std-env: 3.10.0 tinybench: 2.9.0 tinyexec: 0.3.2 tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.1.5(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) - vite-node: 3.2.4(@types/node@24.5.1)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1) + vite: 7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) + vite-node: 3.2.4(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 - '@types/node': 24.5.1 - happy-dom: 18.0.1 + '@types/node': 24.10.0 + happy-dom: 20.0.10 jsdom: 26.1.0(canvas@2.11.2) transitivePeerDependencies: - jiti @@ -24875,22 +24628,25 @@ snapshots: - bufferutil - utf-8-validate - webpack-dev-middleware@5.3.4(webpack@5.100.2): + webpack-dev-middleware@7.4.5(webpack@5.102.1): dependencies: colorette: 2.0.20 - memfs: 3.5.3 - mime-types: 2.1.35 + memfs: 4.50.0 + mime-types: 3.0.1 + on-finished: 2.4.1 range-parser: 1.2.1 - schema-utils: 4.3.2 - webpack: 5.100.2 + schema-utils: 4.3.3 + optionalDependencies: + webpack: 5.102.1 - webpack-dev-server@4.15.2(webpack@5.100.2): + webpack-dev-server@5.2.2(webpack@5.102.1): dependencies: '@types/bonjour': 3.5.13 '@types/connect-history-api-fallback': 1.5.4 - '@types/express': 4.17.23 + '@types/express': 4.17.25 + '@types/express-serve-static-core': 4.19.7 '@types/serve-index': 1.9.4 - '@types/serve-static': 1.15.8 + '@types/serve-static': 1.15.10 '@types/sockjs': 0.3.36 '@types/ws': 8.18.1 ansi-html-community: 0.0.8 @@ -24899,25 +24655,22 @@ snapshots: colorette: 2.0.20 compression: 1.8.1 connect-history-api-fallback: 2.0.0 - default-gateway: 6.0.3 express: 4.21.2 graceful-fs: 4.2.11 - html-entities: 2.6.0 - http-proxy-middleware: 2.0.9(@types/express@4.17.23) + http-proxy-middleware: 2.0.9(@types/express@4.17.25) ipaddr.js: 2.2.0 - launch-editor: 2.10.0 - open: 8.4.2 - p-retry: 4.6.2 - rimraf: 3.0.2 - schema-utils: 4.3.2 + launch-editor: 2.12.0 + open: 10.2.0 + p-retry: 6.2.1 + schema-utils: 4.3.3 selfsigned: 2.4.1 serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 5.3.4(webpack@5.100.2) + webpack-dev-middleware: 7.4.5(webpack@5.102.1) ws: 8.18.3 optionalDependencies: - webpack: 5.100.2 + webpack: 5.102.1 transitivePeerDependencies: - bufferutil - debug @@ -24942,7 +24695,7 @@ snapshots: webpack-virtual-modules@0.6.2: {} - webpack@5.100.2: + webpack@5.100.2(@swc/core@1.14.0(@swc/helpers@0.5.17)): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -24952,7 +24705,7 @@ snapshots: '@webassemblyjs/wasm-parser': 1.14.1 acorn: 8.15.0 acorn-import-phases: 1.0.4(acorn@8.15.0) - browserslist: 4.25.3 + browserslist: 4.27.0 chrome-trace-event: 1.0.4 enhanced-resolve: 5.18.3 es-module-lexer: 1.7.0 @@ -24961,12 +24714,12 @@ snapshots: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 json-parse-even-better-errors: 2.3.1 - loader-runner: 4.3.0 + loader-runner: 4.3.1 mime-types: 2.1.35 neo-async: 2.6.2 - schema-utils: 4.3.2 - tapable: 2.2.2 - terser-webpack-plugin: 5.3.14(webpack@5.100.2) + schema-utils: 4.3.3 + tapable: 2.3.0 + terser-webpack-plugin: 5.3.14(@swc/core@1.14.0(@swc/helpers@0.5.17))(webpack@5.100.2(@swc/core@1.14.0(@swc/helpers@0.5.17))) watchpack: 2.4.4 webpack-sources: 3.3.3 transitivePeerDependencies: @@ -24974,7 +24727,7 @@ snapshots: - esbuild - uglify-js - webpack@5.100.2(@swc/core@1.13.5(@swc/helpers@0.5.17)): + webpack@5.102.1: dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -24984,7 +24737,7 @@ snapshots: '@webassemblyjs/wasm-parser': 1.14.1 acorn: 8.15.0 acorn-import-phases: 1.0.4(acorn@8.15.0) - browserslist: 4.25.3 + browserslist: 4.27.0 chrome-trace-event: 1.0.4 enhanced-resolve: 5.18.3 es-module-lexer: 1.7.0 @@ -24993,12 +24746,12 @@ snapshots: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 json-parse-even-better-errors: 2.3.1 - loader-runner: 4.3.0 + loader-runner: 4.3.1 mime-types: 2.1.35 neo-async: 2.6.2 - schema-utils: 4.3.2 - tapable: 2.2.2 - terser-webpack-plugin: 5.3.14(@swc/core@1.13.5(@swc/helpers@0.5.17))(webpack@5.100.2(@swc/core@1.13.5(@swc/helpers@0.5.17))) + schema-utils: 4.3.3 + tapable: 2.3.0 + terser-webpack-plugin: 5.3.14(webpack@5.102.1) watchpack: 2.4.4 webpack-sources: 3.3.3 transitivePeerDependencies: @@ -25006,7 +24759,7 @@ snapshots: - esbuild - uglify-js - webpackbar@6.0.1(webpack@5.100.2): + webpackbar@6.0.1(webpack@5.102.1): dependencies: ansi-escapes: 4.3.2 chalk: 4.1.2 @@ -25014,8 +24767,8 @@ snapshots: figures: 3.2.0 markdown-table: 2.0.0 pretty-time: 1.1.0 - std-env: 3.9.0 - webpack: 5.100.2 + std-env: 3.10.0 + webpack: 5.102.1 wrap-ansi: 7.0.0 websocket-driver@0.7.4: @@ -25124,6 +24877,10 @@ snapshots: ws@8.18.3: {} + wsl-utils@0.1.0: + dependencies: + is-wsl: 3.1.0 + xdg-basedir@5.1.0: {} xml-js@1.6.11: @@ -25196,7 +24953,7 @@ snapshots: yoctocolors@2.1.2: {} - zimmerframe@1.1.2: {} + zimmerframe@1.1.4: {} zip-stream@6.0.1: dependencies: @@ -25204,6 +24961,8 @@ snapshots: compress-commons: 6.0.2 readable-stream: 4.7.0 + zod@4.1.12: {} + zwitch@1.0.5: {} zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 880d115880..d5629d2323 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -4,13 +4,13 @@ packages: - e2e - open-api/typescript-sdk - server + - plugins - web - .github ignoredBuiltDependencies: - '@nestjs/core' - '@scarf/scarf' - '@swc/core' - - bcrypt - canvas - core-js - core-js-pure @@ -25,9 +25,10 @@ ignoredBuiltDependencies: onlyBuiltDependencies: - sharp - '@tailwindcss/oxide' + - bcrypt overrides: canvas: 2.11.2 - sharp: ^0.34.3 + sharp: ^0.34.4 packageExtensions: nestjs-kysely: dependencies: @@ -51,6 +52,10 @@ packageExtensions: tailwind-variants: dependencies: tailwindcss: '>=4.1' + bcrypt: + dependencies: + node-addon-api: '*' + node-gyp: '*' dedupePeerDependents: false preferWorkspacePackages: true injectWorkspacePackages: true diff --git a/readme_i18n/README_ar_JO.md b/readme_i18n/README_ar_JO.md index 4e7ba99dd2..e0e13eeaf6 100644 --- a/readme_i18n/README_ar_JO.md +++ b/readme_i18n/README_ar_JO.md @@ -28,7 +28,8 @@ Deutsch Nederlands Türkçe - 中文 + 简体中文 + 正體中文 Українська Русский Português Brasileiro @@ -46,13 +47,13 @@ ## محتوى -- [الوثائق الرسمية](https://immich.app/docs) +- [الوثائق الرسمية](https://docs.immich.app) - [خريطة الطريق](https://github.com/orgs/immich-app/projects/1) - [تجريبي](#demo) - [سمات](#features) -- [مقدمة](https://immich.app/docs/overview/introduction) -- [تعليمات التحميل](https://immich.app/docs/install/requirements) -- [قواعد المساهمة](https://immich.app/docs/overview/support-the-project) +- [مقدمة](https://docs.immich.app/overview/introduction) +- [تعليمات التحميل](https://docs.immich.app/install/requirements) +- [قواعد المساهمة](https://docs.immich.app/overview/support-the-project) ## توثيق diff --git a/readme_i18n/README_ca_ES.md b/readme_i18n/README_ca_ES.md index 0b8dd5999b..d09362aa0f 100644 --- a/readme_i18n/README_ca_ES.md +++ b/readme_i18n/README_ca_ES.md @@ -27,7 +27,8 @@ Deutsch Nederlands Türkçe - 中文 + 简体中文 + 正體中文 Українська Русский Português Brasileiro @@ -44,13 +45,13 @@ ## Contingut -- [Documentació oficial](https://immich.app/docs) +- [Documentació oficial](https://docs.immich.app) - [Mapa de ruta](https://github.com/orgs/immich-app/projects/1) - [Demo](#demo) - [Funcionalitats](#funcionalitats) -- [Introducció](https://immich.app/docs/overview/introduction) -- [Instal·lació](https://immich.app/docs/install/requirements) -- [Directrius de contribució](https://immich.app/docs/overview/support-the-project) +- [Introducció](https://docs.immich.app/overview/introduction) +- [Instal·lació](https://docs.immich.app/install/requirements) +- [Directrius de contribució](https://docs.immich.app/overview/support-the-project) ## Documentació diff --git a/readme_i18n/README_de_DE.md b/readme_i18n/README_de_DE.md index d24818b881..a8685e0902 100644 --- a/readme_i18n/README_de_DE.md +++ b/readme_i18n/README_de_DE.md @@ -27,7 +27,8 @@ 한국어 Nederlands Türkçe - 中文 + 简体中文 + 正體中文 Українська Русский Português Brasileiro @@ -50,14 +51,14 @@ ## Inhalt -- [Offizielle Dokumentation](https://immich.app/docs) -- [Über Immich](https://immich.app/docs/overview/introduction) -- [Installation](https://immich.app/docs/install/requirements) +- [Offizielle Dokumentation](https://docs.immich.app) +- [Über Immich](https://docs.immich.app/overview/introduction) +- [Installation](https://docs.immich.app/install/requirements) - [Roadmap](https://github.com/orgs/immich-app/projects/1) - [Demo](#demo) - [Funktionen](#funktionen) -- [Übersetzungen](https://immich.app/docs/developer/translations) -- [Beitragsrichtlinien](https://immich.app/docs/overview/support-the-project) +- [Übersetzungen](https://docs.immich.app/developer/translations) +- [Beitragsrichtlinien](https://docs.immich.app/overview/support-the-project) ## Demo @@ -107,7 +108,7 @@ Die Web-Demo kannst Du unter https://demo.immich.app finden. Für die Handy-App ## Übersetzungen -Mehr zum Thema Übersetzungen kannst du [hier](https://immich.app/docs/developer/translations) erfahren. +Mehr zum Thema Übersetzungen kannst du [hier](https://docs.immich.app/developer/translations) erfahren. Translation status diff --git a/readme_i18n/README_es_ES.md b/readme_i18n/README_es_ES.md index 00417c9188..032f8c50a8 100644 --- a/readme_i18n/README_es_ES.md +++ b/readme_i18n/README_es_ES.md @@ -27,7 +27,8 @@ Deutsch Nederlands Türkçe - 中文 + 简体中文 + 正體中文 Українська Русский Português Brasileiro @@ -45,13 +46,13 @@ ## Contenido -- [Documentación oficial](https://immich.app/docs) +- [Documentación oficial](https://docs.immich.app) - [Hoja de ruta](https://github.com/orgs/immich-app/projects/1) - [Demo](#demo) - [Funciones](#funciones) -- [Introducción](https://immich.app/docs/overview/introduction) -- [Instalación](https://immich.app/docs/install/requirements) -- [Directrices para contribuir](https://immich.app/docs/overview/support-the-project) +- [Introducción](https://docs.immich.app/overview/introduction) +- [Instalación](https://docs.immich.app/install/requirements) +- [Directrices para contribuir](https://docs.immich.app/overview/support-the-project) ## Documentación @@ -99,7 +100,7 @@ contraseña: demo ## Traducciones -Lea mas acerca de las traducciones [acá](https://immich.app/docs/developer/translations). +Lea mas acerca de las traducciones [acá](https://docs.immich.app/developer/translations). Translation status diff --git a/readme_i18n/README_fr_FR.md b/readme_i18n/README_fr_FR.md index e1d4fb1cbc..349a0c49ce 100644 --- a/readme_i18n/README_fr_FR.md +++ b/readme_i18n/README_fr_FR.md @@ -27,7 +27,8 @@ Deutsch Nederlands Türkçe - 中文 + 简体中文 + 正體中文 Українська Русский Português Brasileiro @@ -45,13 +46,13 @@ ## Sommaire -- [Documentation officielle](https://immich.app/docs) +- [Documentation officielle](https://docs.immich.app) - [Feuille de route](https://github.com/orgs/immich-app/projects/1) - [Démo](#démo) - [Fonctionnalités](#fonctionnalités) -- [Introduction](https://immich.app/docs/overview/introduction) -- [Installation](https://immich.app/docs/install/requirements) -- [Contribution](https://immich.app/docs/overview/support-the-project) +- [Introduction](https://docs.immich.app/overview/introduction) +- [Installation](https://docs.immich.app/install/requirements) +- [Contribution](https://docs.immich.app/overview/support-the-project) ## Documentation diff --git a/readme_i18n/README_it_IT.md b/readme_i18n/README_it_IT.md index 5a368fe1f9..711840fd9d 100644 --- a/readme_i18n/README_it_IT.md +++ b/readme_i18n/README_it_IT.md @@ -28,7 +28,8 @@ Deutsch Nederlands Türkçe - 中文 + 简体中文 + 正體中文 Українська Русский Português Brasileiro @@ -49,14 +50,14 @@ ## Link utili -- [Documentazione](https://immich.app/docs) -- [Informazioni](https://immich.app/docs/overview/introduction) -- [Installazione](https://immich.app/docs/install/requirements) +- [Documentazione](https://docs.immich.app) +- [Informazioni](https://docs.immich.app/overview/introduction) +- [Installazione](https://docs.immich.app/install/requirements) - [Roadmap](https://immich.app/roadmap) - [Demo](#demo) - [Funzionalità](#funzionalità) -- [Traduzioni](https://immich.app/docs/developer/translations) -- [Contribuire](https://immich.app/docs/overview/support-the-project) +- [Traduzioni](https://docs.immich.app/developer/translations) +- [Contribuire](https://docs.immich.app/overview/support-the-project) ## Demo @@ -106,7 +107,7 @@ Per l’app mobile puoi usare `https://demo.immich.app` come `Server Endpoint UR ## Traduzioni -Scopri di più sulle traduzioni [qui](https://immich.app/docs/developer/translations). +Scopri di più sulle traduzioni [qui](https://docs.immich.app/developer/translations). Stato traduzioni diff --git a/readme_i18n/README_ja_JP.md b/readme_i18n/README_ja_JP.md index 60dd0f3ed7..0e74077895 100644 --- a/readme_i18n/README_ja_JP.md +++ b/readme_i18n/README_ja_JP.md @@ -27,7 +27,8 @@ Deutsch Nederlands Türkçe - 中文 + 简体中文 + 正體中文 Русский Português Brasileiro Svenska @@ -44,13 +45,13 @@ ## コンテンツ -- [公式ドキュメント](https://immich.app/docs) +- [公式ドキュメント](https://docs.immich.app) - [ロードマップ](https://github.com/orgs/immich-app/projects/1) - [デモ](#デモ) - [機能](#機能) -- [紹介](https://immich.app/docs/overview/introduction) -- [インストール](https://immich.app/docs/install/requirements) -- [コントリビューションガイド](https://immich.app/docs/overview/support-the-project) +- [紹介](https://docs.immich.app/overview/introduction) +- [インストール](https://docs.immich.app/install/requirements) +- [コントリビューションガイド](https://docs.immich.app/overview/support-the-project) ## ドキュメント diff --git a/readme_i18n/README_ko_KR.md b/readme_i18n/README_ko_KR.md index 031e2fd9ca..c2dfd11dd3 100644 --- a/readme_i18n/README_ko_KR.md +++ b/readme_i18n/README_ko_KR.md @@ -28,7 +28,8 @@ Deutsch Nederlands Türkçe -中文 +简体中文 +正體中文 Українська Русский Português Brasileiro @@ -50,14 +51,14 @@ ## 링크 -- [문서](https://immich.app/docs) -- [소개](https://immich.app/docs/overview/introduction) -- [설치](https://immich.app/docs/install/requirements) +- [문서](https://docs.immich.app) +- [소개](https://docs.immich.app/overview/introduction) +- [설치](https://docs.immich.app/install/requirements) - [로드맵](https://immich.app/roadmap) - [데모](#데모) - [기능](#기능) -- [번역](https://immich.app/docs/developer/tranlations) -- [기여](https://immich.app/docs/overview/support-the-project) +- [번역](https://docs.immich.app/developer/tranlations) +- [기여](https://docs.immich.app/overview/support-the-project) ## 데모 @@ -104,7 +105,7 @@ ## 번역 -번역에 대한 자세한 정보는 [이곳](https://immich.app/docs/developer/translations)에서 확인하세요. +번역에 대한 자세한 정보는 [이곳](https://docs.immich.app/developer/translations)에서 확인하세요. 번역 현황 diff --git a/readme_i18n/README_nl_NL.md b/readme_i18n/README_nl_NL.md index 46692bc612..ac72e9d238 100644 --- a/readme_i18n/README_nl_NL.md +++ b/readme_i18n/README_nl_NL.md @@ -27,7 +27,8 @@ 한국어 Deutsch Türkçe - 中文 + 简体中文 + 正體中文 Українська Русский Português Brasileiro @@ -45,13 +46,13 @@ ## Inhoud -- [Officiële documentatie](https://immich.app/docs) +- [Officiële documentatie](https://docs.immich.app) - [Toekomstplannen](https://github.com/orgs/immich-app/projects/1) - [Demo](#demo) - [Functies](#functies) -- [Introductie](https://immich.app/docs/overview/introduction) -- [Installatie](https://immich.app/docs/install/requirements) -- [Richtlijnen voor bijdragen](https://immich.app/docs/overview/support-the-project) +- [Introductie](https://docs.immich.app/overview/introduction) +- [Installatie](https://docs.immich.app/install/requirements) +- [Richtlijnen voor bijdragen](https://docs.immich.app/overview/support-the-project) ## Documentatie @@ -102,7 +103,7 @@ Je kunt de demo [hier](https://demo.immich.app/) bekijken. Voor de mobiele app k ## Vertalingen -Je kunt [hier](https://immich.app/docs/developer/translations) meer over vertalingen lezen. +Je kunt [hier](https://docs.immich.app/developer/translations) meer over vertalingen lezen. ## Repository activiteit diff --git a/readme_i18n/README_pt_BR.md b/readme_i18n/README_pt_BR.md index 2320e8fd6f..d6f51cd779 100644 --- a/readme_i18n/README_pt_BR.md +++ b/readme_i18n/README_pt_BR.md @@ -29,7 +29,8 @@ Deutsch Nederlands Türkçe -中文 +简体中文 +正體中文 Українська Русский Svenska @@ -55,14 +56,14 @@ ## Links -- [Documentação](https://immich.app/docs) -- [Sobre](https://immich.app/docs/overview/introduction) -- [Instalação](https://immich.app/docs/install/requirements) +- [Documentação](https://docs.immich.app) +- [Sobre](https://docs.immich.app/overview/introduction) +- [Instalação](https://docs.immich.app/install/requirements) - [Roadmap](https://github.com/orgs/immich-app/projects/1) - [Demonstração](#demonstração) - [Funcionalidades](#funcionalidades) -- [Traduções](https://immich.app/docs/developer/translations) -- [Diretrizes de Contribuição](https://immich.app/docs/overview/support-the-project) +- [Traduções](https://docs.immich.app/developer/translations) +- [Diretrizes de Contribuição](https://docs.immich.app/overview/support-the-project) ## Demonstração @@ -115,7 +116,7 @@ Acesse a demonstração [aqui](https://demo.immich.app). No aplicativo para disp ## Traduções Leia mais sobre as traduções -[aqui](https://immich.app/docs/developer/translations). +[aqui](https://docs.immich.app/developer/translations). Status da tradução diff --git a/readme_i18n/README_ru_RU.md b/readme_i18n/README_ru_RU.md index be97259acc..9c60e5f772 100644 --- a/readme_i18n/README_ru_RU.md +++ b/readme_i18n/README_ru_RU.md @@ -28,7 +28,8 @@ Deutsch Nederlands Türkçe - 中文 + 简体中文 + 正體中文 Українська Português Brasileiro Svenska @@ -51,14 +52,14 @@ ## Содержание -- [Официальная документация](https://immich.app/docs) -- [Введение](https://immich.app/docs/overview/introduction) -- [Установка](https://immich.app/docs/install/requirements) +- [Официальная документация](https://docs.immich.app) +- [Введение](https://docs.immich.app/overview/introduction) +- [Установка](https://docs.immich.app/install/requirements) - [План разработки](https://github.com/orgs/immich-app/projects/1) - [Демо](#demo) - [Возможности](#features) -- [Перевод](https://immich.app/docs/developer/translations) -- [Гид по участию и поддержке проекта](https://immich.app/docs/overview/support-the-project) +- [Перевод](https://docs.immich.app/developer/translations) +- [Гид по участию и поддержке проекта](https://docs.immich.app/overview/support-the-project) ## Демо @@ -93,7 +94,7 @@ | LivePhoto/MotionPhoto воспроизведение и бекап | Да | Да | | Отображение 360° изображений | Нет | Да | | Настраиваемая структура хранилища | Да | Да | -| Общий доступ к контенту | Нет | Да | +| Общий доступ к контенту | Да | Да | | Архив и избранное | Да | Да | | Мировая карта | Да | Да | | Совместное использование | Да | Да | @@ -103,11 +104,11 @@ | Галереи только для просмотра | Да | Да | | Коллажи | Да | Да | | Метки (теги) | Нет | Да | -| Просмотр папкой | Нет | Да | +| Просмотр папкой | Да | Да | ## Перевод -Всё про перевод проекта [Здесь](https://immich.app/docs/developer/translations). +Всё про перевод проекта [Здесь](https://docs.immich.app/developer/translations). Translation status diff --git a/readme_i18n/README_sv_SE.md b/readme_i18n/README_sv_SE.md index daa68b9874..a421c23c2e 100644 --- a/readme_i18n/README_sv_SE.md +++ b/readme_i18n/README_sv_SE.md @@ -29,7 +29,8 @@ Deutsch Nederlands Türkçe - 中文 + 简体中文 + 正體中文 Українська Русский Português Brasileiro @@ -46,13 +47,13 @@ ## Innehåll -- [Officiell Dokumentation](https://immich.app/docs) +- [Officiell Dokumentation](https://docs.immich.app) - [Roadmap](https://github.com/orgs/immich-app/projects/1) - [Demo](#demo) - [Funktioner](#features) -- [Introduktion](https://immich.app/docs/overview/introduction) -- [Installation](https://immich.app/docs/install/requirements) -- [Riktlinjer för Bidrag](https://immich.app/docs/overview/support-the-project) +- [Introduktion](https://docs.immich.app/overview/introduction) +- [Installation](https://docs.immich.app/install/requirements) +- [Riktlinjer för Bidrag](https://docs.immich.app/overview/support-the-project) ## Dokumentation diff --git a/readme_i18n/README_th_TH.md b/readme_i18n/README_th_TH.md index bdb6db868d..cdc28b14e6 100644 --- a/readme_i18n/README_th_TH.md +++ b/readme_i18n/README_th_TH.md @@ -31,7 +31,8 @@ Deutsch Nederlands Türkçe - 中文 + 简体中文 + 正體中文 Українська Русский Português Brasileiro @@ -52,14 +53,14 @@ ## ลิงก์ -- [คู่มือ](https://immich.app/docs) -- [เกี่ยวกับ](https://immich.app/docs/overview/introduction) -- [การติดตั้ง](https://immich.app/docs/install/requirements) +- [คู่มือ](https://docs.immich.app) +- [เกี่ยวกับ](https://docs.immich.app/overview/introduction) +- [การติดตั้ง](https://docs.immich.app/install/requirements) - [โรดแมป](https://immich.app/roadmap) - [สาธิต](#สาธิต) - [คุณสมบัติ](#คุณสมบัติ) -- [การแปลภาษา](https://immich.app/docs/developer/translations) -- [สนับสนุนโพรเจกต์](https://immich.app/docs/overview/support-the-project) +- [การแปลภาษา](https://docs.immich.app/developer/translations) +- [สนับสนุนโพรเจกต์](https://docs.immich.app/overview/support-the-project) ## สาธิต @@ -106,7 +107,7 @@ ## การแปลภาษา -อ่านเพิ่มเติมเกี่ยวกับการแปล [ที่นี่](https://immich.app/docs/developer/translations) +อ่านเพิ่มเติมเกี่ยวกับการแปล [ที่นี่](https://docs.immich.app/developer/translations) สถานะการแปล diff --git a/readme_i18n/README_tr_TR.md b/readme_i18n/README_tr_TR.md index 6285ab55a2..46aef49745 100644 --- a/readme_i18n/README_tr_TR.md +++ b/readme_i18n/README_tr_TR.md @@ -27,7 +27,8 @@ 한국어 Deutsch Nederlands - 中文 + 简体中文 + 正體中文 Українська Русский Português Brasileiro @@ -44,13 +45,13 @@ ## Content -- [Resmi Belgeler](https://immich.app/docs) +- [Resmi Belgeler](https://docs.immich.app) - [Yol Haritası](https://github.com/orgs/immich-app/projects/1) - [Demo](#demo) - [Özellikler](#özellikler) -- [Giriş](https://immich.app/docs/overview/introduction) -- [Kurulum](https://immich.app/docs/install/requirements) -- [Katkı Sağlama Rehberi](https://immich.app/docs/overview/support-the-project) +- [Giriş](https://docs.immich.app/overview/introduction) +- [Kurulum](https://docs.immich.app/install/requirements) +- [Katkı Sağlama Rehberi](https://docs.immich.app/overview/support-the-project) ## Belgeler diff --git a/readme_i18n/README_uk_UA.md b/readme_i18n/README_uk_UA.md index 33687bbc50..297054ee42 100644 --- a/readme_i18n/README_uk_UA.md +++ b/readme_i18n/README_uk_UA.md @@ -29,7 +29,8 @@ Deutsch Nederlands Türkçe - 中文 + 简体中文 + 正體中文 Русский Português Brasileiro Svenska @@ -50,14 +51,14 @@ ## Посилання -- [Документація](https://immich.app/docs) -- [Про проєкт](https://immich.app/docs/overview/introduction) -- [Встановлення](https://immich.app/docs/install/requirements) +- [Документація](https://docs.immich.app) +- [Про проєкт](https://docs.immich.app/overview/introduction) +- [Встановлення](https://docs.immich.app/install/requirements) - [Дорожня карта](https://immich.app/roadmap) - [Демо](#демо) - [Функції](#функції) -- [Переклади](https://immich.app/docs/developer/translations) -- [Гід для розробки проєкту](https://immich.app/docs/overview/support-the-project) +- [Переклади](https://docs.immich.app/developer/translations) +- [Гід для розробки проєкту](https://docs.immich.app/overview/support-the-project) ## Демо @@ -106,7 +107,7 @@ ## Переклади -Більше про переклади [тут](https://immich.app/docs/developer/translations). +Більше про переклади [тут](https://docs.immich.app/developer/translations). Статус перекладів diff --git a/readme_i18n/README_vi_VN.md b/readme_i18n/README_vi_VN.md index fd04bd9fa1..b6b22ff610 100644 --- a/readme_i18n/README_vi_VN.md +++ b/readme_i18n/README_vi_VN.md @@ -29,7 +29,8 @@ Deutsch Nederlands Türkçe -中文 +简体中文 +正體中文 Українська Русский Português Brasileiro @@ -52,14 +53,14 @@ ## Liên kết -- [Tài liệu](https://immich.app/docs) -- [Giới thiệu](https://immich.app/docs/overview/introduction) -- [Cài đặt](https://immich.app/docs/install/requirements) +- [Tài liệu](https://docs.immich.app) +- [Giới thiệu](https://docs.immich.app/overview/introduction) +- [Cài đặt](https://docs.immich.app/install/requirements) - [Lộ trình](https://immich.app/roadmap) - [Demo](#demo) - [Tính năng](#Tính-năng) -- [Dịch thuật](https://immich.app/docs/developer/translations) -- [Đóng góp](https://immich.app/docs/overview/support-the-project) +- [Dịch thuật](https://docs.immich.app/developer/translations) +- [Đóng góp](https://docs.immich.app/overview/support-the-project) ## Demo @@ -106,7 +107,7 @@ Truy cập bản demo [tại đây](https://demo.immich.app). Đối với ứng ## Dịch thuật -Đọc thêm về dịch thuật [tại đây](https://immich.app/docs/developer/translations). +Đọc thêm về dịch thuật [tại đây](https://docs.immich.app/developer/translations). Tình trạng dịch thuật diff --git a/readme_i18n/README_zh_CN.md b/readme_i18n/README_zh_CN.md index 5151e35379..b48e69f94d 100644 --- a/readme_i18n/README_zh_CN.md +++ b/readme_i18n/README_zh_CN.md @@ -32,6 +32,7 @@ Deutsch Nederlands Türkçe + 正體中文 Українська Русский Português Brasileiro @@ -54,14 +55,14 @@ ## 目录 -- [官方文档](https://immich.app/docs) -- [项目总览](https://immich.app/docs/overview/introduction) -- [安装教程](https://immich.app/docs/install/requirements) +- [官方文档](https://docs.immich.app) +- [项目总览](https://docs.immich.app/overview/introduction) +- [安装教程](https://docs.immich.app/install/requirements) - [路线图](https://immich.app/roadmap) - [在线演示](#示例) - [功能特性](#功能特性) -- [多语言](https://immich.app/docs/developer/translations) -- [贡献者](https://immich.app/docs/overview/support-the-project) +- [多语言](https://docs.immich.app/developer/translations) +- [贡献者](https://docs.immich.app/overview/support-the-project) ## 示例 @@ -110,7 +111,7 @@ ## 多语言 -关于翻译的更多信息请参见[此处](https://immich.app/docs/developer/translations)。 +关于翻译的更多信息请参见[此处](https://docs.immich.app/developer/translations)。 翻译进度 diff --git a/readme_i18n/README_zh_TW.md b/readme_i18n/README_zh_TW.md new file mode 100644 index 0000000000..bf14ce2dc0 --- /dev/null +++ b/readme_i18n/README_zh_TW.md @@ -0,0 +1,132 @@ +

+
+
授權條款:AGPLv3 + + Discord + +
+
+

+ +

+ +

+

高效能的自架照片和影片管理解決方案

+
+ + + +
+ +

+ English + Català + Español + Français + Italiano + 日本語 + 한국어 + Deutsch + Nederlands + Türkçe + 简体中文 + 正體中文 + Українська + Русский + Português Brasileiro + Svenska + العربية + Tiếng Việt + ภาษาไทย +

+ +> [!WARNING] +> ⚠️ 請務必遵循 [3-2-1 備份原則](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/),守護珍貴的照片與影片! +> + +> [!NOTE] +> 主要說明文件(包含安裝指南)可於 https://immich.app/ 取得。 + +## 連結 + +- [說明文件](https://docs.immich.app/) +- [關於](https://docs.immich.app/overview/introduction) +- [安裝](https://docs.immich.app/install/requirements) +- [發展藍圖](https://immich.app/roadmap) +- [線上體驗](#線上體驗) +- [功能](#功能) +- [翻譯](https://docs.immich.app/developer/translations) +- [貢獻指南](https://docs.immich.app/overview/support-the-project) + +## 線上體驗 + +請前往 [Demoo 網站](https://demo.immich.app) 立即體驗 Immich。若要在手機 App 試用,請在 `伺服器端點 URL` 欄位輸入 `https://demo.immich.app`。 + +### 登入資訊 + +| 電子郵件 | 密碼 | +| --------------- | ------ | +| demo@immich.app | demo | + +## 功能 + +| 功能 | 手機版 | 網頁版 | +| :----------------------------------------- | ------ | ------ | +| 上傳與檢視照片與影片 | 是 | 是 | +| 開啟 App 時自動備份 | 是 | 不適用 | +| 避免重複媒體 | 是 | 是 | +| 選擇要備份的相簿 | 是 | 不適用 | +| 下載照片與影片到本機裝置 | 是 | 是 | +| 多使用者支援 | 是 | 是 | +| 相簿與共享相簿 | 是 | 是 | +| 可拖曳的捲軸 | 是 | 是 | +| 支援 RAW 格式 | 是 | 是 | +| 中繼資料檢視(EXIF、地圖) | 是 | 是 | +| 依中繼資料、物件、臉孔與 CLIP 搜尋 | 是 | 是 | +| 管理功能(使用者管理) | 否 | 是 | +| 背景備份 | 是 | 不適用 | +| 虛擬滾動 | 是 | 是 | +| 支援 OAuth | 是 | 是 | +| API 金鑰 | 不適用 | 是 | +| Live Photo/動態照片備份與播放 | 是 | 是 | +| 支援 360 度全景照片顯示 | 否 | 是 | +| 使用者自訂儲存結構 | 是 | 是 | +| 公開分享 | 是 | 是 | +| 封存與收藏 | 是 | 是 | +| 世界地圖 | 是 | 是 | +| 親朋好友分享 | 是 | 是 | +| 臉部辨識與分群 | 是 | 是 | +| 回憶(x 年前) | 是 | 是 | +| 離線支援 | 是 | 否 | +| 唯讀媒體庫 | 是 | 是 | +| 照片堆疊 | 是 | 是 | +| 標籤 | 否 | 是 | +| 資料夾檢視 | 是 | 是 | + +## 翻譯 + +更多翻譯相關資訊請見 [此處](https://docs.immich.app/developer/translations)。 + + +翻譯狀態 + + +## 專案活躍度 + +![專案活躍度](https://repobeats.axiom.co/api/embed/9e86d9dc3ddd137161f2f6d2e758d7863b1789cb.svg "Repobeats 分析圖片") + +## Star 數量歷史紀錄 + + + + + + Star 歷史紀錄圖表 + + + +## 貢獻者 + + + + diff --git a/renovate.json b/renovate.json index 5fe45d61f7..fbbc8976bd 100644 --- a/renovate.json +++ b/renovate.json @@ -21,6 +21,17 @@ "addLabels": [ "📱mobile" ] + }, + { + "matchPackageNames": ["ghcr.io/immich-app/postgres"], + "matchUpdateTypes": ["major"], + "enabled": false + }, + { + "matchPackageNames": ["ruby"], + "groupName": "ruby", + "matchCurrentVersion": "< 3.4", + "enabled": false } ], "ignorePaths": [ diff --git a/server/.nvmrc b/server/.nvmrc index e2228113dd..0a492611a0 100644 --- a/server/.nvmrc +++ b/server/.nvmrc @@ -1 +1 @@ -22.19.0 +24.11.0 diff --git a/server/Dockerfile b/server/Dockerfile index 6702b338c5..0bb7fc6be5 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -1,39 +1,72 @@ -FROM ghcr.io/immich-app/base-server-dev:202509210934@sha256:b5ce2d7eaf379d4cf15efd4bab180d8afc8a80d20b36c9800f4091aca6ae267e AS builder +FROM ghcr.io/immich-app/base-server-dev:202511041104@sha256:7558931a4a71989e7fd9fa3e1ba6c28da15891867310edda8c58236171839f2f AS builder ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \ CI=1 \ - COREPACK_HOME=/tmp + COREPACK_HOME=/tmp \ + PNPM_HOME=/buildcache/pnpm-store \ + PATH="/buildcache/pnpm-store:$PATH" RUN npm install --global corepack@latest && \ - corepack enable pnpm + corepack enable pnpm && \ + pnpm config set store-dir "$PNPM_HOME" FROM builder AS server WORKDIR /usr/src/app -COPY ./package* ./pnpm* .pnpmfile.cjs ./ COPY ./server ./server/ -RUN SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm --filter immich --frozen-lockfile build && \ +RUN --mount=type=cache,id=pnpm-server,target=/buildcache/pnpm-store \ + --mount=type=bind,source=package.json,target=package.json \ + --mount=type=bind,source=.pnpmfile.cjs,target=.pnpmfile.cjs \ + --mount=type=bind,source=pnpm-lock.yaml,target=pnpm-lock.yaml \ + --mount=type=bind,source=pnpm-workspace.yaml,target=pnpm-workspace.yaml \ + SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm --filter immich --frozen-lockfile build && \ SHARP_FORCE_GLOBAL_LIBVIPS=true pnpm --filter immich --frozen-lockfile --prod --no-optional deploy /output/server-pruned FROM builder AS web WORKDIR /usr/src/app -COPY ./package* ./pnpm* .pnpmfile.cjs ./ COPY ./web ./web/ COPY ./i18n ./i18n/ COPY ./open-api ./open-api/ -RUN SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm --filter @immich/sdk --filter immich-web --frozen-lockfile --force install && \ +RUN --mount=type=cache,id=pnpm-web,target=/buildcache/pnpm-store \ + --mount=type=bind,source=package.json,target=package.json \ + --mount=type=bind,source=.pnpmfile.cjs,target=.pnpmfile.cjs \ + --mount=type=bind,source=pnpm-lock.yaml,target=pnpm-lock.yaml \ + --mount=type=bind,source=pnpm-workspace.yaml,target=pnpm-workspace.yaml \ + SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm --filter @immich/sdk --filter immich-web --frozen-lockfile --force install && \ pnpm --filter @immich/sdk --filter immich-web build FROM builder AS cli -COPY ./package* ./pnpm* .pnpmfile.cjs ./ COPY ./cli ./cli/ COPY ./open-api ./open-api/ -RUN pnpm --filter @immich/sdk --filter @immich/cli --frozen-lockfile install && \ +RUN --mount=type=cache,id=pnpm-cli,target=/buildcache/pnpm-store \ + --mount=type=bind,source=package.json,target=package.json \ + --mount=type=bind,source=.pnpmfile.cjs,target=.pnpmfile.cjs \ + --mount=type=bind,source=pnpm-lock.yaml,target=pnpm-lock.yaml \ + --mount=type=bind,source=pnpm-workspace.yaml,target=pnpm-workspace.yaml \ + pnpm --filter @immich/sdk --filter @immich/cli --frozen-lockfile install && \ pnpm --filter @immich/sdk --filter @immich/cli build && \ pnpm --filter @immich/cli --prod --no-optional deploy /output/cli-pruned -FROM ghcr.io/immich-app/base-server-prod:202509210934@sha256:0c7eacf0ba88ca52e1a267cfc62d20d07792ea2c604818c2cbd37dc7dcefdac9 +FROM builder AS plugins + +COPY --from=ghcr.io/jdx/mise:2025.11.3 /usr/local/bin/mise /usr/local/bin/mise + +WORKDIR /usr/src/app +COPY ./plugins/mise.toml ./plugins/ +ENV MISE_TRUSTED_CONFIG_PATHS=/usr/src/app/plugins/mise.toml +RUN mise install --cd plugins + +COPY ./plugins ./plugins/ +# Build plugins +RUN --mount=type=cache,id=pnpm-plugins,target=/buildcache/pnpm-store \ + --mount=type=bind,source=package.json,target=package.json \ + --mount=type=bind,source=.pnpmfile.cjs,target=.pnpmfile.cjs \ + --mount=type=bind,source=pnpm-lock.yaml,target=pnpm-lock.yaml \ + --mount=type=bind,source=pnpm-workspace.yaml,target=pnpm-workspace.yaml \ + cd plugins && mise run build + +FROM ghcr.io/immich-app/base-server-prod:202511041104@sha256:57c0379977fd5521d83cdf661aecd1497c83a9a661ebafe0a5243a09fc1064cb WORKDIR /usr/src/app ENV NODE_ENV=production \ @@ -43,6 +76,8 @@ ENV NODE_ENV=production \ COPY --from=server /output/server-pruned ./server COPY --from=web /usr/src/app/web/build /build/www COPY --from=cli /output/cli-pruned ./cli +COPY --from=plugins /usr/src/app/plugins/dist /build/corePlugin/dist +COPY --from=plugins /usr/src/app/plugins/manifest.json /build/corePlugin/manifest.json RUN ln -s ../../cli/bin/immich server/bin/immich COPY LICENSE /licenses/LICENSE.txt COPY LICENSE /LICENSE diff --git a/server/Dockerfile.dev b/server/Dockerfile.dev index dd2e931745..133b8a835d 100644 --- a/server/Dockerfile.dev +++ b/server/Dockerfile.dev @@ -1,5 +1,5 @@ # dev build -FROM ghcr.io/immich-app/base-server-dev:202509210934@sha256:b5ce2d7eaf379d4cf15efd4bab180d8afc8a80d20b36c9800f4091aca6ae267e AS dev +FROM ghcr.io/immich-app/base-server-dev:202511041104@sha256:7558931a4a71989e7fd9fa3e1ba6c28da15891867310edda8c58236171839f2f AS dev ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \ CI=1 \ @@ -27,8 +27,8 @@ ENTRYPOINT ["tini", "--", "/bin/bash", "-c"] FROM dev AS dev-container-server RUN apt-get update --allow-releaseinfo-change && \ - apt-get install sudo inetutils-ping openjdk-11-jre-headless \ - vim nano \ + apt-get install sudo inetutils-ping openjdk-21-jre-headless \ + vim nano curl \ -y --no-install-recommends --fix-missing RUN usermod -aG sudo node && \ @@ -44,19 +44,24 @@ FROM dev-container-server AS dev-container-mobile USER root # Enable multiarch for arm64 if necessary RUN if [ "$(dpkg --print-architecture)" = "arm64" ]; then \ - dpkg --add-architecture amd64 && \ - apt-get update && \ - apt-get install -y --no-install-recommends \ - qemu-user-static \ - libc6:amd64 \ - libstdc++6:amd64 \ - libgcc1:amd64; \ + dpkg --add-architecture amd64 && \ + apt-get update && \ + apt-get install -y --no-install-recommends \ + gnupg \ + qemu-user-static \ + libc6:amd64 \ + libstdc++6:amd64 \ + libgcc1:amd64; \ + else \ + apt-get update && \ + apt-get install -y --no-install-recommends \ + gnupg; \ fi # Flutter SDK # https://flutter.dev/docs/development/tools/sdk/releases?tab=linux ENV FLUTTER_CHANNEL="stable" -ENV FLUTTER_VERSION="3.35.4" +ENV FLUTTER_VERSION="3.35.7" ENV FLUTTER_HOME=/flutter ENV PATH=${PATH}:${FLUTTER_HOME}/bin @@ -65,11 +70,11 @@ RUN mkdir -p ${FLUTTER_HOME} \ && curl -C - --output flutter.tar.xz https://storage.googleapis.com/flutter_infra_release/releases/${FLUTTER_CHANNEL}/linux/flutter_linux_${FLUTTER_VERSION}-${FLUTTER_CHANNEL}.tar.xz \ && tar -xf flutter.tar.xz --strip-components=1 -C ${FLUTTER_HOME} \ && rm flutter.tar.xz \ - && chown -R node ${FLUTTER_HOME} + && chown -R node ${FLUTTER_HOME} \ + && git config --global --add safe.directory ${FLUTTER_HOME} -RUN apt-get update \ - && wget -qO- https://dcm.dev/pgp-key.public | gpg --dearmor -o /usr/share/keyrings/dcm.gpg \ +RUN wget -qO- https://dcm.dev/pgp-key.public | gpg --dearmor -o /usr/share/keyrings/dcm.gpg \ && echo 'deb [signed-by=/usr/share/keyrings/dcm.gpg arch=amd64] https://dcm.dev/debian stable main' | tee /etc/apt/sources.list.d/dart_stable.list \ && apt-get update \ && apt-get install dcm -y diff --git a/server/bin/start.sh b/server/bin/start.sh index 15390ae158..0afff4c3a8 100755 --- a/server/bin/start.sh +++ b/server/bin/start.sh @@ -1,12 +1,13 @@ #!/usr/bin/env bash echo "Initializing Immich $IMMICH_SOURCE_REF" -lib_path="/usr/lib/$(arch)-linux-gnu/libmimalloc.so.2" -if [ -f "$lib_path" ]; then - export LD_PRELOAD="$lib_path" -else - echo "skipping libmimalloc - path not found $lib_path" -fi +# TODO: Update to mimalloc v3 when verified memory isn't released issue is fixed +# lib_path="/usr/lib/$(arch)-linux-gnu/libmimalloc.so.3" +# if [ -f "$lib_path" ]; then +# export LD_PRELOAD="$lib_path" +# else +# echo "skipping libmimalloc - path not found $lib_path" +# fi export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/usr/lib/jellyfin-ffmpeg/lib" SERVER_HOME="$(readlink -f "$(dirname "$0")/..")" diff --git a/server/mise.toml b/server/mise.toml new file mode 100644 index 0000000000..d2240c4289 --- /dev/null +++ b/server/mise.toml @@ -0,0 +1,66 @@ +[tasks.install] +run = "pnpm install --filter immich --frozen-lockfile" + +[tasks.build] +env._.path = "./node_modules/.bin" +run = "nest build" + +[tasks.test] +env._.path = "./node_modules/.bin" +run = "vitest --config test/vitest.config.mjs" + +[tasks."test-medium"] +env._.path = "./node_modules/.bin" +run = "vitest --config test/vitest.config.medium.mjs" + +[tasks.format] +env._.path = "./node_modules/.bin" +run = "prettier --check ." + +[tasks."format-fix"] +env._.path = "./node_modules/.bin" +run = "prettier --write ." + +[tasks.lint] +env._.path = "./node_modules/.bin" +run = "eslint \"src/**/*.ts\" \"test/**/*.ts\" --max-warnings 0" + +[tasks."lint-fix"] +run = { task = "lint --fix" } + +[tasks.check] +env._.path = "./node_modules/.bin" +run = "tsc --noEmit" + +[tasks.sql] +run = "node ./dist/bin/sync-open-api.js" + +[tasks."open-api"] +run = "node ./dist/bin/sync-open-api.js" + +[tasks.migrations] +run = "node ./dist/bin/migrations.js" +description = "Run database migration commands (create, generate, run, debug, or query)" + +[tasks."schema-drop"] +run = { task = "migrations query 'DROP schema public cascade; CREATE schema public;'" } + +[tasks."schema-reset"] +run = [ + { task = ":schema-drop" }, + { task = "migrations run" }, +] + +[tasks."email-dev"] +env._.path = "./node_modules/.bin" +run = "email dev -p 3050 --dir src/emails" + +[tasks.checklist] +run = [ + { task = ":install" }, + { task = ":format" }, + { task = ":lint" }, + { task = ":check" }, + { task = ":test-medium --run" }, + { task = ":test --run" }, +] diff --git a/server/package.json b/server/package.json index fd5a7e1376..a252a53b8a 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "immich", - "version": "1.143.0", + "version": "2.2.3", "description": "", "author": "", "private": true, @@ -22,11 +22,11 @@ "test:cov": "vitest --config test/vitest.config.mjs --coverage", "test:medium": "vitest --config test/vitest.config.medium.mjs", "typeorm": "typeorm", - "lifecycle": "node ./dist/utils/lifecycle.js", "migrations:debug": "node ./dist/bin/migrations.js debug", "migrations:generate": "node ./dist/bin/migrations.js generate", "migrations:create": "node ./dist/bin/migrations.js create", "migrations:run": "node ./dist/bin/migrations.js run", + "migrations:revert": "node ./dist/bin/migrations.js revert", "schema:drop": "node ./dist/bin/migrations.js query 'DROP schema public cascade; CREATE schema public;'", "schema:reset": "npm run schema:drop && npm run migrations:run", "sync:open-api": "node ./dist/bin/sync-open-api.js", @@ -34,6 +34,7 @@ "email:dev": "email dev -p 3050 --dir src/emails" }, "dependencies": { + "@extism/extism": "2.0.0-rc13", "@nestjs/bullmq": "^11.0.1", "@nestjs/common": "^11.0.4", "@nestjs/core": "^11.0.4", @@ -44,18 +45,19 @@ "@nestjs/websockets": "^11.0.4", "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^2.0.0", - "@opentelemetry/exporter-prometheus": "^0.205.0", - "@opentelemetry/instrumentation-http": "^0.205.0", - "@opentelemetry/instrumentation-ioredis": "^0.53.0", - "@opentelemetry/instrumentation-nestjs-core": "^0.51.0", - "@opentelemetry/instrumentation-pg": "^0.58.0", + "@opentelemetry/exporter-prometheus": "^0.207.0", + "@opentelemetry/instrumentation-http": "^0.207.0", + "@opentelemetry/instrumentation-ioredis": "^0.55.0", + "@opentelemetry/instrumentation-nestjs-core": "^0.54.0", + "@opentelemetry/instrumentation-pg": "^0.60.0", "@opentelemetry/resources": "^2.0.1", "@opentelemetry/sdk-metrics": "^2.0.1", - "@opentelemetry/sdk-node": "^0.205.0", + "@opentelemetry/sdk-node": "^0.207.0", "@opentelemetry/semantic-conventions": "^1.34.0", "@react-email/components": "^0.5.0", "@react-email/render": "^1.1.2", "@socket.io/redis-adapter": "^8.3.0", + "ajv": "^8.17.1", "archiver": "^7.0.0", "async-lock": "^1.4.0", "bcrypt": "^6.0.0", @@ -67,25 +69,26 @@ "compression": "^1.8.0", "cookie": "^1.0.2", "cookie-parser": "^1.4.7", - "cron": "4.3.0", - "exiftool-vendored": "^28.8.0", + "cron": "4.3.3", + "exiftool-vendored": "^31.1.0", "express": "^5.1.0", "fast-glob": "^3.3.2", "fluent-ffmpeg": "^2.1.2", "geo-tz": "^8.0.0", "handlebars": "^4.7.8", "i18n-iso-countries": "^7.6.0", - "ioredis": "^5.3.2", + "ioredis": "^5.8.2", "js-yaml": "^4.1.0", + "jsonwebtoken": "^9.0.2", "kysely": "0.28.2", - "kysely-postgres-js": "^2.0.0", + "kysely-postgres-js": "^3.0.0", "lodash": "^4.17.21", "luxon": "^3.4.2", "mnemonist": "^0.40.3", "multer": "^2.0.2", "nest-commander": "^3.16.0", "nestjs-cls": "^5.0.0", - "nestjs-kysely": "^3.0.0", + "nestjs-kysely": "3.1.2", "nestjs-otel": "^7.0.0", "nodemailer": "^7.0.0", "openid-client": "^6.3.3", @@ -101,7 +104,7 @@ "sanitize-filename": "^1.6.3", "sanitize-html": "^2.14.0", "semver": "^7.6.2", - "sharp": "^0.34.3", + "sharp": "^0.34.4", "sirv": "^3.0.0", "socket.io": "^4.8.1", "tailwindcss-preset-email": "^1.4.0", @@ -124,12 +127,13 @@ "@types/cookie-parser": "^1.4.8", "@types/express": "^5.0.0", "@types/fluent-ffmpeg": "^2.1.21", + "@types/jsonwebtoken": "^9.0.10", "@types/js-yaml": "^4.0.9", "@types/lodash": "^4.14.197", "@types/luxon": "^3.6.2", "@types/mock-fs": "^4.13.1", "@types/multer": "^2.0.0", - "@types/node": "^22.18.1", + "@types/node": "^22.19.0", "@types/nodemailer": "^7.0.0", "@types/picomatch": "^4.0.0", "@types/pngjs": "^6.0.5", @@ -161,9 +165,9 @@ "vitest": "^3.0.0" }, "volta": { - "node": "22.19.0" + "node": "24.11.0" }, "overrides": { - "sharp": "^0.34.3" + "sharp": "^0.34.4" } } diff --git a/server/src/app.module.ts b/server/src/app.module.ts index 8d261463e7..f80a47bb77 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -19,10 +19,11 @@ import { ConfigRepository } from 'src/repositories/config.repository'; import { EventRepository } from 'src/repositories/event.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { teardownTelemetry, TelemetryRepository } from 'src/repositories/telemetry.repository'; +import { WebsocketRepository } from 'src/repositories/websocket.repository'; import { services } from 'src/services'; import { AuthService } from 'src/services/auth.service'; import { CliService } from 'src/services/cli.service'; -import { JobService } from 'src/services/job.service'; +import { QueueService } from 'src/services/queue.service'; import { getKyselyConfig } from 'src/utils/database'; const common = [...repositories, ...services, GlobalExceptionFilter]; @@ -51,10 +52,11 @@ class BaseModule implements OnModuleInit, OnModuleDestroy { constructor( @Inject(IWorker) private worker: ImmichWorker, logger: LoggingRepository, - private eventRepository: EventRepository, - private jobService: JobService, - private telemetryRepository: TelemetryRepository, private authService: AuthService, + private eventRepository: EventRepository, + private queueService: QueueService, + private telemetryRepository: TelemetryRepository, + private websocketRepository: WebsocketRepository, ) { logger.setAppName(this.worker); } @@ -62,9 +64,9 @@ class BaseModule implements OnModuleInit, OnModuleDestroy { async onModuleInit() { this.telemetryRepository.setup({ repositories }); - this.jobService.setServices(services); + this.queueService.setServices(services); - this.eventRepository.setAuthFn(async (client) => + this.websocketRepository.setAuthFn(async (client) => this.authService.authenticate({ headers: client.request.headers, queryParams: {}, diff --git a/server/src/bin/migrations.ts b/server/src/bin/migrations.ts index ebb07af442..588f358023 100644 --- a/server/src/bin/migrations.ts +++ b/server/src/bin/migrations.ts @@ -2,7 +2,7 @@ process.env.DB_URL = process.env.DB_URL || 'postgres://postgres:postgres@localhost:5432/immich'; import { Kysely, sql } from 'kysely'; -import { mkdirSync, writeFileSync } from 'node:fs'; +import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from 'node:fs'; import { basename, dirname, extname, join } from 'node:path'; import postgres from 'postgres'; import { ConfigRepository } from 'src/repositories/config.repository'; @@ -27,6 +27,11 @@ const main = async () => { return; } + case 'revert': { + await revert(); + return; + } + case 'query': { const query = process.argv[3]; await runQuery(query); @@ -48,6 +53,7 @@ const main = async () => { node dist/bin/migrations.js create node dist/bin/migrations.js generate node dist/bin/migrations.js run + node dist/bin/migrations.js revert `); } } @@ -74,6 +80,25 @@ const runMigrations = async () => { await db.destroy(); }; +const revert = async () => { + const configRepository = new ConfigRepository(); + const logger = LoggingRepository.create(); + const db = getDatabaseClient(); + const databaseRepository = new DatabaseRepository(db, logger, configRepository); + + try { + const migrationName = await databaseRepository.revertLastMigration(); + if (!migrationName) { + console.log('No migrations to revert'); + return; + } + + markMigrationAsReverted(migrationName); + } finally { + await db.destroy(); + } +}; + const debug = async () => { const { up } = await compare(); const upSql = '-- UP\n' + up.asSql({ comments: true }).join('\n'); @@ -148,6 +173,37 @@ ${downSql} `; }; +const markMigrationAsReverted = (migrationName: string) => { + // eslint-disable-next-line unicorn/prefer-module + const distRoot = join(__dirname, '..'); + const projectRoot = join(distRoot, '..'); + const sourceFolder = join(projectRoot, 'src', 'schema', 'migrations'); + const distFolder = join(distRoot, 'schema', 'migrations'); + + const sourcePath = join(sourceFolder, `${migrationName}.ts`); + const revertedFolder = join(sourceFolder, 'reverted'); + const revertedPath = join(revertedFolder, `${migrationName}.ts`); + + if (existsSync(revertedPath)) { + console.log(`Migration ${migrationName} is already marked as reverted`); + } else if (existsSync(sourcePath)) { + mkdirSync(revertedFolder, { recursive: true }); + renameSync(sourcePath, revertedPath); + console.log(`Moved ${sourcePath} to ${revertedPath}`); + } else { + console.warn(`Source migration file not found for ${migrationName}`); + } + + const distBase = join(distFolder, migrationName); + for (const extension of ['.js', '.js.map', '.d.ts']) { + const filePath = `${distBase}${extension}`; + if (existsSync(filePath)) { + rmSync(filePath, { force: true }); + console.log(`Removed ${filePath}`); + } + } +}; + main() .then(() => { process.exit(0); diff --git a/server/src/config.ts b/server/src/config.ts index 66c03450fa..c18acd79f8 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -74,6 +74,13 @@ export interface SystemConfig { minFaces: number; maxDistance: number; }; + ocr: { + enabled: boolean; + modelName: string; + minDetectionScore: number; + minRecognitionScore: number; + maxResolution: number; + }; }; map: { enabled: boolean; @@ -159,6 +166,7 @@ export interface SystemConfig { ignoreCert: boolean; host: string; port: number; + secure: boolean; username: string; password: string; }; @@ -226,6 +234,8 @@ export const defaults = Object.freeze({ [QueueName.ThumbnailGeneration]: { concurrency: 3 }, [QueueName.VideoConversion]: { concurrency: 1 }, [QueueName.Notification]: { concurrency: 5 }, + [QueueName.Ocr]: { concurrency: 1 }, + [QueueName.Workflow]: { concurrency: 5 }, }, logging: { enabled: true, @@ -254,6 +264,13 @@ export const defaults = Object.freeze({ maxDistance: 0.5, minFaces: 3, }, + ocr: { + enabled: true, + modelName: 'PP-OCRv5_mobile', + minDetectionScore: 0.5, + minRecognitionScore: 0.8, + maxResolution: 736, + }, }, map: { enabled: true, @@ -356,6 +373,7 @@ export const defaults = Object.freeze({ ignoreCert: false, host: '', port: 587, + secure: false, username: '', password: '', }, diff --git a/server/src/constants.ts b/server/src/constants.ts index 1bae521a9f..d624557c54 100644 --- a/server/src/constants.ts +++ b/server/src/constants.ts @@ -2,18 +2,13 @@ import { Duration } from 'luxon'; import { readFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { SemVer } from 'semver'; -import { DatabaseExtension, ExifOrientation, VectorIndex } from 'src/enum'; +import { ApiTag, DatabaseExtension, ExifOrientation, VectorIndex } from 'src/enum'; export const POSTGRES_VERSION_RANGE = '>=14.0.0'; -export const VECTORCHORD_VERSION_RANGE = '>=0.3 <0.5'; +export const VECTORCHORD_VERSION_RANGE = '>=0.3 <0.6'; export const VECTORS_VERSION_RANGE = '>=0.2 <0.4'; export const VECTOR_VERSION_RANGE = '>=0.5 <1'; -export const NEXT_RELEASE = 'NEXT_RELEASE'; -export const LIFECYCLE_EXTENSION = 'x-immich-lifecycle'; -export const DEPRECATED_IN_PREFIX = 'This property was deprecated in '; -export const ADDED_IN_PREFIX = 'This property was added in '; - export const JOBS_ASSET_PAGINATION_SIZE = 1000; export const JOBS_LIBRARY_PAGINATION_SIZE = 10_000; @@ -138,3 +133,60 @@ export const ORIENTATION_TO_SHARP_ROTATION: Record = { + [ApiTag.Activities]: 'An activity is a like or a comment made by a user on an asset or album.', + [ApiTag.Albums]: 'An album is a collection of assets that can be shared with other users or via shared links.', + [ApiTag.ApiKeys]: 'An api key can be used to programmatically access the Immich API.', + [ApiTag.Assets]: 'An asset is an image or video that has been uploaded to Immich.', + [ApiTag.Authentication]: 'Endpoints related to user authentication, including OAuth.', + [ApiTag.AuthenticationAdmin]: 'Administrative endpoints related to authentication.', + [ApiTag.Deprecated]: 'Deprecated endpoints that are planned for removal in the next major release.', + [ApiTag.Download]: 'Endpoints for downloading assets or collections of assets.', + [ApiTag.Duplicates]: 'Endpoints for managing and identifying duplicate assets.', + [ApiTag.Faces]: + 'A face is a detected human face within an asset, which can be associated with a person. Faces are normally detected via machine learning, but can also be created via manually.', + [ApiTag.Jobs]: + 'Queues and background jobs are used for processing tasks asynchronously. Queues can be paused and resumed as needed.', + [ApiTag.Libraries]: + 'An external library is made up of input file paths or expressions that are scanned for asset files. Discovered files are automatically imported. Assets much be unique within a library, but can be duplicated across libraries. Each user has a default upload library, and can have one or more external libraries.', + [ApiTag.Map]: + 'Map endpoints include supplemental functionality related to geolocation, such as reverse geocoding and retrieving map markers for assets with geolocation data.', + [ApiTag.Memories]: + 'A memory is a specialized collection of assets with dedicated viewing implementations in the web and mobile clients. A memory includes fields related to visibility and are automatically generated per user via a background job.', + [ApiTag.Notifications]: + 'A notification is a specialized message sent to users to inform them of important events. Currently, these notifications are only shown in the Immich web application.', + [ApiTag.NotificationsAdmin]: 'Notification administrative endpoints.', + [ApiTag.Partners]: 'A partner is a link with another user that allows sharing of assets between two users.', + [ApiTag.People]: + 'A person is a collection of faces, which can be favorited and named. A person can also be merged into another person. People are automatically created via the face recognition job.', + [ApiTag.Plugins]: + 'A plugin is an installed module that makes filters and actions available for the workflow feature.', + [ApiTag.Search]: + 'Endpoints related to searching assets via text, smart search, optical character recognition (OCR), and other filters like person, album, and other metadata. Search endpoints usually support pagination and sorting.', + [ApiTag.Server]: + 'Information about the current server deployment, including version and build information, available features, supported media types, and more.', + [ApiTag.Sessions]: + 'A session represents an authenticated login session for a user. Sessions also appear in the web application as "Authorized devices".', + [ApiTag.SharedLinks]: + 'A shared link is a public url that provides access to a specific album, asset, or collection of assets. A shared link can be protected with a password, include a specific slug, allow or disallow downloads, and optionally include an expiration date.', + [ApiTag.Stacks]: + 'A stack is a group of related assets. One asset is the "primary" asset, and the rest are "child" assets. On the main timeline, stack parents are included by default, while child assets are hidden.', + [ApiTag.Sync]: 'A collection of endpoints for the new mobile synchronization implementation.', + [ApiTag.SystemConfig]: 'Endpoints to view, modify, and validate the system configuration settings.', + [ApiTag.SystemMetadata]: + 'Endpoints to view, modify, and validate the system metadata, which includes information about things like admin onboarding status.', + [ApiTag.Tags]: + 'A tag is a user-defined label that can be applied to assets for organizational purposes. Tags can also be hierarchical, allowing for parent-child relationships between tags.', + [ApiTag.Timeline]: + 'Specialized endpoints related to the timeline implementation used in the web application. External applications or tools should not use or rely on these endpoints, as they are subject to change without notice.', + [ApiTag.Trash]: + 'Endpoints for managing the trash can, which includes assets that have been discarded. Items in the trash are automatically deleted after a configured amount of time.', + [ApiTag.UsersAdmin]: + 'Administrative endpoints for managing users, including creating, updating, deleting, and restoring users. Also includes endpoints for resetting passwords and PIN codes.', + [ApiTag.Users]: + 'Endpoints for viewing and updating the current users, including product key information, profile picture data, onboarding progress, and more.', + [ApiTag.Views]: 'Endpoints for specialized views, such as the folder view.', + [ApiTag.Workflows]: + 'A workflow is a set of actions that run whenever a triggering event occurs. Workflows also can include filters to further limit execution.', +}; diff --git a/server/src/controllers/activity.controller.ts b/server/src/controllers/activity.controller.ts index 75b2e2f8a3..850e95510f 100644 --- a/server/src/controllers/activity.controller.ts +++ b/server/src/controllers/activity.controller.ts @@ -1,6 +1,7 @@ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Query, Res } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { Response } from 'express'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { ActivityCreateDto, ActivityDto, @@ -9,24 +10,35 @@ import { ActivityStatisticsResponseDto, } from 'src/dtos/activity.dto'; import { AuthDto } from 'src/dtos/auth.dto'; -import { Permission } from 'src/enum'; +import { ApiTag, Permission } from 'src/enum'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { ActivityService } from 'src/services/activity.service'; import { UUIDParamDto } from 'src/validation'; -@ApiTags('Activities') +@ApiTags(ApiTag.Activities) @Controller('activities') export class ActivityController { constructor(private service: ActivityService) {} @Get() @Authenticated({ permission: Permission.ActivityRead }) + @Endpoint({ + summary: 'List all activities', + description: + 'Returns a list of activities for the selected asset or album. The activities are returned in sorted order, with the oldest activities appearing first.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getActivities(@Auth() auth: AuthDto, @Query() dto: ActivitySearchDto): Promise { return this.service.getAll(auth, dto); } @Post() @Authenticated({ permission: Permission.ActivityCreate }) + @Endpoint({ + summary: 'Create an activity', + description: 'Create a like or a comment for an album, or an asset in an album.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) async createActivity( @Auth() auth: AuthDto, @Body() dto: ActivityCreateDto, @@ -41,6 +53,11 @@ export class ActivityController { @Get('statistics') @Authenticated({ permission: Permission.ActivityStatistics }) + @Endpoint({ + summary: 'Retrieve activity statistics', + description: 'Returns the number of likes and comments for a given album or asset in an album.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getActivityStatistics(@Auth() auth: AuthDto, @Query() dto: ActivityDto): Promise { return this.service.getStatistics(auth, dto); } @@ -48,6 +65,11 @@ export class ActivityController { @Delete(':id') @Authenticated({ permission: Permission.ActivityDelete }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Delete an activity', + description: 'Removes a like or comment from a given album or asset in an album.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) deleteActivity(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.delete(auth, id); } diff --git a/server/src/controllers/album.controller.ts b/server/src/controllers/album.controller.ts index 47f8b5603a..dad70257a7 100644 --- a/server/src/controllers/album.controller.ts +++ b/server/src/controllers/album.controller.ts @@ -1,5 +1,6 @@ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post, Put, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AddUsersDto, AlbumInfoDto, @@ -14,36 +15,56 @@ import { } from 'src/dtos/album.dto'; import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; -import { Permission } from 'src/enum'; +import { ApiTag, Permission } from 'src/enum'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { AlbumService } from 'src/services/album.service'; import { ParseMeUUIDPipe, UUIDParamDto } from 'src/validation'; -@ApiTags('Albums') +@ApiTags(ApiTag.Albums) @Controller('albums') export class AlbumController { constructor(private service: AlbumService) {} @Get() @Authenticated({ permission: Permission.AlbumRead }) + @Endpoint({ + summary: 'List all albums', + description: 'Retrieve a list of albums available to the authenticated user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getAllAlbums(@Auth() auth: AuthDto, @Query() query: GetAlbumsDto): Promise { return this.service.getAll(auth, query); } @Post() @Authenticated({ permission: Permission.AlbumCreate }) + @Endpoint({ + summary: 'Create an album', + description: 'Create a new album. The album can also be created with initial users and assets.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) createAlbum(@Auth() auth: AuthDto, @Body() dto: CreateAlbumDto): Promise { return this.service.create(auth, dto); } @Get('statistics') @Authenticated({ permission: Permission.AlbumStatistics }) + @Endpoint({ + summary: 'Retrieve album statistics', + description: 'Returns statistics about the albums available to the authenticated user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getAlbumStatistics(@Auth() auth: AuthDto): Promise { return this.service.getStatistics(auth); } @Authenticated({ permission: Permission.AlbumRead, sharedLink: true }) @Get(':id') + @Endpoint({ + summary: 'Retrieve an album', + description: 'Retrieve information about a specific album by its ID.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getAlbumInfo( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @@ -54,6 +75,12 @@ export class AlbumController { @Patch(':id') @Authenticated({ permission: Permission.AlbumUpdate }) + @Endpoint({ + summary: 'Update an album', + description: + 'Update the information of a specific album by its ID. This endpoint can be used to update the album name, description, sort order, etc. However, it is not used to add or remove assets or users from the album.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) updateAlbumInfo( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @@ -65,12 +92,23 @@ export class AlbumController { @Delete(':id') @Authenticated({ permission: Permission.AlbumDelete }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Delete an album', + description: + 'Delete a specific album by its ID. Note the album is initially trashed and then immediately scheduled for deletion, but relies on a background job to complete the process.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) deleteAlbum(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto) { return this.service.delete(auth, id); } @Put(':id/assets') @Authenticated({ permission: Permission.AlbumAssetCreate, sharedLink: true }) + @Endpoint({ + summary: 'Add assets to an album', + description: 'Add multiple assets to a specific album by its ID.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) addAssetsToAlbum( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @@ -81,12 +119,22 @@ export class AlbumController { @Put('assets') @Authenticated({ permission: Permission.AlbumAssetCreate, sharedLink: true }) + @Endpoint({ + summary: 'Add assets to albums', + description: 'Send a list of asset IDs and album IDs to add each asset to each album.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) addAssetsToAlbums(@Auth() auth: AuthDto, @Body() dto: AlbumsAddAssetsDto): Promise { return this.service.addAssetsToAlbums(auth, dto); } @Delete(':id/assets') @Authenticated({ permission: Permission.AlbumAssetDelete }) + @Endpoint({ + summary: 'Remove assets from an album', + description: 'Remove multiple assets from a specific album by its ID.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) removeAssetFromAlbum( @Auth() auth: AuthDto, @Body() dto: BulkIdsDto, @@ -97,6 +145,11 @@ export class AlbumController { @Put(':id/users') @Authenticated({ permission: Permission.AlbumUserCreate }) + @Endpoint({ + summary: 'Share album with users', + description: 'Share an album with multiple users. Each user can be given a specific role in the album.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) addUsersToAlbum( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @@ -108,6 +161,11 @@ export class AlbumController { @Put(':id/user/:userId') @Authenticated({ permission: Permission.AlbumUserUpdate }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Update user role', + description: 'Change the role for a specific user in a specific album.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) updateAlbumUser( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @@ -120,6 +178,11 @@ export class AlbumController { @Delete(':id/user/:userId') @Authenticated({ permission: Permission.AlbumUserDelete }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Remove user from album', + description: 'Remove a user from an album. Use an ID of "me" to leave a shared album.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) removeUserFromAlbum( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, diff --git a/server/src/controllers/api-key.controller.ts b/server/src/controllers/api-key.controller.ts index 59b6908128..61ad203331 100644 --- a/server/src/controllers/api-key.controller.ts +++ b/server/src/controllers/api-key.controller.ts @@ -1,43 +1,69 @@ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { APIKeyCreateDto, APIKeyCreateResponseDto, APIKeyResponseDto, APIKeyUpdateDto } from 'src/dtos/api-key.dto'; import { AuthDto } from 'src/dtos/auth.dto'; -import { Permission } from 'src/enum'; +import { ApiTag, Permission } from 'src/enum'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { ApiKeyService } from 'src/services/api-key.service'; import { UUIDParamDto } from 'src/validation'; -@ApiTags('API Keys') +@ApiTags(ApiTag.ApiKeys) @Controller('api-keys') export class ApiKeyController { constructor(private service: ApiKeyService) {} @Post() @Authenticated({ permission: Permission.ApiKeyCreate }) + @Endpoint({ + summary: 'Create an API key', + description: 'Creates a new API key. It will be limited to the permissions specified.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) createApiKey(@Auth() auth: AuthDto, @Body() dto: APIKeyCreateDto): Promise { return this.service.create(auth, dto); } @Get() @Authenticated({ permission: Permission.ApiKeyRead }) + @Endpoint({ + summary: 'List all API keys', + description: 'Retrieve all API keys of the current user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getApiKeys(@Auth() auth: AuthDto): Promise { return this.service.getAll(auth); } @Get('me') @Authenticated({ permission: false }) + @Endpoint({ + summary: 'Retrieve the current API key', + description: 'Retrieve the API key that is used to access this endpoint.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) async getMyApiKey(@Auth() auth: AuthDto): Promise { return this.service.getMine(auth); } @Get(':id') @Authenticated({ permission: Permission.ApiKeyRead }) + @Endpoint({ + summary: 'Retrieve an API key', + description: 'Retrieve an API key by its ID. The current user must own this API key.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getApiKey(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.getById(auth, id); } @Put(':id') @Authenticated({ permission: Permission.ApiKeyUpdate }) + @Endpoint({ + summary: 'Update an API key', + description: 'Updates the name and permissions of an API key by its ID. The current user must own this API key.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) updateApiKey( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @@ -49,6 +75,11 @@ export class ApiKeyController { @Delete(':id') @Authenticated({ permission: Permission.ApiKeyDelete }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Delete an API key', + description: 'Deletes an API key identified by its ID. The current user must own this API key.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) deleteApiKey(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.delete(auth, id); } diff --git a/server/src/controllers/asset-media.controller.ts b/server/src/controllers/asset-media.controller.ts index 688e513b64..843c2a3f3d 100644 --- a/server/src/controllers/asset-media.controller.ts +++ b/server/src/controllers/asset-media.controller.ts @@ -15,9 +15,9 @@ import { UploadedFiles, UseInterceptors, } from '@nestjs/common'; -import { ApiBody, ApiConsumes, ApiHeader, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { ApiBody, ApiConsumes, ApiHeader, ApiTags } from '@nestjs/swagger'; import { NextFunction, Request, Response } from 'express'; -import { EndpointLifecycle } from 'src/decorators'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AssetBulkUploadCheckResponseDto, AssetMediaResponseDto, @@ -34,7 +34,7 @@ import { UploadFieldName, } from 'src/dtos/asset-media.dto'; import { AuthDto } from 'src/dtos/auth.dto'; -import { ImmichHeader, Permission, RouteKey } from 'src/enum'; +import { ApiTag, ImmichHeader, Permission, RouteKey } from 'src/enum'; import { AssetUploadInterceptor } from 'src/middleware/asset-upload.interceptor'; import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard'; import { FileUploadInterceptor, getFiles } from 'src/middleware/file-upload.interceptor'; @@ -44,7 +44,7 @@ import { UploadFiles } from 'src/types'; import { ImmichFileResponse, sendFile } from 'src/utils/file'; import { FileNotEmptyValidator, UUIDParamDto } from 'src/validation'; -@ApiTags('Assets') +@ApiTags(ApiTag.Assets) @Controller(RouteKey.Asset) export class AssetMediaController { constructor( @@ -53,6 +53,7 @@ export class AssetMediaController { ) {} @Post() + @Authenticated({ permission: Permission.AssetUpload, sharedLink: true }) @UseInterceptors(AssetUploadInterceptor, FileUploadInterceptor) @ApiConsumes('multipart/form-data') @ApiHeader({ @@ -61,7 +62,11 @@ export class AssetMediaController { required: false, }) @ApiBody({ description: 'Asset Upload Information', type: AssetMediaCreateDto }) - @Authenticated({ permission: Permission.AssetUpload, sharedLink: true }) + @Endpoint({ + summary: 'Upload asset', + description: 'Uploads a new asset to the server.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) async uploadAsset( @Auth() auth: AuthDto, @UploadedFiles(new ParseFilePipe({ validators: [new FileNotEmptyValidator(['assetData'])] })) files: UploadFiles, @@ -81,6 +86,11 @@ export class AssetMediaController { @Get(':id/original') @FileResponse() @Authenticated({ permission: Permission.AssetDownload, sharedLink: true }) + @Endpoint({ + summary: 'Download original asset', + description: 'Downloads the original file of the specified asset.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) async downloadAsset( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @@ -90,17 +100,13 @@ export class AssetMediaController { await sendFile(res, next, () => this.service.downloadOriginal(auth, id), this.logger); } - /** - * Replace the asset with new file, without changing its id - */ @Put(':id/original') @UseInterceptors(FileUploadInterceptor) @ApiConsumes('multipart/form-data') - @EndpointLifecycle({ - addedAt: 'v1.106.0', - deprecatedAt: 'v1.142.0', - summary: 'replaceAsset', - description: 'Replace the asset with new file, without changing its id', + @Endpoint({ + summary: 'Replace asset', + description: 'Replace the asset with new file, without changing its id.', + history: new HistoryBuilder().added('v1').deprecated('v1', { replacementId: 'copyAsset' }), }) @Authenticated({ permission: Permission.AssetReplace, sharedLink: true }) async replaceAsset( @@ -122,6 +128,11 @@ export class AssetMediaController { @Get(':id/thumbnail') @FileResponse() @Authenticated({ permission: Permission.AssetView, sharedLink: true }) + @Endpoint({ + summary: 'View asset thumbnail', + description: 'Retrieve the thumbnail image for the specified asset.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) async viewAsset( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @@ -159,6 +170,11 @@ export class AssetMediaController { @Get(':id/video/playback') @FileResponse() @Authenticated({ permission: Permission.AssetView, sharedLink: true }) + @Endpoint({ + summary: 'Play asset video', + description: 'Streams the video file for the specified asset. This endpoint also supports byte range requests.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) async playAssetVideo( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @@ -168,14 +184,12 @@ export class AssetMediaController { await sendFile(res, next, () => this.service.playbackVideo(auth, id), this.logger); } - /** - * Checks if multiple assets exist on the server and returns all existing - used by background backup - */ @Post('exist') @Authenticated() - @ApiOperation({ - summary: 'checkExistingAssets', + @Endpoint({ + summary: 'Check existing assets', description: 'Checks if multiple assets exist on the server and returns all existing - used by background backup', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), }) @HttpCode(HttpStatus.OK) checkExistingAssets( @@ -185,14 +199,12 @@ export class AssetMediaController { return this.service.checkExistingAssets(auth, dto); } - /** - * Checks if assets exist by checksums - */ @Post('bulk-upload-check') @Authenticated({ permission: Permission.AssetUpload }) - @ApiOperation({ - summary: 'checkBulkUpload', - description: 'Checks if assets exist by checksums', + @Endpoint({ + summary: 'Check bulk upload', + description: 'Determine which assets have already been uploaded to the server based on their SHA1 checksums.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), }) @HttpCode(HttpStatus.OK) checkBulkUpload( diff --git a/server/src/controllers/asset.controller.spec.ts b/server/src/controllers/asset.controller.spec.ts index 7a7a37fe2e..649c80e850 100644 --- a/server/src/controllers/asset.controller.spec.ts +++ b/server/src/controllers/asset.controller.spec.ts @@ -57,6 +57,28 @@ describe(AssetController.name, () => { }); }); + describe('PUT /assets/copy', () => { + it('should be an authenticated route', async () => { + await request(ctx.getHttpServer()).get(`/assets/copy`); + expect(ctx.authenticate).toHaveBeenCalled(); + }); + + it('should require target and source id', async () => { + const { status, body } = await request(ctx.getHttpServer()).put('/assets/copy').send({}); + expect(status).toBe(400); + expect(body).toEqual( + factory.responses.badRequest(expect.arrayContaining(['sourceId must be a UUID', 'targetId must be a UUID'])), + ); + }); + + it('should work', async () => { + const { status } = await request(ctx.getHttpServer()) + .put('/assets/copy') + .send({ sourceId: factory.uuid(), targetId: factory.uuid() }); + expect(status).toBe(204); + }); + }); + describe('PUT /assets/:id', () => { it('should be an authenticated route', async () => { await request(ctx.getHttpServer()).get(`/assets/123`); diff --git a/server/src/controllers/asset.controller.ts b/server/src/controllers/asset.controller.ts index 1f320f6595..bcc13fbc06 100644 --- a/server/src/controllers/asset.controller.ts +++ b/server/src/controllers/asset.controller.ts @@ -1,10 +1,11 @@ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common'; -import { ApiOperation, ApiTags } from '@nestjs/swagger'; -import { EndpointLifecycle } from 'src/decorators'; +import { ApiTags } from '@nestjs/swagger'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AssetResponseDto } from 'src/dtos/asset-response.dto'; import { AssetBulkDeleteDto, AssetBulkUpdateDto, + AssetCopyDto, AssetJobsDto, AssetMetadataResponseDto, AssetMetadataRouteParams, @@ -16,30 +17,33 @@ import { UpdateAssetDto, } from 'src/dtos/asset.dto'; import { AuthDto } from 'src/dtos/auth.dto'; -import { Permission, RouteKey } from 'src/enum'; +import { AssetOcrResponseDto } from 'src/dtos/ocr.dto'; +import { ApiTag, Permission, RouteKey } from 'src/enum'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { AssetService } from 'src/services/asset.service'; import { UUIDParamDto } from 'src/validation'; -@ApiTags('Assets') +@ApiTags(ApiTag.Assets) @Controller(RouteKey.Asset) export class AssetController { constructor(private service: AssetService) {} @Get('random') @Authenticated({ permission: Permission.AssetRead }) - @EndpointLifecycle({ deprecatedAt: 'v1.116.0' }) + @Endpoint({ + summary: 'Get random assets', + description: 'Retrieve a specified number of random assets for the authenticated user.', + history: new HistoryBuilder().added('v1').deprecated('v1', { replacementId: 'searchAssets' }), + }) getRandom(@Auth() auth: AuthDto, @Query() dto: RandomAssetsDto): Promise { return this.service.getRandom(auth, dto.count ?? 1); } - /** - * Get all asset of a device that are in the database, ID only. - */ @Get('/device/:deviceId') - @ApiOperation({ - summary: 'getAllUserAssetsByDeviceId', + @Endpoint({ + summary: 'Retrieve assets by device ID', description: 'Get all asset of a device that are in the database, ID only.', + history: new HistoryBuilder().added('v1').deprecated('v2'), }) @Authenticated() getAllUserAssetsByDeviceId(@Auth() auth: AuthDto, @Param() { deviceId }: DeviceIdDto) { @@ -48,6 +52,11 @@ export class AssetController { @Get('statistics') @Authenticated({ permission: Permission.AssetStatistics }) + @Endpoint({ + summary: 'Get asset statistics', + description: 'Retrieve various statistics about the assets owned by the authenticated user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getAssetStatistics(@Auth() auth: AuthDto, @Query() dto: AssetStatsDto): Promise { return this.service.getStatistics(auth, dto); } @@ -55,6 +64,11 @@ export class AssetController { @Post('jobs') @Authenticated() @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Run an asset job', + description: 'Run a specific job on a set of assets.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) runAssetJobs(@Auth() auth: AuthDto, @Body() dto: AssetJobsDto): Promise { return this.service.run(auth, dto); } @@ -62,6 +76,11 @@ export class AssetController { @Put() @Authenticated({ permission: Permission.AssetUpdate }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Update assets', + description: 'Updates multiple assets at the same time.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) updateAssets(@Auth() auth: AuthDto, @Body() dto: AssetBulkUpdateDto): Promise { return this.service.updateAll(auth, dto); } @@ -69,18 +88,45 @@ export class AssetController { @Delete() @Authenticated({ permission: Permission.AssetDelete }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Delete assets', + description: 'Deletes multiple assets at the same time.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) deleteAssets(@Auth() auth: AuthDto, @Body() dto: AssetBulkDeleteDto): Promise { return this.service.deleteAll(auth, dto); } @Get(':id') @Authenticated({ permission: Permission.AssetRead, sharedLink: true }) + @Endpoint({ + summary: 'Retrieve an asset', + description: 'Retrieve detailed information about a specific asset.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getAssetInfo(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.get(auth, id) as Promise; } + @Put('copy') + @Authenticated({ permission: Permission.AssetCopy }) + @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Copy asset', + description: 'Copy asset information like albums, tags, etc. from one asset to another.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) + copyAsset(@Auth() auth: AuthDto, @Body() dto: AssetCopyDto): Promise { + return this.service.copy(auth, dto); + } + @Put(':id') @Authenticated({ permission: Permission.AssetUpdate }) + @Endpoint({ + summary: 'Update an asset', + description: 'Update information of a specific asset.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) updateAsset( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @@ -91,12 +137,33 @@ export class AssetController { @Get(':id/metadata') @Authenticated({ permission: Permission.AssetRead }) + @Endpoint({ + summary: 'Get asset metadata', + description: 'Retrieve all metadata key-value pairs associated with the specified asset.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getAssetMetadata(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.getMetadata(auth, id); } + @Get(':id/ocr') + @Authenticated({ permission: Permission.AssetRead }) + @Endpoint({ + summary: 'Retrieve asset OCR data', + description: 'Retrieve all OCR (Optical Character Recognition) data associated with the specified asset.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) + getAssetOcr(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { + return this.service.getOcr(auth, id); + } + @Put(':id/metadata') @Authenticated({ permission: Permission.AssetUpdate }) + @Endpoint({ + summary: 'Update asset metadata', + description: 'Update or add metadata key-value pairs for the specified asset.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) updateAssetMetadata( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @@ -107,6 +174,11 @@ export class AssetController { @Get(':id/metadata/:key') @Authenticated({ permission: Permission.AssetRead }) + @Endpoint({ + summary: 'Retrieve asset metadata by key', + description: 'Retrieve the value of a specific metadata key associated with the specified asset.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getAssetMetadataByKey( @Auth() auth: AuthDto, @Param() { id, key }: AssetMetadataRouteParams, @@ -117,6 +189,11 @@ export class AssetController { @Delete(':id/metadata/:key') @Authenticated({ permission: Permission.AssetUpdate }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Delete asset metadata by key', + description: 'Delete a specific metadata key-value pair associated with the specified asset.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) deleteAssetMetadata(@Auth() auth: AuthDto, @Param() { id, key }: AssetMetadataRouteParams): Promise { return this.service.deleteMetadataByKey(auth, id, key); } diff --git a/server/src/controllers/auth-admin.controller.ts b/server/src/controllers/auth-admin.controller.ts index dba352783e..d4cada9afc 100644 --- a/server/src/controllers/auth-admin.controller.ts +++ b/server/src/controllers/auth-admin.controller.ts @@ -1,17 +1,23 @@ import { Controller, HttpCode, HttpStatus, Post } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AuthDto } from 'src/dtos/auth.dto'; -import { Permission } from 'src/enum'; +import { ApiTag, Permission } from 'src/enum'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { AuthAdminService } from 'src/services/auth-admin.service'; -@ApiTags('Auth (admin)') +@ApiTags(ApiTag.AuthenticationAdmin) @Controller('admin/auth') export class AuthAdminController { constructor(private service: AuthAdminService) {} @Post('unlink-all') @Authenticated({ permission: Permission.AdminAuthUnlinkAll, admin: true }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Unlink all OAuth accounts', + description: 'Unlinks all OAuth accounts associated with user accounts in the system.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) unlinkAllOAuthAccountsAdmin(@Auth() auth: AuthDto): Promise { return this.service.unlinkAll(auth); } diff --git a/server/src/controllers/auth.controller.spec.ts b/server/src/controllers/auth.controller.spec.ts index 031ef460c2..7dd145ff5c 100644 --- a/server/src/controllers/auth.controller.spec.ts +++ b/server/src/controllers/auth.controller.spec.ts @@ -183,7 +183,7 @@ describe(AuthController.name, () => { it('should be an authenticated route', async () => { await request(ctx.getHttpServer()) .post('/auth/change-password') - .send({ password: 'password', newPassword: 'Password1234' }); + .send({ password: 'password', newPassword: 'Password1234', invalidateSessions: false }); expect(ctx.authenticate).toHaveBeenCalled(); }); }); diff --git a/server/src/controllers/auth.controller.ts b/server/src/controllers/auth.controller.ts index 636e3a3047..ea09e33080 100644 --- a/server/src/controllers/auth.controller.ts +++ b/server/src/controllers/auth.controller.ts @@ -1,6 +1,7 @@ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Post, Put, Req, Res } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { Request, Response } from 'express'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AuthDto, AuthStatusResponseDto, @@ -16,17 +17,22 @@ import { ValidateAccessTokenResponseDto, } from 'src/dtos/auth.dto'; import { UserAdminResponseDto } from 'src/dtos/user.dto'; -import { AuthType, ImmichCookie, Permission } from 'src/enum'; +import { ApiTag, AuthType, ImmichCookie, Permission } from 'src/enum'; import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard'; import { AuthService, LoginDetails } from 'src/services/auth.service'; import { respondWithCookie, respondWithoutCookie } from 'src/utils/response'; -@ApiTags('Authentication') +@ApiTags(ApiTag.Authentication) @Controller('auth') export class AuthController { constructor(private service: AuthService) {} @Post('login') + @Endpoint({ + summary: 'Login', + description: 'Login with username and password and receive a session token.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) async login( @Res({ passthrough: true }) res: Response, @Body() loginCredential: LoginCredentialDto, @@ -44,11 +50,21 @@ export class AuthController { } @Post('admin-sign-up') + @Endpoint({ + summary: 'Register admin', + description: 'Create the first admin user in the system.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) signUpAdmin(@Body() dto: SignUpDto): Promise { return this.service.adminSignUp(dto); } @Post('validateToken') + @Endpoint({ + summary: 'Validate access token', + description: 'Validate the current authorization method is still valid.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) @Authenticated({ permission: false }) @HttpCode(HttpStatus.OK) validateAccessToken(): ValidateAccessTokenResponseDto { @@ -58,6 +74,11 @@ export class AuthController { @Post('change-password') @Authenticated({ permission: Permission.AuthChangePassword }) @HttpCode(HttpStatus.OK) + @Endpoint({ + summary: 'Change password', + description: 'Change the password of the current user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) changePassword(@Auth() auth: AuthDto, @Body() dto: ChangePasswordDto): Promise { return this.service.changePassword(auth, dto); } @@ -65,6 +86,11 @@ export class AuthController { @Post('logout') @Authenticated() @HttpCode(HttpStatus.OK) + @Endpoint({ + summary: 'Logout', + description: 'Logout the current user and invalidate the session token.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) async logout( @Req() request: Request, @Res({ passthrough: true }) res: Response, @@ -82,6 +108,11 @@ export class AuthController { @Get('status') @Authenticated() + @Endpoint({ + summary: 'Retrieve auth status', + description: + 'Get information about the current session, including whether the user has a password, and if the session can access locked assets.', + }) getAuthStatus(@Auth() auth: AuthDto): Promise { return this.service.getAuthStatus(auth); } @@ -89,6 +120,11 @@ export class AuthController { @Post('pin-code') @Authenticated({ permission: Permission.PinCodeCreate }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Setup pin code', + description: 'Setup a new pin code for the current user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) setupPinCode(@Auth() auth: AuthDto, @Body() dto: PinCodeSetupDto): Promise { return this.service.setupPinCode(auth, dto); } @@ -96,6 +132,11 @@ export class AuthController { @Put('pin-code') @Authenticated({ permission: Permission.PinCodeUpdate }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Change pin code', + description: 'Change the pin code for the current user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) async changePinCode(@Auth() auth: AuthDto, @Body() dto: PinCodeChangeDto): Promise { return this.service.changePinCode(auth, dto); } @@ -103,6 +144,11 @@ export class AuthController { @Delete('pin-code') @Authenticated({ permission: Permission.PinCodeDelete }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Reset pin code', + description: 'Reset the pin code for the current user by providing the account password', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) async resetPinCode(@Auth() auth: AuthDto, @Body() dto: PinCodeResetDto): Promise { return this.service.resetPinCode(auth, dto); } @@ -110,12 +156,22 @@ export class AuthController { @Post('session/unlock') @Authenticated() @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Unlock auth session', + description: 'Temporarily grant the session elevated access to locked assets by providing the correct PIN code.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) async unlockAuthSession(@Auth() auth: AuthDto, @Body() dto: SessionUnlockDto): Promise { return this.service.unlockSession(auth, dto); } @Post('session/lock') @Authenticated() + @Endpoint({ + summary: 'Lock auth session', + description: 'Remove elevated access to locked assets from the current session.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) @HttpCode(HttpStatus.NO_CONTENT) async lockAuthSession(@Auth() auth: AuthDto): Promise { return this.service.lockSession(auth); diff --git a/server/src/controllers/download.controller.ts b/server/src/controllers/download.controller.ts index a7c2af78ed..942d44f4c3 100644 --- a/server/src/controllers/download.controller.ts +++ b/server/src/controllers/download.controller.ts @@ -1,20 +1,27 @@ import { Body, Controller, HttpCode, HttpStatus, Post, StreamableFile } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AssetIdsDto } from 'src/dtos/asset.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { DownloadInfoDto, DownloadResponseDto } from 'src/dtos/download.dto'; -import { Permission } from 'src/enum'; +import { ApiTag, Permission } from 'src/enum'; import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard'; import { DownloadService } from 'src/services/download.service'; import { asStreamableFile } from 'src/utils/file'; -@ApiTags('Download') +@ApiTags(ApiTag.Download) @Controller('download') export class DownloadController { constructor(private service: DownloadService) {} @Post('info') @Authenticated({ permission: Permission.AssetDownload, sharedLink: true }) + @Endpoint({ + summary: 'Retrieve download information', + description: + 'Retrieve information about how to request a download for the specified assets or album. The response includes groups of assets that can be downloaded together.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getDownloadInfo(@Auth() auth: AuthDto, @Body() dto: DownloadInfoDto): Promise { return this.service.getDownloadInfo(auth, dto); } @@ -23,6 +30,12 @@ export class DownloadController { @Authenticated({ permission: Permission.AssetDownload, sharedLink: true }) @FileResponse() @HttpCode(HttpStatus.OK) + @Endpoint({ + summary: 'Download asset archive', + description: + 'Download a ZIP archive containing the specified assets. The assets must have been previously requested via the "getDownloadInfo" endpoint.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) downloadArchive(@Auth() auth: AuthDto, @Body() dto: AssetIdsDto): Promise { return this.service.downloadArchive(auth, dto).then(asStreamableFile); } diff --git a/server/src/controllers/duplicate.controller.ts b/server/src/controllers/duplicate.controller.ts index 9cf5ae97a6..e8c8e5ef80 100644 --- a/server/src/controllers/duplicate.controller.ts +++ b/server/src/controllers/duplicate.controller.ts @@ -1,20 +1,26 @@ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { DuplicateResponseDto } from 'src/dtos/duplicate.dto'; -import { Permission } from 'src/enum'; +import { ApiTag, Permission } from 'src/enum'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { DuplicateService } from 'src/services/duplicate.service'; import { UUIDParamDto } from 'src/validation'; -@ApiTags('Duplicates') +@ApiTags(ApiTag.Duplicates) @Controller('duplicates') export class DuplicateController { constructor(private service: DuplicateService) {} @Get() @Authenticated({ permission: Permission.DuplicateRead }) + @Endpoint({ + summary: 'Retrieve duplicates', + description: 'Retrieve a list of duplicate assets available to the authenticated user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getAssetDuplicates(@Auth() auth: AuthDto): Promise { return this.service.getDuplicates(auth); } @@ -22,6 +28,11 @@ export class DuplicateController { @Delete() @Authenticated({ permission: Permission.DuplicateDelete }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Delete duplicates', + description: 'Delete multiple duplicate assets specified by their IDs.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) deleteDuplicates(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise { return this.service.deleteAll(auth, dto); } @@ -29,6 +40,11 @@ export class DuplicateController { @Delete(':id') @Authenticated({ permission: Permission.DuplicateDelete }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Delete a duplicate', + description: 'Delete a single duplicate asset specified by its ID.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) deleteDuplicate(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.delete(auth, id); } diff --git a/server/src/controllers/face.controller.ts b/server/src/controllers/face.controller.ts index 564b217c16..a1c1d6ee4d 100644 --- a/server/src/controllers/face.controller.ts +++ b/server/src/controllers/face.controller.ts @@ -1,5 +1,6 @@ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AuthDto } from 'src/dtos/auth.dto'; import { AssetFaceCreateDto, @@ -8,30 +9,46 @@ import { FaceDto, PersonResponseDto, } from 'src/dtos/person.dto'; -import { Permission } from 'src/enum'; +import { ApiTag, Permission } from 'src/enum'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { PersonService } from 'src/services/person.service'; import { UUIDParamDto } from 'src/validation'; -@ApiTags('Faces') +@ApiTags(ApiTag.Faces) @Controller('faces') export class FaceController { constructor(private service: PersonService) {} @Post() @Authenticated({ permission: Permission.FaceCreate }) + @Endpoint({ + summary: 'Create a face', + description: + 'Create a new face that has not been discovered by facial recognition. The content of the bounding box is considered a face.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) createFace(@Auth() auth: AuthDto, @Body() dto: AssetFaceCreateDto) { return this.service.createFace(auth, dto); } @Get() @Authenticated({ permission: Permission.FaceRead }) + @Endpoint({ + summary: 'Retrieve faces for asset', + description: 'Retrieve all faces belonging to an asset.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getFaces(@Auth() auth: AuthDto, @Query() dto: FaceDto): Promise { return this.service.getFacesById(auth, dto); } @Put(':id') @Authenticated({ permission: Permission.FaceUpdate }) + @Endpoint({ + summary: 'Re-assign a face to another person', + description: 'Re-assign the face provided in the body to the person identified by the id in the path parameter.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) reassignFacesById( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @@ -43,6 +60,11 @@ export class FaceController { @Delete(':id') @Authenticated({ permission: Permission.FaceDelete }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Delete a face', + description: 'Delete a face identified by the id. Optionally can be force deleted.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) deleteFace(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @Body() dto: AssetFaceDeleteDto): Promise { return this.service.deleteFace(auth, id, dto); } diff --git a/server/src/controllers/index.ts b/server/src/controllers/index.ts index e3661ec794..c0c0461fb3 100644 --- a/server/src/controllers/index.ts +++ b/server/src/controllers/index.ts @@ -18,6 +18,7 @@ import { NotificationController } from 'src/controllers/notification.controller' import { OAuthController } from 'src/controllers/oauth.controller'; import { PartnerController } from 'src/controllers/partner.controller'; import { PersonController } from 'src/controllers/person.controller'; +import { PluginController } from 'src/controllers/plugin.controller'; import { SearchController } from 'src/controllers/search.controller'; import { ServerController } from 'src/controllers/server.controller'; import { SessionController } from 'src/controllers/session.controller'; @@ -32,6 +33,7 @@ import { TrashController } from 'src/controllers/trash.controller'; import { UserAdminController } from 'src/controllers/user-admin.controller'; import { UserController } from 'src/controllers/user.controller'; import { ViewController } from 'src/controllers/view.controller'; +import { WorkflowController } from 'src/controllers/workflow.controller'; export const controllers = [ ApiKeyController, @@ -54,6 +56,7 @@ export const controllers = [ OAuthController, PartnerController, PersonController, + PluginController, SearchController, ServerController, SessionController, @@ -68,4 +71,5 @@ export const controllers = [ UserAdminController, UserController, ViewController, + WorkflowController, ]; diff --git a/server/src/controllers/job.controller.ts b/server/src/controllers/job.controller.ts index 9c4e819649..977f1e0f1e 100644 --- a/server/src/controllers/job.controller.ts +++ b/server/src/controllers/job.controller.ts @@ -1,31 +1,54 @@ import { Body, Controller, Get, HttpCode, HttpStatus, Param, Post, Put } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { AllJobStatusResponseDto, JobCommandDto, JobCreateDto, JobIdParamDto, JobStatusDto } from 'src/dtos/job.dto'; -import { Permission } from 'src/enum'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; +import { JobCreateDto } from 'src/dtos/job.dto'; +import { QueueCommandDto, QueueNameParamDto, QueueResponseDto, QueuesResponseDto } from 'src/dtos/queue.dto'; +import { ApiTag, Permission } from 'src/enum'; import { Authenticated } from 'src/middleware/auth.guard'; import { JobService } from 'src/services/job.service'; +import { QueueService } from 'src/services/queue.service'; -@ApiTags('Jobs') +@ApiTags(ApiTag.Jobs) @Controller('jobs') export class JobController { - constructor(private service: JobService) {} + constructor( + private service: JobService, + private queueService: QueueService, + ) {} @Get() @Authenticated({ permission: Permission.JobRead, admin: true }) - getAllJobsStatus(): Promise { - return this.service.getAllJobsStatus(); + @Endpoint({ + summary: 'Retrieve queue counts and status', + description: 'Retrieve the counts of the current queue, as well as the current status.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) + getQueuesLegacy(): Promise { + return this.queueService.getAll(); } @Post() @Authenticated({ permission: Permission.JobCreate, admin: true }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Create a manual job', + description: + 'Run a specific job. Most jobs are queued automatically, but this endpoint allows for manual creation of a handful of jobs, including various cleanup tasks, as well as creating a new database backup.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) createJob(@Body() dto: JobCreateDto): Promise { return this.service.create(dto); } - @Put(':id') + @Put(':name') @Authenticated({ permission: Permission.JobCreate, admin: true }) - sendJobCommand(@Param() { id }: JobIdParamDto, @Body() dto: JobCommandDto): Promise { - return this.service.handleCommand(id, dto); + @Endpoint({ + summary: 'Run jobs', + description: + 'Queue all assets for a specific job type. Defaults to only queueing assets that have not yet been processed, but the force command can be used to re-process all assets.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) + runQueueCommandLegacy(@Param() { name }: QueueNameParamDto, @Body() dto: QueueCommandDto): Promise { + return this.queueService.runCommand(name, dto); } } diff --git a/server/src/controllers/library.controller.ts b/server/src/controllers/library.controller.ts index b37bc40ce7..5672e9117a 100644 --- a/server/src/controllers/library.controller.ts +++ b/server/src/controllers/library.controller.ts @@ -1,5 +1,6 @@ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { CreateLibraryDto, LibraryResponseDto, @@ -8,36 +9,56 @@ import { ValidateLibraryDto, ValidateLibraryResponseDto, } from 'src/dtos/library.dto'; -import { Permission } from 'src/enum'; +import { ApiTag, Permission } from 'src/enum'; import { Authenticated } from 'src/middleware/auth.guard'; import { LibraryService } from 'src/services/library.service'; import { UUIDParamDto } from 'src/validation'; -@ApiTags('Libraries') +@ApiTags(ApiTag.Libraries) @Controller('libraries') export class LibraryController { constructor(private service: LibraryService) {} @Get() @Authenticated({ permission: Permission.LibraryRead, admin: true }) + @Endpoint({ + summary: 'Retrieve libraries', + description: 'Retrieve a list of external libraries.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getAllLibraries(): Promise { return this.service.getAll(); } @Post() @Authenticated({ permission: Permission.LibraryCreate, admin: true }) + @Endpoint({ + summary: 'Create a library', + description: 'Create a new external library.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) createLibrary(@Body() dto: CreateLibraryDto): Promise { return this.service.create(dto); } @Get(':id') @Authenticated({ permission: Permission.LibraryRead, admin: true }) + @Endpoint({ + summary: 'Retrieve a library', + description: 'Retrieve an external library by its ID.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getLibrary(@Param() { id }: UUIDParamDto): Promise { return this.service.get(id); } @Put(':id') @Authenticated({ permission: Permission.LibraryUpdate, admin: true }) + @Endpoint({ + summary: 'Update a library', + description: 'Update an existing external library.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) updateLibrary(@Param() { id }: UUIDParamDto, @Body() dto: UpdateLibraryDto): Promise { return this.service.update(id, dto); } @@ -45,6 +66,11 @@ export class LibraryController { @Delete(':id') @Authenticated({ permission: Permission.LibraryDelete, admin: true }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Delete a library', + description: 'Delete an external library by its ID.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) deleteLibrary(@Param() { id }: UUIDParamDto): Promise { return this.service.delete(id); } @@ -52,6 +78,11 @@ export class LibraryController { @Post(':id/validate') @Authenticated({ admin: true }) @HttpCode(HttpStatus.OK) + @Endpoint({ + summary: 'Validate library settings', + description: 'Validate the settings of an external library.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) // TODO: change endpoint to validate current settings instead validate(@Param() { id }: UUIDParamDto, @Body() dto: ValidateLibraryDto): Promise { return this.service.validate(id, dto); @@ -59,6 +90,12 @@ export class LibraryController { @Get(':id/statistics') @Authenticated({ permission: Permission.LibraryStatistics, admin: true }) + @Endpoint({ + summary: 'Retrieve library statistics', + description: + 'Retrieve statistics for a specific external library, including number of videos, images, and storage usage.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getLibraryStatistics(@Param() { id }: UUIDParamDto): Promise { return this.service.getStatistics(id); } @@ -66,6 +103,11 @@ export class LibraryController { @Post(':id/scan') @Authenticated({ permission: Permission.LibraryUpdate, admin: true }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Scan a library', + description: 'Queue a scan for the external library to find and import new assets.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) scanLibrary(@Param() { id }: UUIDParamDto): Promise { return this.service.queueScan(id); } diff --git a/server/src/controllers/map.controller.ts b/server/src/controllers/map.controller.ts index 88104e6b58..dbd1082561 100644 --- a/server/src/controllers/map.controller.ts +++ b/server/src/controllers/map.controller.ts @@ -1,5 +1,6 @@ import { Controller, Get, HttpCode, HttpStatus, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AuthDto } from 'src/dtos/auth.dto'; import { MapMarkerDto, @@ -7,16 +8,22 @@ import { MapReverseGeocodeDto, MapReverseGeocodeResponseDto, } from 'src/dtos/map.dto'; +import { ApiTag } from 'src/enum'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { MapService } from 'src/services/map.service'; -@ApiTags('Map') +@ApiTags(ApiTag.Map) @Controller('map') export class MapController { constructor(private service: MapService) {} @Get('markers') @Authenticated() + @Endpoint({ + summary: 'Retrieve map markers', + description: 'Retrieve a list of latitude and longitude coordinates for every asset with location data.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getMapMarkers(@Auth() auth: AuthDto, @Query() options: MapMarkerDto): Promise { return this.service.getMapMarkers(auth, options); } @@ -24,6 +31,11 @@ export class MapController { @Authenticated() @Get('reverse-geocode') @HttpCode(HttpStatus.OK) + @Endpoint({ + summary: 'Reverse geocode coordinates', + description: 'Retrieve location information (e.g., city, country) for given latitude and longitude coordinates.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) reverseGeocode(@Query() dto: MapReverseGeocodeDto): Promise { return this.service.reverseGeocode(dto); } diff --git a/server/src/controllers/memory.controller.spec.ts b/server/src/controllers/memory.controller.spec.ts index ac96e54a5b..8629b6c799 100644 --- a/server/src/controllers/memory.controller.spec.ts +++ b/server/src/controllers/memory.controller.spec.ts @@ -24,6 +24,11 @@ describe(MemoryController.name, () => { await request(ctx.getHttpServer()).get('/memories'); expect(ctx.authenticate).toHaveBeenCalled(); }); + + it('should not require any parameters', async () => { + await request(ctx.getHttpServer()).get('/memories').query({}); + expect(service.search).toHaveBeenCalled(); + }); }); describe('POST /memories', () => { diff --git a/server/src/controllers/memory.controller.ts b/server/src/controllers/memory.controller.ts index 3b5ad2bb4e..cbf86199bb 100644 --- a/server/src/controllers/memory.controller.ts +++ b/server/src/controllers/memory.controller.ts @@ -1,5 +1,6 @@ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { @@ -9,42 +10,69 @@ import { MemoryStatisticsResponseDto, MemoryUpdateDto, } from 'src/dtos/memory.dto'; -import { Permission } from 'src/enum'; +import { ApiTag, Permission } from 'src/enum'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { MemoryService } from 'src/services/memory.service'; import { UUIDParamDto } from 'src/validation'; -@ApiTags('Memories') +@ApiTags(ApiTag.Memories) @Controller('memories') export class MemoryController { constructor(private service: MemoryService) {} @Get() @Authenticated({ permission: Permission.MemoryRead }) + @Endpoint({ + summary: 'Retrieve memories', + description: + 'Retrieve a list of memories. Memories are sorted descending by creation date by default, although they can also be sorted in ascending order, or randomly.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) searchMemories(@Auth() auth: AuthDto, @Query() dto: MemorySearchDto): Promise { return this.service.search(auth, dto); } @Post() @Authenticated({ permission: Permission.MemoryCreate }) + @Endpoint({ + summary: 'Create a memory', + description: + 'Create a new memory by providing a name, description, and a list of asset IDs to include in the memory.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) createMemory(@Auth() auth: AuthDto, @Body() dto: MemoryCreateDto): Promise { return this.service.create(auth, dto); } @Get('statistics') @Authenticated({ permission: Permission.MemoryStatistics }) + @Endpoint({ + summary: 'Retrieve memories statistics', + description: 'Retrieve statistics about memories, such as total count and other relevant metrics.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) memoriesStatistics(@Auth() auth: AuthDto, @Query() dto: MemorySearchDto): Promise { return this.service.statistics(auth, dto); } @Get(':id') @Authenticated({ permission: Permission.MemoryRead }) + @Endpoint({ + summary: 'Retrieve a memory', + description: 'Retrieve a specific memory by its ID.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getMemory(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.get(auth, id); } @Put(':id') @Authenticated({ permission: Permission.MemoryUpdate }) + @Endpoint({ + summary: 'Update a memory', + description: 'Update an existing memory by its ID.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) updateMemory( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @@ -56,12 +84,22 @@ export class MemoryController { @Delete(':id') @Authenticated({ permission: Permission.MemoryDelete }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Delete a memory', + description: 'Delete a specific memory by its ID.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) deleteMemory(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.remove(auth, id); } @Put(':id/assets') @Authenticated({ permission: Permission.MemoryAssetCreate }) + @Endpoint({ + summary: 'Add assets to a memory', + description: 'Add a list of asset IDs to a specific memory.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) addMemoryAssets( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @@ -73,6 +111,11 @@ export class MemoryController { @Delete(':id/assets') @Authenticated({ permission: Permission.MemoryAssetDelete }) @HttpCode(HttpStatus.OK) + @Endpoint({ + summary: 'Remove assets from a memory', + description: 'Remove a list of asset IDs from a specific memory.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) removeMemoryAssets( @Auth() auth: AuthDto, @Body() dto: BulkIdsDto, diff --git a/server/src/controllers/notification-admin.controller.ts b/server/src/controllers/notification-admin.controller.ts index 28ca7bfd30..c322c5a2b6 100644 --- a/server/src/controllers/notification-admin.controller.ts +++ b/server/src/controllers/notification-admin.controller.ts @@ -1,5 +1,6 @@ import { Body, Controller, HttpCode, HttpStatus, Param, Post } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AuthDto } from 'src/dtos/auth.dto'; import { NotificationCreateDto, @@ -9,17 +10,23 @@ import { TestEmailResponseDto, } from 'src/dtos/notification.dto'; import { SystemConfigSmtpDto } from 'src/dtos/system-config.dto'; +import { ApiTag } from 'src/enum'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { EmailTemplate } from 'src/repositories/email.repository'; import { NotificationAdminService } from 'src/services/notification-admin.service'; -@ApiTags('Notifications (Admin)') +@ApiTags(ApiTag.NotificationsAdmin) @Controller('admin/notifications') export class NotificationAdminController { constructor(private service: NotificationAdminService) {} @Post() @Authenticated({ admin: true }) + @Endpoint({ + summary: 'Create a notification', + description: 'Create a new notification for a specific user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) createNotification(@Auth() auth: AuthDto, @Body() dto: NotificationCreateDto): Promise { return this.service.create(auth, dto); } @@ -27,6 +34,11 @@ export class NotificationAdminController { @Post('test-email') @Authenticated({ admin: true }) @HttpCode(HttpStatus.OK) + @Endpoint({ + summary: 'Send test email', + description: 'Send a test email using the provided SMTP configuration.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) sendTestEmailAdmin(@Auth() auth: AuthDto, @Body() dto: SystemConfigSmtpDto): Promise { return this.service.sendTestEmail(auth.user.id, dto); } @@ -34,6 +46,11 @@ export class NotificationAdminController { @Post('templates/:name') @Authenticated({ admin: true }) @HttpCode(HttpStatus.OK) + @Endpoint({ + summary: 'Render email template', + description: 'Retrieve a preview of the provided email template.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getNotificationTemplateAdmin( @Auth() auth: AuthDto, @Param('name') name: EmailTemplate, diff --git a/server/src/controllers/notification.controller.ts b/server/src/controllers/notification.controller.ts index 8ce183c5d0..0a28e1bda8 100644 --- a/server/src/controllers/notification.controller.ts +++ b/server/src/controllers/notification.controller.ts @@ -1,5 +1,6 @@ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Put, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AuthDto } from 'src/dtos/auth.dto'; import { NotificationDeleteAllDto, @@ -8,18 +9,23 @@ import { NotificationUpdateAllDto, NotificationUpdateDto, } from 'src/dtos/notification.dto'; -import { Permission } from 'src/enum'; +import { ApiTag, Permission } from 'src/enum'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { NotificationService } from 'src/services/notification.service'; import { UUIDParamDto } from 'src/validation'; -@ApiTags('Notifications') +@ApiTags(ApiTag.Notifications) @Controller('notifications') export class NotificationController { constructor(private service: NotificationService) {} @Get() @Authenticated({ permission: Permission.NotificationRead }) + @Endpoint({ + summary: 'Retrieve notifications', + description: 'Retrieve a list of notifications.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getNotifications(@Auth() auth: AuthDto, @Query() dto: NotificationSearchDto): Promise { return this.service.search(auth, dto); } @@ -27,6 +33,11 @@ export class NotificationController { @Put() @Authenticated({ permission: Permission.NotificationUpdate }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Update notifications', + description: 'Update a list of notifications. Allows to bulk-set the read status of notifications.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) updateNotifications(@Auth() auth: AuthDto, @Body() dto: NotificationUpdateAllDto): Promise { return this.service.updateAll(auth, dto); } @@ -34,18 +45,33 @@ export class NotificationController { @Delete() @Authenticated({ permission: Permission.NotificationDelete }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Delete notifications', + description: 'Delete a list of notifications at once.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) deleteNotifications(@Auth() auth: AuthDto, @Body() dto: NotificationDeleteAllDto): Promise { return this.service.deleteAll(auth, dto); } @Get(':id') @Authenticated({ permission: Permission.NotificationRead }) + @Endpoint({ + summary: 'Get a notification', + description: 'Retrieve a specific notification identified by id.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getNotification(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.get(auth, id); } @Put(':id') @Authenticated({ permission: Permission.NotificationUpdate }) + @Endpoint({ + summary: 'Update a notification', + description: 'Update a specific notification to set its read status.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) updateNotification( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @@ -57,6 +83,11 @@ export class NotificationController { @Delete(':id') @Authenticated({ permission: Permission.NotificationDelete }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Delete a notification', + description: 'Delete a specific notification.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) deleteNotification(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.delete(auth, id); } diff --git a/server/src/controllers/oauth.controller.ts b/server/src/controllers/oauth.controller.ts index f81a184557..797bf497ef 100644 --- a/server/src/controllers/oauth.controller.ts +++ b/server/src/controllers/oauth.controller.ts @@ -1,6 +1,7 @@ import { Body, Controller, Get, HttpCode, HttpStatus, Post, Redirect, Req, Res } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { Request, Response } from 'express'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AuthDto, LoginResponseDto, @@ -9,18 +10,24 @@ import { OAuthConfigDto, } from 'src/dtos/auth.dto'; import { UserAdminResponseDto } from 'src/dtos/user.dto'; -import { AuthType, ImmichCookie } from 'src/enum'; +import { ApiTag, AuthType, ImmichCookie } from 'src/enum'; import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard'; import { AuthService, LoginDetails } from 'src/services/auth.service'; import { respondWithCookie } from 'src/utils/response'; -@ApiTags('OAuth') +@ApiTags(ApiTag.Authentication) @Controller('oauth') export class OAuthController { constructor(private service: AuthService) {} @Get('mobile-redirect') @Redirect() + @Endpoint({ + summary: 'Redirect OAuth to mobile', + description: + 'Requests to this URL are automatically forwarded to the mobile app, and is used in some cases for OAuth redirecting.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) redirectOAuthToMobile(@Req() request: Request) { return { url: this.service.getMobileRedirect(request.url), @@ -29,6 +36,11 @@ export class OAuthController { } @Post('authorize') + @Endpoint({ + summary: 'Start OAuth', + description: 'Initiate the OAuth authorization process.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) async startOAuth( @Body() dto: OAuthConfigDto, @Res({ passthrough: true }) res: Response, @@ -49,6 +61,11 @@ export class OAuthController { } @Post('callback') + @Endpoint({ + summary: 'Finish OAuth', + description: 'Complete the OAuth authorization process by exchanging the authorization code for a session token.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) async finishOAuth( @Req() request: Request, @Res({ passthrough: true }) res: Response, @@ -71,6 +88,11 @@ export class OAuthController { @Post('link') @Authenticated() @HttpCode(HttpStatus.OK) + @Endpoint({ + summary: 'Link OAuth account', + description: 'Link an OAuth account to the authenticated user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) linkOAuthAccount( @Req() request: Request, @Auth() auth: AuthDto, @@ -82,6 +104,11 @@ export class OAuthController { @Post('unlink') @Authenticated() @HttpCode(HttpStatus.OK) + @Endpoint({ + summary: 'Unlink OAuth account', + description: 'Unlink the OAuth account from the authenticated user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) unlinkOAuthAccount(@Auth() auth: AuthDto): Promise { return this.service.unlink(auth); } diff --git a/server/src/controllers/partner.controller.ts b/server/src/controllers/partner.controller.ts index 7cb5c1c274..951aee7e0c 100644 --- a/server/src/controllers/partner.controller.ts +++ b/server/src/controllers/partner.controller.ts @@ -1,32 +1,46 @@ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { EndpointLifecycle } from 'src/decorators'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AuthDto } from 'src/dtos/auth.dto'; import { PartnerCreateDto, PartnerResponseDto, PartnerSearchDto, PartnerUpdateDto } from 'src/dtos/partner.dto'; -import { Permission } from 'src/enum'; +import { ApiTag, Permission } from 'src/enum'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { PartnerService } from 'src/services/partner.service'; import { UUIDParamDto } from 'src/validation'; -@ApiTags('Partners') +@ApiTags(ApiTag.Partners) @Controller('partners') export class PartnerController { constructor(private service: PartnerService) {} @Get() @Authenticated({ permission: Permission.PartnerRead }) + @Endpoint({ + summary: 'Retrieve partners', + description: 'Retrieve a list of partners with whom assets are shared.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getPartners(@Auth() auth: AuthDto, @Query() dto: PartnerSearchDto): Promise { return this.service.search(auth, dto); } @Post() @Authenticated({ permission: Permission.PartnerCreate }) + @Endpoint({ + summary: 'Create a partner', + description: 'Create a new partner to share assets with.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) createPartner(@Auth() auth: AuthDto, @Body() dto: PartnerCreateDto): Promise { return this.service.create(auth, dto); } @Post(':id') - @EndpointLifecycle({ deprecatedAt: 'v1.141.0' }) + @Endpoint({ + summary: 'Create a partner', + description: 'Create a new partner to share assets with.', + history: new HistoryBuilder().added('v1').deprecated('v1', { replacementId: 'createPartner' }), + }) @Authenticated({ permission: Permission.PartnerCreate }) createPartnerDeprecated(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.create(auth, { sharedWithId: id }); @@ -34,6 +48,11 @@ export class PartnerController { @Put(':id') @Authenticated({ permission: Permission.PartnerUpdate }) + @Endpoint({ + summary: 'Update a partner', + description: "Specify whether a partner's assets should appear in the user's timeline.", + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) updatePartner( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @@ -45,6 +64,11 @@ export class PartnerController { @Delete(':id') @Authenticated({ permission: Permission.PartnerDelete }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Remove a partner', + description: 'Stop sharing assets with a partner.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) removePartner(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.remove(auth, id); } diff --git a/server/src/controllers/person.controller.ts b/server/src/controllers/person.controller.ts index 84bb864cd3..5abd6eb1b4 100644 --- a/server/src/controllers/person.controller.ts +++ b/server/src/controllers/person.controller.ts @@ -14,6 +14,7 @@ import { } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { NextFunction, Response } from 'express'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { @@ -27,14 +28,14 @@ import { PersonStatisticsResponseDto, PersonUpdateDto, } from 'src/dtos/person.dto'; -import { Permission } from 'src/enum'; +import { ApiTag, Permission } from 'src/enum'; import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { PersonService } from 'src/services/person.service'; import { sendFile } from 'src/utils/file'; import { UUIDParamDto } from 'src/validation'; -@ApiTags('People') +@ApiTags(ApiTag.People) @Controller('people') export class PersonController { constructor( @@ -46,18 +47,33 @@ export class PersonController { @Get() @Authenticated({ permission: Permission.PersonRead }) + @Endpoint({ + summary: 'Get all people', + description: 'Retrieve a list of all people.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getAllPeople(@Auth() auth: AuthDto, @Query() options: PersonSearchDto): Promise { return this.service.getAll(auth, options); } @Post() @Authenticated({ permission: Permission.PersonCreate }) + @Endpoint({ + summary: 'Create a person', + description: 'Create a new person that can have multiple faces assigned to them.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) createPerson(@Auth() auth: AuthDto, @Body() dto: PersonCreateDto): Promise { return this.service.create(auth, dto); } @Put() @Authenticated({ permission: Permission.PersonUpdate }) + @Endpoint({ + summary: 'Update people', + description: 'Bulk update multiple people at once.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) updatePeople(@Auth() auth: AuthDto, @Body() dto: PeopleUpdateDto): Promise { return this.service.updateAll(auth, dto); } @@ -65,18 +81,33 @@ export class PersonController { @Delete() @Authenticated({ permission: Permission.PersonDelete }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Delete people', + description: 'Bulk delete a list of people at once.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) deletePeople(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise { return this.service.deleteAll(auth, dto); } @Get(':id') @Authenticated({ permission: Permission.PersonRead }) + @Endpoint({ + summary: 'Get a person', + description: 'Retrieve a person by id.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getPerson(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.getById(auth, id); } @Put(':id') @Authenticated({ permission: Permission.PersonUpdate }) + @Endpoint({ + summary: 'Update person', + description: 'Update an individual person.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) updatePerson( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @@ -88,12 +119,22 @@ export class PersonController { @Delete(':id') @Authenticated({ permission: Permission.PersonDelete }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Delete person', + description: 'Delete an individual person.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) deletePerson(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.delete(auth, id); } @Get(':id/statistics') @Authenticated({ permission: Permission.PersonStatistics }) + @Endpoint({ + summary: 'Get person statistics', + description: 'Retrieve statistics about a specific person.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getPersonStatistics(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.getStatistics(auth, id); } @@ -101,6 +142,11 @@ export class PersonController { @Get(':id/thumbnail') @FileResponse() @Authenticated({ permission: Permission.PersonRead }) + @Endpoint({ + summary: 'Get person thumbnail', + description: 'Retrieve the thumbnail file for a person.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) async getPersonThumbnail( @Res() res: Response, @Next() next: NextFunction, @@ -112,6 +158,11 @@ export class PersonController { @Put(':id/reassign') @Authenticated({ permission: Permission.PersonReassign }) + @Endpoint({ + summary: 'Reassign faces', + description: 'Bulk reassign a list of faces to a different person.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) reassignFaces( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @@ -123,6 +174,11 @@ export class PersonController { @Post(':id/merge') @Authenticated({ permission: Permission.PersonMerge }) @HttpCode(HttpStatus.OK) + @Endpoint({ + summary: 'Merge people', + description: 'Merge a list of people into the person specified in the path parameter.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) mergePerson( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, diff --git a/server/src/controllers/plugin.controller.ts b/server/src/controllers/plugin.controller.ts new file mode 100644 index 0000000000..a0a4d14b0b --- /dev/null +++ b/server/src/controllers/plugin.controller.ts @@ -0,0 +1,36 @@ +import { Controller, Get, Param } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; +import { PluginResponseDto } from 'src/dtos/plugin.dto'; +import { Permission } from 'src/enum'; +import { Authenticated } from 'src/middleware/auth.guard'; +import { PluginService } from 'src/services/plugin.service'; +import { UUIDParamDto } from 'src/validation'; + +@ApiTags('Plugins') +@Controller('plugins') +export class PluginController { + constructor(private service: PluginService) {} + + @Get() + @Authenticated({ permission: Permission.PluginRead }) + @Endpoint({ + summary: 'List all plugins', + description: 'Retrieve a list of plugins available to the authenticated user.', + history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), + }) + getPlugins(): Promise { + return this.service.getAll(); + } + + @Get(':id') + @Authenticated({ permission: Permission.PluginRead }) + @Endpoint({ + summary: 'Retrieve a plugin', + description: 'Retrieve information about a specific plugin by its ID.', + history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), + }) + getPlugin(@Param() { id }: UUIDParamDto): Promise { + return this.service.get(id); + } +} diff --git a/server/src/controllers/search.controller.ts b/server/src/controllers/search.controller.ts index f9aa6bce81..439a7a5118 100644 --- a/server/src/controllers/search.controller.ts +++ b/server/src/controllers/search.controller.ts @@ -1,5 +1,6 @@ import { Body, Controller, Get, HttpCode, HttpStatus, Post, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AssetResponseDto } from 'src/dtos/asset-response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { PersonResponseDto } from 'src/dtos/person.dto'; @@ -17,11 +18,11 @@ import { SmartSearchDto, StatisticsSearchDto, } from 'src/dtos/search.dto'; -import { Permission } from 'src/enum'; +import { ApiTag, Permission } from 'src/enum'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { SearchService } from 'src/services/search.service'; -@ApiTags('Search') +@ApiTags(ApiTag.Search) @Controller('search') export class SearchController { constructor(private service: SearchService) {} @@ -29,6 +30,11 @@ export class SearchController { @Post('metadata') @Authenticated({ permission: Permission.AssetRead }) @HttpCode(HttpStatus.OK) + @Endpoint({ + summary: 'Search assets by metadata', + description: 'Search for assets based on various metadata criteria.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) searchAssets(@Auth() auth: AuthDto, @Body() dto: MetadataSearchDto): Promise { return this.service.searchMetadata(auth, dto); } @@ -36,6 +42,11 @@ export class SearchController { @Post('statistics') @Authenticated({ permission: Permission.AssetStatistics }) @HttpCode(HttpStatus.OK) + @Endpoint({ + summary: 'Search asset statistics', + description: 'Retrieve statistical data about assets based on search criteria, such as the total matching count.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) searchAssetStatistics(@Auth() auth: AuthDto, @Body() dto: StatisticsSearchDto): Promise { return this.service.searchStatistics(auth, dto); } @@ -43,6 +54,11 @@ export class SearchController { @Post('random') @Authenticated({ permission: Permission.AssetRead }) @HttpCode(HttpStatus.OK) + @Endpoint({ + summary: 'Search random assets', + description: 'Retrieve a random selection of assets based on the provided criteria.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) searchRandom(@Auth() auth: AuthDto, @Body() dto: RandomSearchDto): Promise { return this.service.searchRandom(auth, dto); } @@ -50,6 +66,11 @@ export class SearchController { @Post('large-assets') @Authenticated({ permission: Permission.AssetRead }) @HttpCode(HttpStatus.OK) + @Endpoint({ + summary: 'Search large assets', + description: 'Search for assets that are considered large based on specified criteria.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) searchLargeAssets(@Auth() auth: AuthDto, @Query() dto: LargeAssetSearchDto): Promise { return this.service.searchLargeAssets(auth, dto); } @@ -57,36 +78,68 @@ export class SearchController { @Post('smart') @Authenticated({ permission: Permission.AssetRead }) @HttpCode(HttpStatus.OK) + @Endpoint({ + summary: 'Smart asset search', + description: 'Perform a smart search for assets by using machine learning vectors to determine relevance.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) searchSmart(@Auth() auth: AuthDto, @Body() dto: SmartSearchDto): Promise { return this.service.searchSmart(auth, dto); } @Get('explore') @Authenticated({ permission: Permission.AssetRead }) + @Endpoint({ + summary: 'Retrieve explore data', + description: 'Retrieve data for the explore section, such as popular people and places.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getExploreData(@Auth() auth: AuthDto): Promise { return this.service.getExploreData(auth); } @Get('person') @Authenticated({ permission: Permission.PersonRead }) + @Endpoint({ + summary: 'Search people', + description: 'Search for people by name.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) searchPerson(@Auth() auth: AuthDto, @Query() dto: SearchPeopleDto): Promise { return this.service.searchPerson(auth, dto); } @Get('places') @Authenticated({ permission: Permission.AssetRead }) + @Endpoint({ + summary: 'Search places', + description: 'Search for places by name.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) searchPlaces(@Query() dto: SearchPlacesDto): Promise { return this.service.searchPlaces(dto); } @Get('cities') @Authenticated({ permission: Permission.AssetRead }) + @Endpoint({ + summary: 'Retrieve assets by city', + description: + 'Retrieve a list of assets with each asset belonging to a different city. This endpoint is used on the places pages to show a single thumbnail for each city the user has assets in.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getAssetsByCity(@Auth() auth: AuthDto): Promise { return this.service.getAssetsByCity(auth); } @Get('suggestions') @Authenticated({ permission: Permission.AssetRead }) + @Endpoint({ + summary: 'Retrieve search suggestions', + description: + 'Retrieve search suggestions based on partial input. This endpoint is used for typeahead search features.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getSearchSuggestions(@Auth() auth: AuthDto, @Query() dto: SearchSuggestionRequestDto): Promise { // TODO fix open api generation to indicate that results can be nullable return this.service.getSearchSuggestions(auth, dto) as Promise; diff --git a/server/src/controllers/server.controller.ts b/server/src/controllers/server.controller.ts index f9a340eb31..ffcb50c674 100644 --- a/server/src/controllers/server.controller.ts +++ b/server/src/controllers/server.controller.ts @@ -1,5 +1,6 @@ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Put } from '@nestjs/common'; import { ApiNotFoundResponse, ApiTags } from '@nestjs/swagger'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { LicenseKeyDto, LicenseResponseDto } from 'src/dtos/license.dto'; import { ServerAboutResponseDto, @@ -15,13 +16,13 @@ import { ServerVersionResponseDto, } from 'src/dtos/server.dto'; import { VersionCheckStateResponseDto } from 'src/dtos/system-metadata.dto'; -import { Permission } from 'src/enum'; +import { ApiTag, Permission } from 'src/enum'; import { Authenticated } from 'src/middleware/auth.guard'; import { ServerService } from 'src/services/server.service'; import { SystemMetadataService } from 'src/services/system-metadata.service'; import { VersionService } from 'src/services/version.service'; -@ApiTags('Server') +@ApiTags(ApiTag.Server) @Controller('server') export class ServerController { constructor( @@ -32,59 +33,114 @@ export class ServerController { @Get('about') @Authenticated({ permission: Permission.ServerAbout }) + @Endpoint({ + summary: 'Get server information', + description: 'Retrieve a list of information about the server.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getAboutInfo(): Promise { return this.service.getAboutInfo(); } @Get('apk-links') @Authenticated({ permission: Permission.ServerApkLinks }) + @Endpoint({ + summary: 'Get APK links', + description: 'Retrieve links to the APKs for the current server version.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getApkLinks(): ServerApkLinksDto { return this.service.getApkLinks(); } @Get('storage') @Authenticated({ permission: Permission.ServerStorage }) + @Endpoint({ + summary: 'Get storage', + description: 'Retrieve the current storage utilization information of the server.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getStorage(): Promise { return this.service.getStorage(); } @Get('ping') + @Endpoint({ + summary: 'Ping', + description: 'Pong', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) pingServer(): ServerPingResponse { return this.service.ping(); } @Get('version') + @Endpoint({ + summary: 'Get server version', + description: 'Retrieve the current server version in semantic versioning (semver) format.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getServerVersion(): ServerVersionResponseDto { return this.versionService.getVersion(); } @Get('version-history') + @Endpoint({ + summary: 'Get version history', + description: 'Retrieve a list of past versions the server has been on.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getVersionHistory(): Promise { return this.versionService.getVersionHistory(); } @Get('features') + @Endpoint({ + summary: 'Get features', + description: 'Retrieve available features supported by this server.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getServerFeatures(): Promise { return this.service.getFeatures(); } @Get('theme') + @Endpoint({ + summary: 'Get theme', + description: 'Retrieve the custom CSS, if existent.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getTheme(): Promise { return this.service.getTheme(); } @Get('config') + @Endpoint({ + summary: 'Get config', + description: 'Retrieve the current server configuration.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getServerConfig(): Promise { return this.service.getSystemConfig(); } @Get('statistics') @Authenticated({ permission: Permission.ServerStatistics, admin: true }) + @Endpoint({ + summary: 'Get statistics', + description: 'Retrieve statistics about the entire Immich instance such as asset counts.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getServerStatistics(): Promise { return this.service.getStatistics(); } @Get('media-types') + @Endpoint({ + summary: 'Get supported media types', + description: 'Retrieve all media types supported by the server.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getSupportedMediaTypes(): ServerMediaTypesResponseDto { return this.service.getSupportedMediaTypes(); } @@ -92,12 +148,22 @@ export class ServerController { @Get('license') @Authenticated({ permission: Permission.ServerLicenseRead, admin: true }) @ApiNotFoundResponse() + @Endpoint({ + summary: 'Get product key', + description: 'Retrieve information about whether the server currently has a product key registered.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getServerLicense(): Promise { return this.service.getLicense(); } @Put('license') @Authenticated({ permission: Permission.ServerLicenseUpdate, admin: true }) + @Endpoint({ + summary: 'Set server product key', + description: 'Validate and set the server product key if successful.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) setServerLicense(@Body() license: LicenseKeyDto): Promise { return this.service.setLicense(license); } @@ -105,12 +171,22 @@ export class ServerController { @Delete('license') @Authenticated({ permission: Permission.ServerLicenseDelete, admin: true }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Delete server product key', + description: 'Delete the currently set server product key.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) deleteServerLicense(): Promise { return this.service.deleteLicense(); } @Get('version-check') @Authenticated({ permission: Permission.ServerVersionCheck }) + @Endpoint({ + summary: 'Get version check status', + description: 'Retrieve information about the last time the version check ran.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getVersionCheck(): Promise { return this.systemMetadataService.getVersionCheckState(); } diff --git a/server/src/controllers/session.controller.ts b/server/src/controllers/session.controller.ts index cbe8158fee..d21cca3a83 100644 --- a/server/src/controllers/session.controller.ts +++ b/server/src/controllers/session.controller.ts @@ -1,25 +1,36 @@ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AuthDto } from 'src/dtos/auth.dto'; import { SessionCreateDto, SessionCreateResponseDto, SessionResponseDto, SessionUpdateDto } from 'src/dtos/session.dto'; -import { Permission } from 'src/enum'; +import { ApiTag, Permission } from 'src/enum'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { SessionService } from 'src/services/session.service'; import { UUIDParamDto } from 'src/validation'; -@ApiTags('Sessions') +@ApiTags(ApiTag.Sessions) @Controller('sessions') export class SessionController { constructor(private service: SessionService) {} @Post() @Authenticated({ permission: Permission.SessionCreate }) + @Endpoint({ + summary: 'Create a session', + description: 'Create a session as a child to the current session. This endpoint is used for casting.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) createSession(@Auth() auth: AuthDto, @Body() dto: SessionCreateDto): Promise { return this.service.create(auth, dto); } @Get() @Authenticated({ permission: Permission.SessionRead }) + @Endpoint({ + summary: 'Retrieve sessions', + description: 'Retrieve a list of sessions for the user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getSessions(@Auth() auth: AuthDto): Promise { return this.service.getAll(auth); } @@ -27,12 +38,22 @@ export class SessionController { @Delete() @Authenticated({ permission: Permission.SessionDelete }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Delete all sessions', + description: 'Delete all sessions for the user. This will not delete the current session.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) deleteAllSessions(@Auth() auth: AuthDto): Promise { return this.service.deleteAll(auth); } @Put(':id') @Authenticated({ permission: Permission.SessionUpdate }) + @Endpoint({ + summary: 'Update a session', + description: 'Update a specific session identified by id.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) updateSession( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @@ -44,6 +65,11 @@ export class SessionController { @Delete(':id') @Authenticated({ permission: Permission.SessionDelete }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Delete a session', + description: 'Delete a specific session by id.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) deleteSession(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.delete(auth, id); } @@ -51,6 +77,11 @@ export class SessionController { @Post(':id/lock') @Authenticated({ permission: Permission.SessionLock }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Lock a session', + description: 'Lock a specific session by id.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) lockSession(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.lock(auth, id); } diff --git a/server/src/controllers/shared-link.controller.ts b/server/src/controllers/shared-link.controller.ts index ef0a93e012..8875127a25 100644 --- a/server/src/controllers/shared-link.controller.ts +++ b/server/src/controllers/shared-link.controller.ts @@ -15,6 +15,7 @@ import { } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { Request, Response } from 'express'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AssetIdsResponseDto } from 'src/dtos/asset-ids.response.dto'; import { AssetIdsDto } from 'src/dtos/asset.dto'; import { AuthDto } from 'src/dtos/auth.dto'; @@ -25,26 +26,36 @@ import { SharedLinkResponseDto, SharedLinkSearchDto, } from 'src/dtos/shared-link.dto'; -import { ImmichCookie, Permission } from 'src/enum'; +import { ApiTag, ImmichCookie, Permission } from 'src/enum'; import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard'; import { LoginDetails } from 'src/services/auth.service'; import { SharedLinkService } from 'src/services/shared-link.service'; import { respondWithCookie } from 'src/utils/response'; import { UUIDParamDto } from 'src/validation'; -@ApiTags('Shared Links') +@ApiTags(ApiTag.SharedLinks) @Controller('shared-links') export class SharedLinkController { constructor(private service: SharedLinkService) {} @Get() @Authenticated({ permission: Permission.SharedLinkRead }) + @Endpoint({ + summary: 'Retrieve all shared links', + description: 'Retrieve a list of all shared links.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getAllSharedLinks(@Auth() auth: AuthDto, @Query() dto: SharedLinkSearchDto): Promise { return this.service.getAll(auth, dto); } @Get('me') @Authenticated({ sharedLink: true }) + @Endpoint({ + summary: 'Retrieve current shared link', + description: 'Retrieve the current shared link associated with authentication method.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) async getMySharedLink( @Auth() auth: AuthDto, @Query() dto: SharedLinkPasswordDto, @@ -65,18 +76,33 @@ export class SharedLinkController { @Get(':id') @Authenticated({ permission: Permission.SharedLinkRead }) + @Endpoint({ + summary: 'Retrieve a shared link', + description: 'Retrieve a specific shared link by its ID.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getSharedLinkById(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.get(auth, id); } @Post() @Authenticated({ permission: Permission.SharedLinkCreate }) + @Endpoint({ + summary: 'Create a shared link', + description: 'Create a new shared link.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) createSharedLink(@Auth() auth: AuthDto, @Body() dto: SharedLinkCreateDto) { return this.service.create(auth, dto); } @Patch(':id') @Authenticated({ permission: Permission.SharedLinkUpdate }) + @Endpoint({ + summary: 'Update a shared link', + description: 'Update an existing shared link by its ID.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) updateSharedLink( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @@ -88,12 +114,23 @@ export class SharedLinkController { @Delete(':id') @Authenticated({ permission: Permission.SharedLinkDelete }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Delete a shared link', + description: 'Delete a specific shared link by its ID.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) removeSharedLink(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.remove(auth, id); } @Put(':id/assets') @Authenticated({ sharedLink: true }) + @Endpoint({ + summary: 'Add assets to a shared link', + description: + 'Add assets to a specific shared link by its ID. This endpoint is only relevant for shared link of type individual.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) addSharedLinkAssets( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @@ -104,6 +141,12 @@ export class SharedLinkController { @Delete(':id/assets') @Authenticated({ sharedLink: true }) + @Endpoint({ + summary: 'Remove assets from a shared link', + description: + 'Remove assets from a specific shared link by its ID. This endpoint is only relevant for shared link of type individual.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) removeSharedLinkAssets( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, diff --git a/server/src/controllers/stack.controller.ts b/server/src/controllers/stack.controller.ts index 6acd4abc24..b35b49c786 100644 --- a/server/src/controllers/stack.controller.ts +++ b/server/src/controllers/stack.controller.ts @@ -1,26 +1,38 @@ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { StackCreateDto, StackResponseDto, StackSearchDto, StackUpdateDto } from 'src/dtos/stack.dto'; -import { Permission } from 'src/enum'; +import { ApiTag, Permission } from 'src/enum'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { StackService } from 'src/services/stack.service'; import { UUIDAssetIDParamDto, UUIDParamDto } from 'src/validation'; -@ApiTags('Stacks') +@ApiTags(ApiTag.Stacks) @Controller('stacks') export class StackController { constructor(private service: StackService) {} @Get() @Authenticated({ permission: Permission.StackRead }) + @Endpoint({ + summary: 'Retrieve stacks', + description: 'Retrieve a list of stacks.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) searchStacks(@Auth() auth: AuthDto, @Query() query: StackSearchDto): Promise { return this.service.search(auth, query); } @Post() @Authenticated({ permission: Permission.StackCreate }) + @Endpoint({ + summary: 'Create a stack', + description: + 'Create a new stack by providing a name and a list of asset IDs to include in the stack. If any of the provided asset IDs are primary assets of an existing stack, the existing stack will be merged into the newly created stack.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) createStack(@Auth() auth: AuthDto, @Body() dto: StackCreateDto): Promise { return this.service.create(auth, dto); } @@ -28,18 +40,33 @@ export class StackController { @Delete() @Authenticated({ permission: Permission.StackDelete }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Delete stacks', + description: 'Delete multiple stacks by providing a list of stack IDs.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) deleteStacks(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise { return this.service.deleteAll(auth, dto); } @Get(':id') @Authenticated({ permission: Permission.StackRead }) + @Endpoint({ + summary: 'Retrieve a stack', + description: 'Retrieve a specific stack by its ID.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getStack(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.get(auth, id); } @Put(':id') @Authenticated({ permission: Permission.StackUpdate }) + @Endpoint({ + summary: 'Update a stack', + description: 'Update an existing stack by its ID.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) updateStack( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @@ -51,6 +78,11 @@ export class StackController { @Delete(':id') @Authenticated({ permission: Permission.StackDelete }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Delete a stack', + description: 'Delete a specific stack by its ID.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) deleteStack(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.delete(auth, id); } @@ -58,6 +90,11 @@ export class StackController { @Delete(':id/assets/:assetId') @Authenticated({ permission: Permission.StackUpdate }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Remove an asset from a stack', + description: 'Remove a specific asset from a stack by providing the stack ID and asset ID.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) removeAssetFromStack(@Auth() auth: AuthDto, @Param() dto: UUIDAssetIDParamDto): Promise { return this.service.removeAsset(auth, dto); } diff --git a/server/src/controllers/sync.controller.ts b/server/src/controllers/sync.controller.ts index 61432e43e3..de94738f73 100644 --- a/server/src/controllers/sync.controller.ts +++ b/server/src/controllers/sync.controller.ts @@ -1,6 +1,7 @@ import { Body, Controller, Delete, Get, Header, HttpCode, HttpStatus, Post, Res } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { Response } from 'express'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AssetResponseDto } from 'src/dtos/asset-response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { @@ -12,12 +13,12 @@ import { SyncAckSetDto, SyncStreamDto, } from 'src/dtos/sync.dto'; -import { Permission } from 'src/enum'; +import { ApiTag, Permission } from 'src/enum'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { GlobalExceptionFilter } from 'src/middleware/global-exception.filter'; import { SyncService } from 'src/services/sync.service'; -@ApiTags('Sync') +@ApiTags(ApiTag.Sync) @Controller('sync') export class SyncController { constructor( @@ -28,6 +29,11 @@ export class SyncController { @Post('full-sync') @Authenticated() @HttpCode(HttpStatus.OK) + @Endpoint({ + summary: 'Get full sync for user', + description: 'Retrieve all assets for a full synchronization for the authenticated user.', + history: new HistoryBuilder().added('v1').deprecated('v2'), + }) getFullSyncForUser(@Auth() auth: AuthDto, @Body() dto: AssetFullSyncDto): Promise { return this.service.getFullSync(auth, dto); } @@ -35,6 +41,11 @@ export class SyncController { @Post('delta-sync') @Authenticated() @HttpCode(HttpStatus.OK) + @Endpoint({ + summary: 'Get delta sync for user', + description: 'Retrieve changed assets since the last sync for the authenticated user.', + history: new HistoryBuilder().added('v1').deprecated('v2'), + }) getDeltaSync(@Auth() auth: AuthDto, @Body() dto: AssetDeltaSyncDto): Promise { return this.service.getDeltaSync(auth, dto); } @@ -43,6 +54,12 @@ export class SyncController { @Authenticated({ permission: Permission.SyncStream }) @Header('Content-Type', 'application/jsonlines+json') @HttpCode(HttpStatus.OK) + @Endpoint({ + summary: 'Stream sync changes', + description: + 'Retrieve a JSON lines streamed response of changes for synchronization. This endpoint is used by the mobile app to efficiently stay up to date with changes.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) async getSyncStream(@Auth() auth: AuthDto, @Res() res: Response, @Body() dto: SyncStreamDto) { try { await this.service.stream(auth, res, dto); @@ -54,6 +71,11 @@ export class SyncController { @Get('ack') @Authenticated({ permission: Permission.SyncCheckpointRead }) + @Endpoint({ + summary: 'Retrieve acknowledgements', + description: 'Retrieve the synchronization acknowledgments for the current session.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getSyncAck(@Auth() auth: AuthDto): Promise { return this.service.getAcks(auth); } @@ -61,6 +83,12 @@ export class SyncController { @Post('ack') @Authenticated({ permission: Permission.SyncCheckpointUpdate }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Acknowledge changes', + description: + 'Send a list of synchronization acknowledgements to confirm that the latest changes have been received.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) sendSyncAck(@Auth() auth: AuthDto, @Body() dto: SyncAckSetDto) { return this.service.setAcks(auth, dto); } @@ -68,6 +96,11 @@ export class SyncController { @Delete('ack') @Authenticated({ permission: Permission.SyncCheckpointDelete }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Delete acknowledgements', + description: 'Delete specific synchronization acknowledgments.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) deleteSyncAck(@Auth() auth: AuthDto, @Body() dto: SyncAckDeleteDto): Promise { return this.service.deleteAcks(auth, dto); } diff --git a/server/src/controllers/system-config.controller.ts b/server/src/controllers/system-config.controller.ts index 69117f4d45..6b79b38d98 100644 --- a/server/src/controllers/system-config.controller.ts +++ b/server/src/controllers/system-config.controller.ts @@ -1,12 +1,13 @@ import { Body, Controller, Get, Put } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { SystemConfigDto, SystemConfigTemplateStorageOptionDto } from 'src/dtos/system-config.dto'; -import { Permission } from 'src/enum'; +import { ApiTag, Permission } from 'src/enum'; import { Authenticated } from 'src/middleware/auth.guard'; import { StorageTemplateService } from 'src/services/storage-template.service'; import { SystemConfigService } from 'src/services/system-config.service'; -@ApiTags('System Config') +@ApiTags(ApiTag.SystemConfig) @Controller('system-config') export class SystemConfigController { constructor( @@ -16,24 +17,44 @@ export class SystemConfigController { @Get() @Authenticated({ permission: Permission.SystemConfigRead, admin: true }) + @Endpoint({ + summary: 'Get system configuration', + description: 'Retrieve the current system configuration.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getConfig(): Promise { return this.service.getSystemConfig(); } @Get('defaults') @Authenticated({ permission: Permission.SystemConfigRead, admin: true }) + @Endpoint({ + summary: 'Get system configuration defaults', + description: 'Retrieve the default values for the system configuration.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getConfigDefaults(): SystemConfigDto { return this.service.getDefaults(); } @Put() @Authenticated({ permission: Permission.SystemConfigUpdate, admin: true }) + @Endpoint({ + summary: 'Update system configuration', + description: 'Update the system configuration with a new system configuration.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) updateConfig(@Body() dto: SystemConfigDto): Promise { return this.service.updateSystemConfig(dto); } @Get('storage-template-options') @Authenticated({ permission: Permission.SystemConfigRead, admin: true }) + @Endpoint({ + summary: 'Get storage template options', + description: 'Retrieve exemplary storage template options.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getStorageTemplateOptions(): SystemConfigTemplateStorageOptionDto { return this.storageTemplateService.getStorageTemplateOptions(); } diff --git a/server/src/controllers/system-metadata.controller.ts b/server/src/controllers/system-metadata.controller.ts index d6634e9444..8f73def3f7 100644 --- a/server/src/controllers/system-metadata.controller.ts +++ b/server/src/controllers/system-metadata.controller.ts @@ -1,21 +1,27 @@ import { Body, Controller, Get, HttpCode, HttpStatus, Post } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AdminOnboardingUpdateDto, ReverseGeocodingStateResponseDto, VersionCheckStateResponseDto, } from 'src/dtos/system-metadata.dto'; -import { Permission } from 'src/enum'; +import { ApiTag, Permission } from 'src/enum'; import { Authenticated } from 'src/middleware/auth.guard'; import { SystemMetadataService } from 'src/services/system-metadata.service'; -@ApiTags('System Metadata') +@ApiTags(ApiTag.SystemMetadata) @Controller('system-metadata') export class SystemMetadataController { constructor(private service: SystemMetadataService) {} @Get('admin-onboarding') @Authenticated({ permission: Permission.SystemMetadataRead, admin: true }) + @Endpoint({ + summary: 'Retrieve admin onboarding', + description: 'Retrieve the current admin onboarding status.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getAdminOnboarding(): Promise { return this.service.getAdminOnboarding(); } @@ -23,18 +29,33 @@ export class SystemMetadataController { @Post('admin-onboarding') @Authenticated({ permission: Permission.SystemMetadataUpdate, admin: true }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Update admin onboarding', + description: 'Update the admin onboarding status.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) updateAdminOnboarding(@Body() dto: AdminOnboardingUpdateDto): Promise { return this.service.updateAdminOnboarding(dto); } @Get('reverse-geocoding-state') @Authenticated({ permission: Permission.SystemMetadataRead, admin: true }) + @Endpoint({ + summary: 'Retrieve reverse geocoding state', + description: 'Retrieve the current state of the reverse geocoding import.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getReverseGeocodingState(): Promise { return this.service.getReverseGeocodingState(); } @Get('version-check-state') @Authenticated({ permission: Permission.SystemMetadataRead, admin: true }) + @Endpoint({ + summary: 'Retrieve version check state', + description: 'Retrieve the current state of the version check process.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getVersionCheckState(): Promise { return this.service.getVersionCheckState(); } diff --git a/server/src/controllers/tag.controller.ts b/server/src/controllers/tag.controller.ts index 59915ef2a4..101e89f3a5 100644 --- a/server/src/controllers/tag.controller.ts +++ b/server/src/controllers/tag.controller.ts @@ -1,5 +1,6 @@ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { @@ -10,48 +11,78 @@ import { TagUpdateDto, TagUpsertDto, } from 'src/dtos/tag.dto'; -import { Permission } from 'src/enum'; +import { ApiTag, Permission } from 'src/enum'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { TagService } from 'src/services/tag.service'; import { UUIDParamDto } from 'src/validation'; -@ApiTags('Tags') +@ApiTags(ApiTag.Tags) @Controller('tags') export class TagController { constructor(private service: TagService) {} @Post() @Authenticated({ permission: Permission.TagCreate }) + @Endpoint({ + summary: 'Create a tag', + description: 'Create a new tag by providing a name and optional color.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) createTag(@Auth() auth: AuthDto, @Body() dto: TagCreateDto): Promise { return this.service.create(auth, dto); } @Get() @Authenticated({ permission: Permission.TagRead }) + @Endpoint({ + summary: 'Retrieve tags', + description: 'Retrieve a list of all tags.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getAllTags(@Auth() auth: AuthDto): Promise { return this.service.getAll(auth); } @Put() @Authenticated({ permission: Permission.TagCreate }) + @Endpoint({ + summary: 'Upsert tags', + description: 'Create or update multiple tags in a single request.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) upsertTags(@Auth() auth: AuthDto, @Body() dto: TagUpsertDto): Promise { return this.service.upsert(auth, dto); } @Put('assets') @Authenticated({ permission: Permission.TagAsset }) + @Endpoint({ + summary: 'Tag assets', + description: 'Add multiple tags to multiple assets in a single request.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) bulkTagAssets(@Auth() auth: AuthDto, @Body() dto: TagBulkAssetsDto): Promise { return this.service.bulkTagAssets(auth, dto); } @Get(':id') @Authenticated({ permission: Permission.TagRead }) + @Endpoint({ + summary: 'Retrieve a tag', + description: 'Retrieve a specific tag by its ID.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getTagById(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.get(auth, id); } @Put(':id') @Authenticated({ permission: Permission.TagUpdate }) + @Endpoint({ + summary: 'Update a tag', + description: 'Update an existing tag identified by its ID.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) updateTag(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @Body() dto: TagUpdateDto): Promise { return this.service.update(auth, id, dto); } @@ -59,12 +90,22 @@ export class TagController { @Delete(':id') @Authenticated({ permission: Permission.TagDelete }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Delete a tag', + description: 'Delete a specific tag by its ID.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) deleteTag(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.remove(auth, id); } @Put(':id/assets') @Authenticated({ permission: Permission.TagAsset }) + @Endpoint({ + summary: 'Tag assets', + description: 'Add a tag to all the specified assets.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) tagAssets( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @@ -75,6 +116,11 @@ export class TagController { @Delete(':id/assets') @Authenticated({ permission: Permission.TagAsset }) + @Endpoint({ + summary: 'Untag assets', + description: 'Remove a tag from all the specified assets.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) untagAssets( @Auth() auth: AuthDto, @Body() dto: BulkIdsDto, diff --git a/server/src/controllers/timeline.controller.ts b/server/src/controllers/timeline.controller.ts index 8cab840ec8..f1789a79e8 100644 --- a/server/src/controllers/timeline.controller.ts +++ b/server/src/controllers/timeline.controller.ts @@ -1,18 +1,24 @@ import { Controller, Get, Header, Query } from '@nestjs/common'; import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AuthDto } from 'src/dtos/auth.dto'; import { TimeBucketAssetDto, TimeBucketAssetResponseDto, TimeBucketDto } from 'src/dtos/time-bucket.dto'; -import { Permission } from 'src/enum'; +import { ApiTag, Permission } from 'src/enum'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { TimelineService } from 'src/services/timeline.service'; -@ApiTags('Timeline') +@ApiTags(ApiTag.Timeline) @Controller('timeline') export class TimelineController { constructor(private service: TimelineService) {} @Get('buckets') @Authenticated({ permission: Permission.AssetRead, sharedLink: true }) + @Endpoint({ + summary: 'Get time buckets', + description: 'Retrieve a list of all minimal time buckets.', + history: new HistoryBuilder().added('v1').internal('v1'), + }) getTimeBuckets(@Auth() auth: AuthDto, @Query() dto: TimeBucketDto) { return this.service.getTimeBuckets(auth, dto); } @@ -21,6 +27,11 @@ export class TimelineController { @Authenticated({ permission: Permission.AssetRead, sharedLink: true }) @ApiOkResponse({ type: TimeBucketAssetResponseDto }) @Header('Content-Type', 'application/json') + @Endpoint({ + summary: 'Get time bucket', + description: 'Retrieve a string of all asset ids in a given time bucket.', + history: new HistoryBuilder().added('v1').internal('v1'), + }) getTimeBucket(@Auth() auth: AuthDto, @Query() dto: TimeBucketAssetDto) { return this.service.getTimeBucket(auth, dto); } diff --git a/server/src/controllers/trash.controller.ts b/server/src/controllers/trash.controller.ts index eaf489f104..ec37c63ecc 100644 --- a/server/src/controllers/trash.controller.ts +++ b/server/src/controllers/trash.controller.ts @@ -1,13 +1,14 @@ import { Body, Controller, HttpCode, HttpStatus, Post } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { TrashResponseDto } from 'src/dtos/trash.dto'; -import { Permission } from 'src/enum'; +import { ApiTag, Permission } from 'src/enum'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { TrashService } from 'src/services/trash.service'; -@ApiTags('Trash') +@ApiTags(ApiTag.Trash) @Controller('trash') export class TrashController { constructor(private service: TrashService) {} @@ -15,6 +16,11 @@ export class TrashController { @Post('empty') @Authenticated({ permission: Permission.AssetDelete }) @HttpCode(HttpStatus.OK) + @Endpoint({ + summary: 'Empty trash', + description: 'Permanently delete all items in the trash.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) emptyTrash(@Auth() auth: AuthDto): Promise { return this.service.empty(auth); } @@ -22,6 +28,11 @@ export class TrashController { @Post('restore') @Authenticated({ permission: Permission.AssetDelete }) @HttpCode(HttpStatus.OK) + @Endpoint({ + summary: 'Restore trash', + description: 'Restore all items in the trash.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) restoreTrash(@Auth() auth: AuthDto): Promise { return this.service.restore(auth); } @@ -29,6 +40,11 @@ export class TrashController { @Post('restore/assets') @Authenticated({ permission: Permission.AssetDelete }) @HttpCode(HttpStatus.OK) + @Endpoint({ + summary: 'Restore assets', + description: 'Restore specific assets from the trash.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) restoreAssets(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise { return this.service.restoreAssets(auth, dto); } diff --git a/server/src/controllers/user-admin.controller.ts b/server/src/controllers/user-admin.controller.ts index d50bd174ad..6dd919e193 100644 --- a/server/src/controllers/user-admin.controller.ts +++ b/server/src/controllers/user-admin.controller.ts @@ -1,7 +1,9 @@ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AssetStatsDto, AssetStatsResponseDto } from 'src/dtos/asset.dto'; import { AuthDto } from 'src/dtos/auth.dto'; +import { SessionResponseDto } from 'src/dtos/session.dto'; import { UserPreferencesResponseDto, UserPreferencesUpdateDto } from 'src/dtos/user-preferences.dto'; import { UserAdminCreateDto, @@ -10,36 +12,56 @@ import { UserAdminSearchDto, UserAdminUpdateDto, } from 'src/dtos/user.dto'; -import { Permission } from 'src/enum'; +import { ApiTag, Permission } from 'src/enum'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { UserAdminService } from 'src/services/user-admin.service'; import { UUIDParamDto } from 'src/validation'; -@ApiTags('Users (admin)') +@ApiTags(ApiTag.UsersAdmin) @Controller('admin/users') export class UserAdminController { constructor(private service: UserAdminService) {} @Get() @Authenticated({ permission: Permission.AdminUserRead, admin: true }) + @Endpoint({ + summary: 'Search users', + description: 'Search for users.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) searchUsersAdmin(@Auth() auth: AuthDto, @Query() dto: UserAdminSearchDto): Promise { return this.service.search(auth, dto); } @Post() @Authenticated({ permission: Permission.AdminUserCreate, admin: true }) + @Endpoint({ + summary: 'Create a user', + description: 'Create a new user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) createUserAdmin(@Body() createUserDto: UserAdminCreateDto): Promise { return this.service.create(createUserDto); } @Get(':id') @Authenticated({ permission: Permission.AdminUserRead, admin: true }) + @Endpoint({ + summary: 'Retrieve a user', + description: 'Retrieve a specific user by their ID.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getUserAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.get(auth, id); } @Put(':id') @Authenticated({ permission: Permission.AdminUserUpdate, admin: true }) + @Endpoint({ + summary: 'Update a user', + description: 'Update an existing user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) updateUserAdmin( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @@ -50,6 +72,11 @@ export class UserAdminController { @Delete(':id') @Authenticated({ permission: Permission.AdminUserDelete, admin: true }) + @Endpoint({ + summary: 'Delete a user', + description: 'Delete a user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) deleteUserAdmin( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @@ -58,8 +85,24 @@ export class UserAdminController { return this.service.delete(auth, id, dto); } + @Get(':id/sessions') + @Authenticated({ permission: Permission.AdminSessionRead, admin: true }) + @Endpoint({ + summary: 'Retrieve user sessions', + description: 'Retrieve all sessions for a specific user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) + getUserSessionsAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { + return this.service.getSessions(auth, id); + } + @Get(':id/statistics') @Authenticated({ permission: Permission.AdminUserRead, admin: true }) + @Endpoint({ + summary: 'Retrieve user statistics', + description: 'Retrieve asset statistics for a specific user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getUserStatisticsAdmin( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @@ -70,12 +113,22 @@ export class UserAdminController { @Get(':id/preferences') @Authenticated({ permission: Permission.AdminUserRead, admin: true }) + @Endpoint({ + summary: 'Retrieve user preferences', + description: 'Retrieve the preferences of a specific user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getUserPreferencesAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.getPreferences(auth, id); } @Put(':id/preferences') @Authenticated({ permission: Permission.AdminUserUpdate, admin: true }) + @Endpoint({ + summary: 'Update user preferences', + description: 'Update the preferences of a specific user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) updateUserPreferencesAdmin( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @@ -87,6 +140,11 @@ export class UserAdminController { @Post(':id/restore') @Authenticated({ permission: Permission.AdminUserDelete, admin: true }) @HttpCode(HttpStatus.OK) + @Endpoint({ + summary: 'Restore a deleted user', + description: 'Restore a previously deleted user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) restoreUserAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.restore(auth, id); } diff --git a/server/src/controllers/user.controller.ts b/server/src/controllers/user.controller.ts index d72b088c54..9c0dd3db7a 100644 --- a/server/src/controllers/user.controller.ts +++ b/server/src/controllers/user.controller.ts @@ -15,13 +15,14 @@ import { } from '@nestjs/common'; import { ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger'; import { NextFunction, Response } from 'express'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AuthDto } from 'src/dtos/auth.dto'; import { LicenseKeyDto, LicenseResponseDto } from 'src/dtos/license.dto'; import { OnboardingDto, OnboardingResponseDto } from 'src/dtos/onboarding.dto'; import { UserPreferencesResponseDto, UserPreferencesUpdateDto } from 'src/dtos/user-preferences.dto'; import { CreateProfileImageDto, CreateProfileImageResponseDto } from 'src/dtos/user-profile.dto'; import { UserAdminResponseDto, UserResponseDto, UserUpdateMeDto } from 'src/dtos/user.dto'; -import { Permission, RouteKey } from 'src/enum'; +import { ApiTag, Permission, RouteKey } from 'src/enum'; import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard'; import { FileUploadInterceptor } from 'src/middleware/file-upload.interceptor'; import { LoggingRepository } from 'src/repositories/logging.repository'; @@ -29,7 +30,7 @@ import { UserService } from 'src/services/user.service'; import { sendFile } from 'src/utils/file'; import { UUIDParamDto } from 'src/validation'; -@ApiTags('Users') +@ApiTags(ApiTag.Users) @Controller(RouteKey.User) export class UserController { constructor( @@ -39,30 +40,55 @@ export class UserController { @Get() @Authenticated({ permission: Permission.UserRead }) + @Endpoint({ + summary: 'Get all users', + description: 'Retrieve a list of all users on the server.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) searchUsers(@Auth() auth: AuthDto): Promise { return this.service.search(auth); } @Get('me') @Authenticated({ permission: Permission.UserRead }) + @Endpoint({ + summary: 'Get current user', + description: 'Retrieve information about the user making the API request.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getMyUser(@Auth() auth: AuthDto): Promise { return this.service.getMe(auth); } @Put('me') @Authenticated({ permission: Permission.UserUpdate }) + @Endpoint({ + summary: 'Update current user', + description: 'Update the current user making teh API request.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) updateMyUser(@Auth() auth: AuthDto, @Body() dto: UserUpdateMeDto): Promise { return this.service.updateMe(auth, dto); } @Get('me/preferences') @Authenticated({ permission: Permission.UserPreferenceRead }) + @Endpoint({ + summary: 'Get my preferences', + description: 'Retrieve the preferences for the current user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getMyPreferences(@Auth() auth: AuthDto): Promise { return this.service.getMyPreferences(auth); } @Put('me/preferences') @Authenticated({ permission: Permission.UserPreferenceUpdate }) + @Endpoint({ + summary: 'Update my preferences', + description: 'Update the preferences of the current user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) updateMyPreferences( @Auth() auth: AuthDto, @Body() dto: UserPreferencesUpdateDto, @@ -72,12 +98,22 @@ export class UserController { @Get('me/license') @Authenticated({ permission: Permission.UserLicenseRead }) + @Endpoint({ + summary: 'Retrieve user product key', + description: 'Retrieve information about whether the current user has a registered product key.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getUserLicense(@Auth() auth: AuthDto): Promise { return this.service.getLicense(auth); } @Put('me/license') @Authenticated({ permission: Permission.UserLicenseUpdate }) + @Endpoint({ + summary: 'Set user product key', + description: 'Register a product key for the current user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) async setUserLicense(@Auth() auth: AuthDto, @Body() license: LicenseKeyDto): Promise { return this.service.setLicense(auth, license); } @@ -85,18 +121,33 @@ export class UserController { @Delete('me/license') @Authenticated({ permission: Permission.UserLicenseDelete }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Delete user product key', + description: 'Delete the registered product key for the current user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) async deleteUserLicense(@Auth() auth: AuthDto): Promise { await this.service.deleteLicense(auth); } @Get('me/onboarding') @Authenticated({ permission: Permission.UserOnboardingRead }) + @Endpoint({ + summary: 'Retrieve user onboarding', + description: 'Retrieve the onboarding status of the current user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getUserOnboarding(@Auth() auth: AuthDto): Promise { return this.service.getOnboarding(auth); } @Put('me/onboarding') @Authenticated({ permission: Permission.UserOnboardingUpdate }) + @Endpoint({ + summary: 'Update user onboarding', + description: 'Update the onboarding status of the current user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) async setUserOnboarding(@Auth() auth: AuthDto, @Body() Onboarding: OnboardingDto): Promise { return this.service.setOnboarding(auth, Onboarding); } @@ -104,12 +155,22 @@ export class UserController { @Delete('me/onboarding') @Authenticated({ permission: Permission.UserOnboardingDelete }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Delete user onboarding', + description: 'Delete the onboarding status of the current user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) async deleteUserOnboarding(@Auth() auth: AuthDto): Promise { await this.service.deleteOnboarding(auth); } @Get(':id') @Authenticated({ permission: Permission.UserRead }) + @Endpoint({ + summary: 'Retrieve a user', + description: 'Retrieve a specific user by their ID.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getUser(@Param() { id }: UUIDParamDto): Promise { return this.service.get(id); } @@ -119,6 +180,11 @@ export class UserController { @UseInterceptors(FileUploadInterceptor) @ApiConsumes('multipart/form-data') @ApiBody({ description: 'A new avatar for the user', type: CreateProfileImageDto }) + @Endpoint({ + summary: 'Create user profile image', + description: 'Upload and set a new profile image for the current user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) createProfileImage( @Auth() auth: AuthDto, @UploadedFile() fileInfo: Express.Multer.File, @@ -129,6 +195,11 @@ export class UserController { @Delete('profile-image') @Authenticated({ permission: Permission.UserProfileImageDelete }) @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Delete user profile image', + description: 'Delete the profile image of the current user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) deleteProfileImage(@Auth() auth: AuthDto): Promise { return this.service.deleteProfileImage(auth); } @@ -136,6 +207,11 @@ export class UserController { @Get(':id/profile-image') @FileResponse() @Authenticated({ permission: Permission.UserProfileImageRead }) + @Endpoint({ + summary: 'Retrieve user profile image', + description: 'Retrieve the profile image file for a user.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) async getProfileImage(@Res() res: Response, @Next() next: NextFunction, @Param() { id }: UUIDParamDto) { await sendFile(res, next, () => this.service.getProfileImage(id), this.logger); } diff --git a/server/src/controllers/view.controller.ts b/server/src/controllers/view.controller.ts index b5e281e093..8a977e15bc 100644 --- a/server/src/controllers/view.controller.ts +++ b/server/src/controllers/view.controller.ts @@ -1,23 +1,35 @@ import { Controller, Get, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AssetResponseDto } from 'src/dtos/asset-response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; +import { ApiTag } from 'src/enum'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { ViewService } from 'src/services/view.service'; -@ApiTags('View') +@ApiTags(ApiTag.Views) @Controller('view') export class ViewController { constructor(private service: ViewService) {} @Get('folder/unique-paths') @Authenticated() + @Endpoint({ + summary: 'Retrieve unique paths', + description: 'Retrieve a list of unique folder paths from asset original paths.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getUniqueOriginalPaths(@Auth() auth: AuthDto): Promise { return this.service.getUniqueOriginalPaths(auth); } @Get('folder') @Authenticated() + @Endpoint({ + summary: 'Retrieve assets by original path', + description: 'Retrieve assets that are children of a specific folder.', + history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), + }) getAssetsByOriginalPath(@Auth() auth: AuthDto, @Query('path') path: string): Promise { return this.service.getAssetsByOriginalPath(auth, path); } diff --git a/server/src/controllers/workflow.controller.ts b/server/src/controllers/workflow.controller.ts new file mode 100644 index 0000000000..e07b6443f4 --- /dev/null +++ b/server/src/controllers/workflow.controller.ts @@ -0,0 +1,76 @@ +import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { Endpoint, HistoryBuilder } from 'src/decorators'; +import { AuthDto } from 'src/dtos/auth.dto'; +import { WorkflowCreateDto, WorkflowResponseDto, WorkflowUpdateDto } from 'src/dtos/workflow.dto'; +import { Permission } from 'src/enum'; +import { Auth, Authenticated } from 'src/middleware/auth.guard'; +import { WorkflowService } from 'src/services/workflow.service'; +import { UUIDParamDto } from 'src/validation'; + +@ApiTags('Workflows') +@Controller('workflows') +export class WorkflowController { + constructor(private service: WorkflowService) {} + + @Post() + @Authenticated({ permission: Permission.WorkflowCreate }) + @Endpoint({ + summary: 'Create a workflow', + description: 'Create a new workflow, the workflow can also be created with empty filters and actions.', + history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), + }) + createWorkflow(@Auth() auth: AuthDto, @Body() dto: WorkflowCreateDto): Promise { + return this.service.create(auth, dto); + } + + @Get() + @Authenticated({ permission: Permission.WorkflowRead }) + @Endpoint({ + summary: 'List all workflows', + description: 'Retrieve a list of workflows available to the authenticated user.', + history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), + }) + getWorkflows(@Auth() auth: AuthDto): Promise { + return this.service.getAll(auth); + } + + @Get(':id') + @Authenticated({ permission: Permission.WorkflowRead }) + @Endpoint({ + summary: 'Retrieve a workflow', + description: 'Retrieve information about a specific workflow by its ID.', + history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), + }) + getWorkflow(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { + return this.service.get(auth, id); + } + + @Put(':id') + @Authenticated({ permission: Permission.WorkflowUpdate }) + @Endpoint({ + summary: 'Update a workflow', + description: + 'Update the information of a specific workflow by its ID. This endpoint can be used to update the workflow name, description, trigger type, filters and actions order, etc.', + history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), + }) + updateWorkflow( + @Auth() auth: AuthDto, + @Param() { id }: UUIDParamDto, + @Body() dto: WorkflowUpdateDto, + ): Promise { + return this.service.update(auth, id, dto); + } + + @Delete(':id') + @Authenticated({ permission: Permission.WorkflowDelete }) + @HttpCode(HttpStatus.NO_CONTENT) + @Endpoint({ + summary: 'Delete a workflow', + description: 'Delete a workflow by its ID.', + history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), + }) + deleteWorkflow(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { + return this.service.delete(auth, id); + } +} diff --git a/server/src/cores/storage.core.spec.ts b/server/src/cores/storage.core.spec.ts index ed446f9259..08e410bbe3 100644 --- a/server/src/cores/storage.core.spec.ts +++ b/server/src/cores/storage.core.spec.ts @@ -2,8 +2,6 @@ import { StorageCore } from 'src/cores/storage.core'; import { vitest } from 'vitest'; vitest.mock('src/constants', () => ({ - ADDED_IN_PREFIX: 'This property was added in ', - DEPRECATED_IN_PREFIX: 'This property was deprecated in ', IWorker: 'IWorker', })); diff --git a/server/src/database.ts b/server/src/database.ts index f472c643ee..4aa69127ff 100644 --- a/server/src/database.ts +++ b/server/src/database.ts @@ -7,6 +7,8 @@ import { AssetVisibility, MemoryType, Permission, + PluginContext, + PluginTriggerType, SharedLinkType, SourceType, UserAvatarColor, @@ -14,7 +16,10 @@ import { } from 'src/enum'; import { AlbumTable } from 'src/schema/tables/album.table'; import { AssetExifTable } from 'src/schema/tables/asset-exif.table'; +import { PluginActionTable, PluginFilterTable, PluginTable } from 'src/schema/tables/plugin.table'; +import { WorkflowActionTable, WorkflowFilterTable, WorkflowTable } from 'src/schema/tables/workflow.table'; import { UserMetadataItem } from 'src/types'; +import type { ActionConfig, FilterConfig, JSONSchema } from 'src/types/plugin-schema.types'; export type AuthUser = { id: string; @@ -238,6 +243,7 @@ export type Session = { expiresAt: Date | null; deviceOS: string; deviceType: string; + appVersion: string | null; pinExpiresAt: Date | null; isPendingSyncReset: boolean; }; @@ -276,6 +282,45 @@ export type AssetFace = { updateId: string; }; +export type Plugin = Selectable; + +export type PluginFilter = Selectable & { + methodName: string; + title: string; + description: string; + supportedContexts: PluginContext[]; + schema: JSONSchema | null; +}; + +export type PluginAction = Selectable & { + methodName: string; + title: string; + description: string; + supportedContexts: PluginContext[]; + schema: JSONSchema | null; +}; + +export type Workflow = Selectable & { + triggerType: PluginTriggerType; + name: string | null; + description: string; + enabled: boolean; +}; + +export type WorkflowFilter = Selectable & { + workflowId: string; + filterId: string; + filterConfig: FilterConfig | null; + order: number; +}; + +export type WorkflowAction = Selectable & { + workflowId: string; + actionId: string; + actionConfig: ActionConfig | null; + order: number; +}; + const userColumns = ['id', 'name', 'email', 'avatarColor', 'profileImagePath', 'profileChangedAt'] as const; const userWithPrefixColumns = [ 'user2.id', @@ -308,7 +353,7 @@ export const columns = { assetFiles: ['asset_file.id', 'asset_file.path', 'asset_file.type'], authUser: ['user.id', 'user.name', 'user.email', 'user.isAdmin', 'user.quotaUsageInBytes', 'user.quotaSizeInBytes'], authApiKey: ['api_key.id', 'api_key.permissions'], - authSession: ['session.id', 'session.updatedAt', 'session.pinExpiresAt'], + authSession: ['session.id', 'session.updatedAt', 'session.pinExpiresAt', 'session.appVersion'], authSharedLink: [ 'shared_link.id', 'shared_link.userId', @@ -355,7 +400,7 @@ export const columns = { 'asset.stackId', 'asset.libraryId', ], - syncAlbumUser: ['album_user.albumsId as albumId', 'album_user.usersId as userId', 'album_user.role'], + syncAlbumUser: ['album_user.albumId as albumId', 'album_user.userId as userId', 'album_user.role'], syncStack: ['stack.id', 'stack.createdAt', 'stack.updatedAt', 'stack.primaryAssetId', 'stack.ownerId'], syncUser: ['id', 'name', 'email', 'avatarColor', 'deletedAt', 'updateId', 'profileImagePath', 'profileChangedAt'], stack: ['stack.id', 'stack.primaryAssetId', 'ownerId'], @@ -417,4 +462,15 @@ export const columns = { 'asset_exif.state', 'asset_exif.timeZone', ], + plugin: [ + 'plugin.id as id', + 'plugin.name as name', + 'plugin.title as title', + 'plugin.description as description', + 'plugin.author as author', + 'plugin.version as version', + 'plugin.wasmPath as wasmPath', + 'plugin.createdAt as createdAt', + 'plugin.updatedAt as updatedAt', + ], } as const; diff --git a/server/src/decorators.ts b/server/src/decorators.ts index 2f1e76d097..054bbf8fec 100644 --- a/server/src/decorators.ts +++ b/server/src/decorators.ts @@ -1,8 +1,7 @@ import { SetMetadata, applyDecorators } from '@nestjs/common'; -import { ApiExtension, ApiOperation, ApiOperationOptions, ApiProperty, ApiTags } from '@nestjs/swagger'; +import { ApiOperation, ApiOperationOptions, ApiProperty, ApiPropertyOptions, ApiTags } from '@nestjs/swagger'; import _ from 'lodash'; -import { ADDED_IN_PREFIX, DEPRECATED_IN_PREFIX, LIFECYCLE_EXTENSION } from 'src/constants'; -import { ImmichWorker, JobName, MetadataKey, QueueName } from 'src/enum'; +import { ApiCustomExtension, ApiTag, ImmichWorker, JobName, MetadataKey, QueueName } from 'src/enum'; import { EmitEvent } from 'src/repositories/event.repository'; import { immich_uuid_v7, updated_at } from 'src/schema/functions'; import { BeforeUpdateTrigger, Column, ColumnOptions } from 'src/sql-tools'; @@ -87,7 +86,7 @@ export function Chunked( return Promise.all( chunks(argument, chunkSize).map(async (chunk) => { - await Reflect.apply(originalMethod, this, [ + return await Reflect.apply(originalMethod, this, [ ...arguments_.slice(0, parameterIndex), chunk, ...arguments_.slice(parameterIndex + 1), @@ -103,7 +102,7 @@ export function ChunkedArray(options?: { paramIndex?: number }): MethodDecorator } export function ChunkedSet(options?: { paramIndex?: number }): MethodDecorator { - return Chunked({ ...options, mergeFn: setUnion }); + return Chunked({ ...options, mergeFn: (args: Set[]) => setUnion(...args) }); } const UUID = '00000000-0000-4000-a000-000000000000'; @@ -153,39 +152,122 @@ export type JobConfig = { }; export const OnJob = (config: JobConfig) => SetMetadata(MetadataKey.JobConfig, config); -type LifecycleRelease = 'NEXT_RELEASE' | string; -type LifecycleMetadata = { - addedAt?: LifecycleRelease; - deprecatedAt?: LifecycleRelease; -}; +type EndpointOptions = ApiOperationOptions & { history?: HistoryBuilder }; +export const Endpoint = ({ history, ...options }: EndpointOptions) => { + const decorators: MethodDecorator[] = []; + const extensions = history?.getExtensions() ?? {}; -export const EndpointLifecycle = ({ - addedAt, - deprecatedAt, - description, - ...options -}: LifecycleMetadata & ApiOperationOptions) => { - const decorators: MethodDecorator[] = [ApiExtension(LIFECYCLE_EXTENSION, { addedAt, deprecatedAt })]; - if (deprecatedAt) { - decorators.push( - ApiTags('Deprecated'), - ApiOperation({ - deprecated: true, - description: DEPRECATED_IN_PREFIX + deprecatedAt + (description ? `. ${description}` : ''), - ...options, - }), - ); + if (!extensions[ApiCustomExtension.History]) { + console.log(`Missing history for endpoint: ${options.summary}`); } + if (history?.isDeprecated()) { + options.deprecated = true; + decorators.push(ApiTags(ApiTag.Deprecated)); + } + + decorators.push(ApiOperation({ ...options, ...extensions })); + return applyDecorators(...decorators); }; -export const PropertyLifecycle = ({ addedAt, deprecatedAt }: LifecycleMetadata) => { - const decorators: PropertyDecorator[] = []; - decorators.push(ApiProperty({ description: ADDED_IN_PREFIX + addedAt })); - if (deprecatedAt) { - decorators.push(ApiProperty({ deprecated: true, description: DEPRECATED_IN_PREFIX + deprecatedAt })); +type PropertyOptions = ApiPropertyOptions & { history?: HistoryBuilder }; +export const Property = ({ history, ...options }: PropertyOptions) => { + const extensions = history?.getExtensions() ?? {}; + + if (history?.isDeprecated()) { + options.deprecated = true; } - return applyDecorators(...decorators); + return ApiProperty({ ...options, ...extensions }); }; + +type HistoryEntry = { + version: string; + state: ApiState | 'Added' | 'Updated'; + description?: string; + replacementId?: string; +}; + +type DeprecatedOptions = { + /** replacement operationId */ + replacementId?: string; +}; + +type CustomExtensions = { + [ApiCustomExtension.State]?: ApiState; + [ApiCustomExtension.History]?: HistoryEntry[]; +}; + +enum ApiState { + 'Stable' = 'Stable', + 'Alpha' = 'Alpha', + 'Beta' = 'Beta', + 'Internal' = 'Internal', + 'Deprecated' = 'Deprecated', +} +export class HistoryBuilder { + private hasDeprecated = false; + private items: HistoryEntry[] = []; + + added(version: string, description?: string) { + return this.push({ version, state: 'Added', description }); + } + + updated(version: string, description: string) { + return this.push({ version, state: 'Updated', description }); + } + + alpha(version: string) { + return this.push({ version, state: ApiState.Alpha }); + } + + beta(version: string) { + return this.push({ version, state: ApiState.Beta }); + } + + internal(version: string) { + return this.push({ version, state: ApiState.Internal }); + } + + stable(version: string) { + return this.push({ version, state: ApiState.Stable }); + } + + deprecated(version: string, options?: DeprecatedOptions) { + const { replacementId } = options || {}; + this.hasDeprecated = true; + return this.push({ version, state: ApiState.Deprecated, replacementId }); + } + + isDeprecated(): boolean { + return this.hasDeprecated; + } + + getExtensions() { + const extensions: CustomExtensions = {}; + + if (this.items.length > 0) { + extensions[ApiCustomExtension.History] = this.items; + } + + for (const item of this.items.toReversed()) { + if (item.state === 'Added' || item.state === 'Updated') { + continue; + } + + extensions[ApiCustomExtension.State] = item.state; + break; + } + + return extensions; + } + + private push(item: HistoryEntry) { + if (!item.version.startsWith('v')) { + throw new Error(`Version string must start with 'v': received '${JSON.stringify(item)}'`); + } + this.items.push(item); + return this; + } +} diff --git a/server/src/dtos/album.dto.ts b/server/src/dtos/album.dto.ts index 00f5759aac..2f3f22099a 100644 --- a/server/src/dtos/album.dto.ts +++ b/server/src/dtos/album.dto.ts @@ -128,6 +128,14 @@ export class AlbumUserResponseDto { role!: AlbumUserRole; } +export class ContributorCountResponseDto { + @ApiProperty() + userId!: string; + + @ApiProperty({ type: 'integer' }) + assetCount!: number; +} + export class AlbumResponseDto { id!: string; ownerId!: string; @@ -149,6 +157,11 @@ export class AlbumResponseDto { isActivityEnabled!: boolean; @ValidateEnum({ enum: AssetOrder, name: 'AssetOrder', optional: true }) order?: AssetOrder; + + // Optional per-user contribution counts for shared albums + @Type(() => ContributorCountResponseDto) + @ApiProperty({ type: [ContributorCountResponseDto], required: false }) + contributorCounts?: ContributorCountResponseDto[]; } export type MapAlbumDto = { diff --git a/server/src/dtos/asset-response.dto.ts b/server/src/dtos/asset-response.dto.ts index f60f2a8824..1716c327f3 100644 --- a/server/src/dtos/asset-response.dto.ts +++ b/server/src/dtos/asset-response.dto.ts @@ -1,7 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { Selectable } from 'kysely'; import { AssetFace, AssetFile, Exif, Stack, Tag, User } from 'src/database'; -import { PropertyLifecycle } from 'src/decorators'; +import { HistoryBuilder, Property } from 'src/decorators'; import { AuthDto } from 'src/dtos/auth.dto'; import { ExifResponseDto, mapExif } from 'src/dtos/exif.dto'; import { @@ -48,7 +48,7 @@ export class AssetResponseDto extends SanitizedAssetResponseDto { deviceId!: string; ownerId!: string; owner?: UserResponseDto; - @PropertyLifecycle({ deprecatedAt: 'v1.106.0' }) + @Property({ history: new HistoryBuilder().added('v1').deprecated('v1') }) libraryId?: string | null; originalPath!: string; originalFileName!: string; @@ -91,7 +91,7 @@ export class AssetResponseDto extends SanitizedAssetResponseDto { stack?: AssetStackResponseDto | null; duplicateId?: string | null; - @PropertyLifecycle({ deprecatedAt: 'v1.113.0' }) + @Property({ history: new HistoryBuilder().added('v1').deprecated('v1.113.0') }) resized?: boolean; } @@ -212,7 +212,7 @@ export function mapAsset(entity: MapAsset, options: AssetMapOptions = {}): Asset fileModifiedAt: entity.fileModifiedAt, localDateTime: entity.localDateTime, updatedAt: entity.updatedAt, - isFavorite: options.auth?.user.id === entity.ownerId ? entity.isFavorite : false, + isFavorite: options.auth?.user.id === entity.ownerId && entity.isFavorite, isArchived: entity.visibility === AssetVisibility.Archive, isTrashed: !!entity.deletedAt, visibility: entity.visibility, diff --git a/server/src/dtos/asset.dto.ts b/server/src/dtos/asset.dto.ts index 6a89b7e2cf..dc43a0200c 100644 --- a/server/src/dtos/asset.dto.ts +++ b/server/src/dtos/asset.dto.ts @@ -186,6 +186,29 @@ export class AssetMetadataResponseDto { updatedAt!: Date; } +export class AssetCopyDto { + @ValidateUUID() + sourceId!: string; + + @ValidateUUID() + targetId!: string; + + @ValidateBoolean({ optional: true, default: true }) + sharedLinks?: boolean; + + @ValidateBoolean({ optional: true, default: true }) + albums?: boolean; + + @ValidateBoolean({ optional: true, default: true }) + sidecar?: boolean; + + @ValidateBoolean({ optional: true, default: true }) + stack?: boolean; + + @ValidateBoolean({ optional: true, default: true }) + favorite?: boolean; +} + export const mapStats = (stats: AssetStats): AssetStatsResponseDto => { return { images: stats[AssetType.Image], diff --git a/server/src/dtos/auth.dto.ts b/server/src/dtos/auth.dto.ts index 2bb98b34a5..d700fc2ab8 100644 --- a/server/src/dtos/auth.dto.ts +++ b/server/src/dtos/auth.dto.ts @@ -4,7 +4,7 @@ import { IsEmail, IsNotEmpty, IsString, MinLength } from 'class-validator'; import { AuthApiKey, AuthSession, AuthSharedLink, AuthUser, UserAdmin } from 'src/database'; import { ImmichCookie, UserMetadataKey } from 'src/enum'; import { UserMetadataItem } from 'src/types'; -import { Optional, PinCode, toEmail } from 'src/validation'; +import { Optional, PinCode, toEmail, ValidateBoolean } from 'src/validation'; export type CookieResponse = { isSecure: boolean; @@ -83,6 +83,9 @@ export class ChangePasswordDto { @MinLength(8) @ApiProperty({ example: 'password' }) newPassword!: string; + + @ValidateBoolean({ optional: true, default: false }) + invalidateSessions?: boolean; } export class PinCodeSetupDto { diff --git a/server/src/dtos/env.dto.ts b/server/src/dtos/env.dto.ts index 3543d8dae9..2a9dd8b662 100644 --- a/server/src/dtos/env.dto.ts +++ b/server/src/dtos/env.dto.ts @@ -57,6 +57,13 @@ export class EnvDto { @Type(() => Number) IMMICH_MICROSERVICES_METRICS_PORT?: number; + @ValidateBoolean({ optional: true }) + IMMICH_PLUGINS_ENABLED?: boolean; + + @Optional() + @Matches(/^\//, { message: 'IMMICH_PLUGINS_INSTALL_FOLDER must be an absolute path' }) + IMMICH_PLUGINS_INSTALL_FOLDER?: string; + @IsInt() @Optional() @Type(() => Number) diff --git a/server/src/dtos/job.dto.ts b/server/src/dtos/job.dto.ts index 2123b65878..794af6e5e0 100644 --- a/server/src/dtos/job.dto.ts +++ b/server/src/dtos/job.dto.ts @@ -1,96 +1,7 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { JobCommand, ManualJobName, QueueName } from 'src/enum'; -import { ValidateBoolean, ValidateEnum } from 'src/validation'; - -export class JobIdParamDto { - @ValidateEnum({ enum: QueueName, name: 'JobName' }) - id!: QueueName; -} - -export class JobCommandDto { - @ValidateEnum({ enum: JobCommand, name: 'JobCommand' }) - command!: JobCommand; - - @ValidateBoolean({ optional: true }) - force?: boolean; // TODO: this uses undefined as a third state, which should be refactored to be more explicit -} +import { ManualJobName } from 'src/enum'; +import { ValidateEnum } from 'src/validation'; export class JobCreateDto { @ValidateEnum({ enum: ManualJobName, name: 'ManualJobName' }) name!: ManualJobName; } - -export class JobCountsDto { - @ApiProperty({ type: 'integer' }) - active!: number; - @ApiProperty({ type: 'integer' }) - completed!: number; - @ApiProperty({ type: 'integer' }) - failed!: number; - @ApiProperty({ type: 'integer' }) - delayed!: number; - @ApiProperty({ type: 'integer' }) - waiting!: number; - @ApiProperty({ type: 'integer' }) - paused!: number; -} - -export class QueueStatusDto { - isActive!: boolean; - isPaused!: boolean; -} - -export class JobStatusDto { - @ApiProperty({ type: JobCountsDto }) - jobCounts!: JobCountsDto; - - @ApiProperty({ type: QueueStatusDto }) - queueStatus!: QueueStatusDto; -} - -export class AllJobStatusResponseDto implements Record { - @ApiProperty({ type: JobStatusDto }) - [QueueName.ThumbnailGeneration]!: JobStatusDto; - - @ApiProperty({ type: JobStatusDto }) - [QueueName.MetadataExtraction]!: JobStatusDto; - - @ApiProperty({ type: JobStatusDto }) - [QueueName.VideoConversion]!: JobStatusDto; - - @ApiProperty({ type: JobStatusDto }) - [QueueName.SmartSearch]!: JobStatusDto; - - @ApiProperty({ type: JobStatusDto }) - [QueueName.StorageTemplateMigration]!: JobStatusDto; - - @ApiProperty({ type: JobStatusDto }) - [QueueName.Migration]!: JobStatusDto; - - @ApiProperty({ type: JobStatusDto }) - [QueueName.BackgroundTask]!: JobStatusDto; - - @ApiProperty({ type: JobStatusDto }) - [QueueName.Search]!: JobStatusDto; - - @ApiProperty({ type: JobStatusDto }) - [QueueName.DuplicateDetection]!: JobStatusDto; - - @ApiProperty({ type: JobStatusDto }) - [QueueName.FaceDetection]!: JobStatusDto; - - @ApiProperty({ type: JobStatusDto }) - [QueueName.FacialRecognition]!: JobStatusDto; - - @ApiProperty({ type: JobStatusDto }) - [QueueName.Sidecar]!: JobStatusDto; - - @ApiProperty({ type: JobStatusDto }) - [QueueName.Library]!: JobStatusDto; - - @ApiProperty({ type: JobStatusDto }) - [QueueName.Notification]!: JobStatusDto; - - @ApiProperty({ type: JobStatusDto }) - [QueueName.BackupDatabase]!: JobStatusDto; -} diff --git a/server/src/dtos/memory.dto.ts b/server/src/dtos/memory.dto.ts index a79511c73e..8e7320f831 100644 --- a/server/src/dtos/memory.dto.ts +++ b/server/src/dtos/memory.dto.ts @@ -4,8 +4,8 @@ import { IsInt, IsObject, IsPositive, ValidateNested } from 'class-validator'; import { Memory } from 'src/database'; import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; -import { MemoryType } from 'src/enum'; -import { ValidateBoolean, ValidateDate, ValidateEnum, ValidateUUID } from 'src/validation'; +import { AssetOrderWithRandom, MemoryType } from 'src/enum'; +import { Optional, ValidateBoolean, ValidateDate, ValidateEnum, ValidateUUID } from 'src/validation'; class MemoryBaseDto { @ValidateBoolean({ optional: true }) @@ -27,6 +27,16 @@ export class MemorySearchDto { @ValidateBoolean({ optional: true }) isSaved?: boolean; + + @IsInt() + @IsPositive() + @Type(() => Number) + @Optional() + @ApiProperty({ type: 'integer', description: 'Number of memories to return' }) + size?: number; + + @ValidateEnum({ enum: AssetOrderWithRandom, name: 'MemorySearchOrder', optional: true }) + order?: AssetOrderWithRandom; } class OnThisDayDto { diff --git a/server/src/dtos/model-config.dto.ts b/server/src/dtos/model-config.dto.ts index f8b9e2043f..527317346a 100644 --- a/server/src/dtos/model-config.dto.ts +++ b/server/src/dtos/model-config.dto.ts @@ -46,3 +46,25 @@ export class FacialRecognitionConfig extends ModelConfig { @ApiProperty({ type: 'integer' }) minFaces!: number; } + +export class OcrConfig extends ModelConfig { + @IsNumber() + @Min(1) + @Type(() => Number) + @ApiProperty({ type: 'integer' }) + maxResolution!: number; + + @IsNumber() + @Min(0.1) + @Max(1) + @Type(() => Number) + @ApiProperty({ type: 'number', format: 'double' }) + minDetectionScore!: number; + + @IsNumber() + @Min(0.1) + @Max(1) + @Type(() => Number) + @ApiProperty({ type: 'number', format: 'double' }) + minRecognitionScore!: number; +} diff --git a/server/src/dtos/ocr.dto.ts b/server/src/dtos/ocr.dto.ts new file mode 100644 index 0000000000..1e838d0ec0 --- /dev/null +++ b/server/src/dtos/ocr.dto.ts @@ -0,0 +1,42 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class AssetOcrResponseDto { + @ApiProperty({ type: 'string', format: 'uuid' }) + id!: string; + + @ApiProperty({ type: 'string', format: 'uuid' }) + assetId!: string; + + @ApiProperty({ type: 'number', format: 'double', description: 'Normalized x coordinate of box corner 1 (0-1)' }) + x1!: number; + + @ApiProperty({ type: 'number', format: 'double', description: 'Normalized y coordinate of box corner 1 (0-1)' }) + y1!: number; + + @ApiProperty({ type: 'number', format: 'double', description: 'Normalized x coordinate of box corner 2 (0-1)' }) + x2!: number; + + @ApiProperty({ type: 'number', format: 'double', description: 'Normalized y coordinate of box corner 2 (0-1)' }) + y2!: number; + + @ApiProperty({ type: 'number', format: 'double', description: 'Normalized x coordinate of box corner 3 (0-1)' }) + x3!: number; + + @ApiProperty({ type: 'number', format: 'double', description: 'Normalized y coordinate of box corner 3 (0-1)' }) + y3!: number; + + @ApiProperty({ type: 'number', format: 'double', description: 'Normalized x coordinate of box corner 4 (0-1)' }) + x4!: number; + + @ApiProperty({ type: 'number', format: 'double', description: 'Normalized y coordinate of box corner 4 (0-1)' }) + y4!: number; + + @ApiProperty({ type: 'number', format: 'double', description: 'Confidence score for text detection box' }) + boxScore!: number; + + @ApiProperty({ type: 'number', format: 'double', description: 'Confidence score for text recognition' }) + textScore!: number; + + @ApiProperty({ type: 'string', description: 'Recognized text' }) + text!: string; +} diff --git a/server/src/dtos/person.dto.ts b/server/src/dtos/person.dto.ts index f9b41627d9..3c90cfdc59 100644 --- a/server/src/dtos/person.dto.ts +++ b/server/src/dtos/person.dto.ts @@ -4,7 +4,7 @@ import { IsArray, IsInt, IsNotEmpty, IsNumber, IsString, Max, Min, ValidateNeste import { Selectable } from 'kysely'; import { DateTime } from 'luxon'; import { AssetFace, Person } from 'src/database'; -import { PropertyLifecycle } from 'src/decorators'; +import { HistoryBuilder, Property } from 'src/decorators'; import { AuthDto } from 'src/dtos/auth.dto'; import { SourceType } from 'src/enum'; import { AssetFaceTable } from 'src/schema/tables/asset-face.table'; @@ -111,11 +111,11 @@ export class PersonResponseDto { birthDate!: string | null; thumbnailPath!: string; isHidden!: boolean; - @PropertyLifecycle({ addedAt: 'v1.107.0' }) + @Property({ history: new HistoryBuilder().added('v1.107.0').stable('v2') }) updatedAt?: Date; - @PropertyLifecycle({ addedAt: 'v1.126.0' }) + @Property({ history: new HistoryBuilder().added('v1.126.0').stable('v2') }) isFavorite?: boolean; - @PropertyLifecycle({ addedAt: 'v1.126.0' }) + @Property({ history: new HistoryBuilder().added('v1.126.0').stable('v2') }) color?: string; } @@ -216,7 +216,7 @@ export class PeopleResponseDto { people!: PersonResponseDto[]; // TODO: make required after a few versions - @PropertyLifecycle({ addedAt: 'v1.110.0' }) + @Property({ history: new HistoryBuilder().added('v1.110.0').stable('v2') }) hasNextPage?: boolean; } diff --git a/server/src/dtos/plugin-manifest.dto.ts b/server/src/dtos/plugin-manifest.dto.ts new file mode 100644 index 0000000000..fcb3ad4a22 --- /dev/null +++ b/server/src/dtos/plugin-manifest.dto.ts @@ -0,0 +1,110 @@ +import { Type } from 'class-transformer'; +import { + ArrayMinSize, + IsArray, + IsEnum, + IsNotEmpty, + IsObject, + IsOptional, + IsSemVer, + IsString, + Matches, + ValidateNested, +} from 'class-validator'; +import { PluginContext } from 'src/enum'; +import { JSONSchema } from 'src/types/plugin-schema.types'; +import { ValidateEnum } from 'src/validation'; + +class PluginManifestWasmDto { + @IsString() + @IsNotEmpty() + path!: string; +} + +class PluginManifestFilterDto { + @IsString() + @IsNotEmpty() + methodName!: string; + + @IsString() + @IsNotEmpty() + title!: string; + + @IsString() + @IsNotEmpty() + description!: string; + + @IsArray() + @ArrayMinSize(1) + @IsEnum(PluginContext, { each: true }) + supportedContexts!: PluginContext[]; + + @IsObject() + @IsOptional() + schema?: JSONSchema; +} + +class PluginManifestActionDto { + @IsString() + @IsNotEmpty() + methodName!: string; + + @IsString() + @IsNotEmpty() + title!: string; + + @IsString() + @IsNotEmpty() + description!: string; + + @IsArray() + @ArrayMinSize(1) + @ValidateEnum({ enum: PluginContext, name: 'PluginContext', each: true }) + supportedContexts!: PluginContext[]; + + @IsObject() + @IsOptional() + schema?: JSONSchema; +} + +export class PluginManifestDto { + @IsString() + @IsNotEmpty() + @Matches(/^[a-z0-9-]+[a-z0-9]$/, { + message: 'Plugin name must contain only lowercase letters, numbers, and hyphens, and cannot end with a hyphen', + }) + name!: string; + + @IsString() + @IsNotEmpty() + @IsSemVer() + version!: string; + + @IsString() + @IsNotEmpty() + title!: string; + + @IsString() + @IsNotEmpty() + description!: string; + + @IsString() + @IsNotEmpty() + author!: string; + + @ValidateNested() + @Type(() => PluginManifestWasmDto) + wasm!: PluginManifestWasmDto; + + @IsArray() + @ValidateNested({ each: true }) + @Type(() => PluginManifestFilterDto) + @IsOptional() + filters?: PluginManifestFilterDto[]; + + @IsArray() + @ValidateNested({ each: true }) + @Type(() => PluginManifestActionDto) + @IsOptional() + actions?: PluginManifestActionDto[]; +} diff --git a/server/src/dtos/plugin.dto.ts b/server/src/dtos/plugin.dto.ts new file mode 100644 index 0000000000..ce80eccd65 --- /dev/null +++ b/server/src/dtos/plugin.dto.ts @@ -0,0 +1,77 @@ +import { IsNotEmpty, IsString } from 'class-validator'; +import { PluginAction, PluginFilter } from 'src/database'; +import { PluginContext } from 'src/enum'; +import type { JSONSchema } from 'src/types/plugin-schema.types'; +import { ValidateEnum } from 'src/validation'; + +export class PluginResponseDto { + id!: string; + name!: string; + title!: string; + description!: string; + author!: string; + version!: string; + createdAt!: string; + updatedAt!: string; + filters!: PluginFilterResponseDto[]; + actions!: PluginActionResponseDto[]; +} + +export class PluginFilterResponseDto { + id!: string; + pluginId!: string; + methodName!: string; + title!: string; + description!: string; + + @ValidateEnum({ enum: PluginContext, name: 'PluginContext' }) + supportedContexts!: PluginContext[]; + schema!: JSONSchema | null; +} + +export class PluginActionResponseDto { + id!: string; + pluginId!: string; + methodName!: string; + title!: string; + description!: string; + + @ValidateEnum({ enum: PluginContext, name: 'PluginContext' }) + supportedContexts!: PluginContext[]; + schema!: JSONSchema | null; +} + +export class PluginInstallDto { + @IsString() + @IsNotEmpty() + manifestPath!: string; +} + +export type MapPlugin = { + id: string; + name: string; + title: string; + description: string; + author: string; + version: string; + wasmPath: string; + createdAt: Date; + updatedAt: Date; + filters: PluginFilter[]; + actions: PluginAction[]; +}; + +export function mapPlugin(plugin: MapPlugin): PluginResponseDto { + return { + id: plugin.id, + name: plugin.name, + title: plugin.title, + description: plugin.description, + author: plugin.author, + version: plugin.version, + createdAt: plugin.createdAt.toISOString(), + updatedAt: plugin.updatedAt.toISOString(), + filters: plugin.filters, + actions: plugin.actions, + }; +} diff --git a/server/src/dtos/queue.dto.ts b/server/src/dtos/queue.dto.ts new file mode 100644 index 0000000000..df00c5cfc2 --- /dev/null +++ b/server/src/dtos/queue.dto.ts @@ -0,0 +1,97 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { QueueCommand, QueueName } from 'src/enum'; +import { ValidateBoolean, ValidateEnum } from 'src/validation'; + +export class QueueNameParamDto { + @ValidateEnum({ enum: QueueName, name: 'QueueName' }) + name!: QueueName; +} + +export class QueueCommandDto { + @ValidateEnum({ enum: QueueCommand, name: 'QueueCommand' }) + command!: QueueCommand; + + @ValidateBoolean({ optional: true }) + force?: boolean; // TODO: this uses undefined as a third state, which should be refactored to be more explicit +} + +export class QueueStatisticsDto { + @ApiProperty({ type: 'integer' }) + active!: number; + @ApiProperty({ type: 'integer' }) + completed!: number; + @ApiProperty({ type: 'integer' }) + failed!: number; + @ApiProperty({ type: 'integer' }) + delayed!: number; + @ApiProperty({ type: 'integer' }) + waiting!: number; + @ApiProperty({ type: 'integer' }) + paused!: number; +} + +export class QueueStatusDto { + isActive!: boolean; + isPaused!: boolean; +} + +export class QueueResponseDto { + @ApiProperty({ type: QueueStatisticsDto }) + jobCounts!: QueueStatisticsDto; + + @ApiProperty({ type: QueueStatusDto }) + queueStatus!: QueueStatusDto; +} + +export class QueuesResponseDto implements Record { + @ApiProperty({ type: QueueResponseDto }) + [QueueName.ThumbnailGeneration]!: QueueResponseDto; + + @ApiProperty({ type: QueueResponseDto }) + [QueueName.MetadataExtraction]!: QueueResponseDto; + + @ApiProperty({ type: QueueResponseDto }) + [QueueName.VideoConversion]!: QueueResponseDto; + + @ApiProperty({ type: QueueResponseDto }) + [QueueName.SmartSearch]!: QueueResponseDto; + + @ApiProperty({ type: QueueResponseDto }) + [QueueName.StorageTemplateMigration]!: QueueResponseDto; + + @ApiProperty({ type: QueueResponseDto }) + [QueueName.Migration]!: QueueResponseDto; + + @ApiProperty({ type: QueueResponseDto }) + [QueueName.BackgroundTask]!: QueueResponseDto; + + @ApiProperty({ type: QueueResponseDto }) + [QueueName.Search]!: QueueResponseDto; + + @ApiProperty({ type: QueueResponseDto }) + [QueueName.DuplicateDetection]!: QueueResponseDto; + + @ApiProperty({ type: QueueResponseDto }) + [QueueName.FaceDetection]!: QueueResponseDto; + + @ApiProperty({ type: QueueResponseDto }) + [QueueName.FacialRecognition]!: QueueResponseDto; + + @ApiProperty({ type: QueueResponseDto }) + [QueueName.Sidecar]!: QueueResponseDto; + + @ApiProperty({ type: QueueResponseDto }) + [QueueName.Library]!: QueueResponseDto; + + @ApiProperty({ type: QueueResponseDto }) + [QueueName.Notification]!: QueueResponseDto; + + @ApiProperty({ type: QueueResponseDto }) + [QueueName.BackupDatabase]!: QueueResponseDto; + + @ApiProperty({ type: QueueResponseDto }) + [QueueName.Ocr]!: QueueResponseDto; + + @ApiProperty({ type: QueueResponseDto }) + [QueueName.Workflow]!: QueueResponseDto; +} diff --git a/server/src/dtos/search.dto.ts b/server/src/dtos/search.dto.ts index 5f8b018afe..068cd6630c 100644 --- a/server/src/dtos/search.dto.ts +++ b/server/src/dtos/search.dto.ts @@ -2,7 +2,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { IsInt, IsNotEmpty, IsString, Max, Min } from 'class-validator'; import { Place } from 'src/database'; -import { PropertyLifecycle } from 'src/decorators'; +import { HistoryBuilder, Property } from 'src/decorators'; import { AlbumResponseDto } from 'src/dtos/album.dto'; import { AssetResponseDto } from 'src/dtos/asset-response.dto'; import { AssetOrder, AssetType, AssetVisibility } from 'src/enum'; @@ -101,6 +101,11 @@ class BaseSearchDto { @Max(5) @Min(-1) rating?: number; + + @IsString() + @IsNotEmpty() + @Optional() + ocr?: string; } class BaseSearchWithResultsDto extends BaseSearchDto { @@ -249,6 +254,7 @@ export enum SearchSuggestionType { CITY = 'city', CAMERA_MAKE = 'camera-make', CAMERA_MODEL = 'camera-model', + CAMERA_LENS_MODEL = 'camera-lens-model', } export class SearchSuggestionRequestDto { @@ -271,8 +277,12 @@ export class SearchSuggestionRequestDto { @Optional() model?: string; + @IsString() + @Optional() + lensModel?: string; + @ValidateBoolean({ optional: true }) - @PropertyLifecycle({ addedAt: 'v111.0.0' }) + @Property({ history: new HistoryBuilder().added('v1.111.0').stable('v2') }) includeNull?: boolean; } diff --git a/server/src/dtos/server.dto.ts b/server/src/dtos/server.dto.ts index 47442ad4fb..d7589c8a29 100644 --- a/server/src/dtos/server.dto.ts +++ b/server/src/dtos/server.dto.ts @@ -171,6 +171,7 @@ export class ServerFeaturesDto { sidecar!: boolean; search!: boolean; email!: boolean; + ocr!: boolean; } export interface ReleaseNotification { diff --git a/server/src/dtos/session.dto.ts b/server/src/dtos/session.dto.ts index 7ccc72a5f1..49351eda52 100644 --- a/server/src/dtos/session.dto.ts +++ b/server/src/dtos/session.dto.ts @@ -34,6 +34,7 @@ export class SessionResponseDto { current!: boolean; deviceType!: string; deviceOS!: string; + appVersion!: string | null; isPendingSyncReset!: boolean; } @@ -47,6 +48,7 @@ export const mapSession = (entity: Session, currentId?: string): SessionResponse updatedAt: entity.updatedAt.toISOString(), expiresAt: entity.expiresAt?.toISOString(), current: currentId === entity.id, + appVersion: entity.appVersion, deviceOS: entity.deviceOS, deviceType: entity.deviceType, isPendingSyncReset: entity.isPendingSyncReset, diff --git a/server/src/dtos/system-config.dto.ts b/server/src/dtos/system-config.dto.ts index 1facc6c331..c835073c31 100644 --- a/server/src/dtos/system-config.dto.ts +++ b/server/src/dtos/system-config.dto.ts @@ -15,7 +15,7 @@ import { ValidateNested, } from 'class-validator'; import { SystemConfig } from 'src/config'; -import { CLIPConfig, DuplicateDetectionConfig, FacialRecognitionConfig } from 'src/dtos/model-config.dto'; +import { CLIPConfig, DuplicateDetectionConfig, FacialRecognitionConfig, OcrConfig } from 'src/dtos/model-config.dto'; import { AudioCodec, CQMode, @@ -201,6 +201,12 @@ class SystemConfigJobDto implements Record @Type(() => JobSettingsDto) [QueueName.FaceDetection]!: JobSettingsDto; + @ApiProperty({ type: JobSettingsDto }) + @ValidateNested() + @IsObject() + @Type(() => JobSettingsDto) + [QueueName.Ocr]!: JobSettingsDto; + @ApiProperty({ type: JobSettingsDto }) @ValidateNested() @IsObject() @@ -218,6 +224,12 @@ class SystemConfigJobDto implements Record @IsObject() @Type(() => JobSettingsDto) [QueueName.Notification]!: JobSettingsDto; + + @ApiProperty({ type: JobSettingsDto }) + @ValidateNested() + @IsObject() + @Type(() => JobSettingsDto) + [QueueName.Workflow]!: JobSettingsDto; } class SystemConfigLibraryScanDto { @@ -296,6 +308,11 @@ class SystemConfigMachineLearningDto { @ValidateNested() @IsObject() facialRecognition!: FacialRecognitionConfig; + + @Type(() => OcrConfig) + @ValidateNested() + @IsObject() + ocr!: OcrConfig; } enum MapTheme { @@ -463,6 +480,9 @@ class SystemConfigSmtpTransportDto { @Max(65_535) port!: number; + @ValidateBoolean() + secure!: boolean; + @IsString() username!: string; diff --git a/server/src/dtos/user-preferences.dto.ts b/server/src/dtos/user-preferences.dto.ts index b258158ae2..452384b423 100644 --- a/server/src/dtos/user-preferences.dto.ts +++ b/server/src/dtos/user-preferences.dto.ts @@ -13,6 +13,12 @@ class AvatarUpdate { class MemoriesUpdate { @ValidateBoolean({ optional: true }) enabled?: boolean; + + @Optional() + @IsInt() + @IsPositive() + @ApiProperty({ type: 'integer' }) + duration?: number; } class RatingsUpdate { @@ -166,6 +172,9 @@ class RatingsResponse { class MemoriesResponse { enabled: boolean = true; + + @ApiProperty({ type: 'integer' }) + duration: number = 5; } class FoldersResponse { diff --git a/server/src/dtos/user.dto.ts b/server/src/dtos/user.dto.ts index 443178aa10..c5067f3e8d 100644 --- a/server/src/dtos/user.dto.ts +++ b/server/src/dtos/user.dto.ts @@ -173,6 +173,7 @@ export function mapUserAdmin(entity: UserAdmin): UserAdminResponseDto { const license = metadata.find( (item): item is UserMetadataItem => item.key === UserMetadataKey.License, )?.value; + return { ...mapUser(entity), storageLabel: entity.storageLabel, diff --git a/server/src/dtos/workflow.dto.ts b/server/src/dtos/workflow.dto.ts new file mode 100644 index 0000000000..307440945d --- /dev/null +++ b/server/src/dtos/workflow.dto.ts @@ -0,0 +1,120 @@ +import { Type } from 'class-transformer'; +import { IsNotEmpty, IsObject, IsString, IsUUID, ValidateNested } from 'class-validator'; +import { WorkflowAction, WorkflowFilter } from 'src/database'; +import { PluginTriggerType } from 'src/enum'; +import type { ActionConfig, FilterConfig } from 'src/types/plugin-schema.types'; +import { Optional, ValidateBoolean, ValidateEnum } from 'src/validation'; + +export class WorkflowFilterItemDto { + @IsUUID() + filterId!: string; + + @IsObject() + @Optional() + filterConfig?: FilterConfig; +} + +export class WorkflowActionItemDto { + @IsUUID() + actionId!: string; + + @IsObject() + @Optional() + actionConfig?: ActionConfig; +} + +export class WorkflowCreateDto { + @ValidateEnum({ enum: PluginTriggerType, name: 'PluginTriggerType' }) + triggerType!: PluginTriggerType; + + @IsString() + @IsNotEmpty() + name!: string; + + @IsString() + @Optional() + description?: string; + + @ValidateBoolean({ optional: true }) + enabled?: boolean; + + @ValidateNested({ each: true }) + @Type(() => WorkflowFilterItemDto) + filters!: WorkflowFilterItemDto[]; + + @ValidateNested({ each: true }) + @Type(() => WorkflowActionItemDto) + actions!: WorkflowActionItemDto[]; +} + +export class WorkflowUpdateDto { + @IsString() + @IsNotEmpty() + @Optional() + name?: string; + + @IsString() + @Optional() + description?: string; + + @ValidateBoolean({ optional: true }) + enabled?: boolean; + + @ValidateNested({ each: true }) + @Type(() => WorkflowFilterItemDto) + @Optional() + filters?: WorkflowFilterItemDto[]; + + @ValidateNested({ each: true }) + @Type(() => WorkflowActionItemDto) + @Optional() + actions?: WorkflowActionItemDto[]; +} + +export class WorkflowResponseDto { + id!: string; + ownerId!: string; + triggerType!: PluginTriggerType; + name!: string | null; + description!: string; + createdAt!: string; + enabled!: boolean; + filters!: WorkflowFilterResponseDto[]; + actions!: WorkflowActionResponseDto[]; +} + +export class WorkflowFilterResponseDto { + id!: string; + workflowId!: string; + filterId!: string; + filterConfig!: FilterConfig | null; + order!: number; +} + +export class WorkflowActionResponseDto { + id!: string; + workflowId!: string; + actionId!: string; + actionConfig!: ActionConfig | null; + order!: number; +} + +export function mapWorkflowFilter(filter: WorkflowFilter): WorkflowFilterResponseDto { + return { + id: filter.id, + workflowId: filter.workflowId, + filterId: filter.filterId, + filterConfig: filter.filterConfig, + order: filter.order, + }; +} + +export function mapWorkflowAction(action: WorkflowAction): WorkflowActionResponseDto { + return { + id: action.id, + workflowId: action.workflowId, + actionId: action.actionId, + actionConfig: action.actionConfig, + order: action.order, + }; +} diff --git a/server/src/emails/album-update.email.tsx b/server/src/emails/album-update.email.tsx index 3bed3a5b36..6fd2abb055 100644 --- a/server/src/emails/album-update.email.tsx +++ b/server/src/emails/album-update.email.tsx @@ -29,8 +29,8 @@ export const AlbumUpdateEmail = ({ - New media has been added to {albumName}, -
check it out! + New media has been added to {albumName}. +
Check it out!
); diff --git a/server/src/emails/components/button.component.tsx b/server/src/emails/components/button.component.tsx index b490e36650..a1fc4636cc 100644 --- a/server/src/emails/components/button.component.tsx +++ b/server/src/emails/components/button.component.tsx @@ -1,12 +1,12 @@ import React from 'react'; -import { Button, ButtonProps } from '@react-email/components'; +import { Button, ButtonProps, Text } from '@react-email/components'; export const ImmichButton = ({ children, ...props }: ButtonProps) => ( ); diff --git a/server/src/enum.ts b/server/src/enum.ts index 646138b060..6055ee85bf 100644 --- a/server/src/enum.ts +++ b/server/src/enum.ts @@ -71,6 +71,14 @@ export enum MemoryType { OnThisDay = 'on_this_day', } +export enum AssetOrderWithRandom { + // Include existing values + Asc = AssetOrder.Asc, + Desc = AssetOrder.Desc, + /** Randomly Ordered */ + Random = 'random', +} + export enum Permission { All = 'all', @@ -95,6 +103,7 @@ export enum Permission { AssetDownload = 'asset.download', AssetUpload = 'asset.upload', AssetReplace = 'asset.replace', + AssetCopy = 'asset.copy', AlbumCreate = 'album.create', AlbumRead = 'album.read', @@ -168,6 +177,11 @@ export enum Permission { PinCodeUpdate = 'pinCode.update', PinCodeDelete = 'pinCode.delete', + PluginCreate = 'plugin.create', + PluginRead = 'plugin.read', + PluginUpdate = 'plugin.update', + PluginDelete = 'plugin.delete', + ServerAbout = 'server.about', ServerApkLinks = 'server.apkLinks', ServerStorage = 'server.storage', @@ -231,11 +245,18 @@ export enum Permission { UserProfileImageUpdate = 'userProfileImage.update', UserProfileImageDelete = 'userProfileImage.delete', + WorkflowCreate = 'workflow.create', + WorkflowRead = 'workflow.read', + WorkflowUpdate = 'workflow.update', + WorkflowDelete = 'workflow.delete', + AdminUserCreate = 'adminUser.create', AdminUserRead = 'adminUser.read', AdminUserUpdate = 'adminUser.update', AdminUserDelete = 'adminUser.delete', + AdminSessionRead = 'adminSession.read', + AdminAuthUnlinkAll = 'adminAuth.unlinkAll', } @@ -423,6 +444,8 @@ export enum LogLevel { export enum ApiCustomExtension { Permission = 'x-immich-permission', AdminOnly = 'x-immich-admin-only', + History = 'x-immich-history', + State = 'x-immich-state', } export enum MetadataKey { @@ -511,6 +534,8 @@ export enum QueueName { Library = 'library', Notification = 'notifications', BackupDatabase = 'backupDatabase', + Ocr = 'ocr', + Workflow = 'workflow', } export enum JobName { @@ -583,9 +608,16 @@ export enum JobName { TagCleanup = 'TagCleanup', VersionCheck = 'VersionCheck', + + // OCR + OcrQueueAll = 'OcrQueueAll', + Ocr = 'Ocr', + + // Workflow + WorkflowRun = 'WorkflowRun', } -export enum JobCommand { +export enum QueueCommand { Start = 'start', Pause = 'pause', Resume = 'resume', @@ -722,6 +754,8 @@ export enum NotificationType { JobFailed = 'JobFailed', BackupFailed = 'BackupFailed', SystemMessage = 'SystemMessage', + AlbumInvite = 'AlbumInvite', + AlbumUpdate = 'AlbumUpdate', Custom = 'Custom', } @@ -753,3 +787,51 @@ export enum CronJob { LibraryScan = 'LibraryScan', NightlyJobs = 'NightlyJobs', } + +export enum ApiTag { + Activities = 'Activities', + Albums = 'Albums', + ApiKeys = 'API keys', + Authentication = 'Authentication', + AuthenticationAdmin = 'Authentication (admin)', + Assets = 'Assets', + Deprecated = 'Deprecated', + Download = 'Download', + Duplicates = 'Duplicates', + Faces = 'Faces', + Jobs = 'Jobs', + Libraries = 'Libraries', + Map = 'Map', + Memories = 'Memories', + Notifications = 'Notifications', + NotificationsAdmin = 'Notifications (admin)', + Partners = 'Partners', + People = 'People', + Plugins = 'Plugins', + Search = 'Search', + Server = 'Server', + Sessions = 'Sessions', + SharedLinks = 'Shared links', + Stacks = 'Stacks', + Sync = 'Sync', + SystemConfig = 'System config', + SystemMetadata = 'System metadata', + Tags = 'Tags', + Timeline = 'Timeline', + Trash = 'Trash', + UsersAdmin = 'Users (admin)', + Users = 'Users', + Views = 'Views', + Workflows = 'Workflows', +} + +export enum PluginContext { + Asset = 'asset', + Album = 'album', + Person = 'person', +} + +export enum PluginTriggerType { + AssetCreate = 'AssetCreate', + PersonRecognized = 'PersonRecognized', +} diff --git a/server/src/middleware/auth.guard.ts b/server/src/middleware/auth.guard.ts index 8af7bf7fb3..4964fefbbc 100644 --- a/server/src/middleware/auth.guard.ts +++ b/server/src/middleware/auth.guard.ts @@ -13,7 +13,7 @@ import { AuthDto } from 'src/dtos/auth.dto'; import { ApiCustomExtension, ImmichQuery, MetadataKey, Permission } from 'src/enum'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { AuthService, LoginDetails } from 'src/services/auth.service'; -import { UAParser } from 'ua-parser-js'; +import { getUserAgentDetails } from 'src/utils/request'; type AdminRoute = { admin?: true }; type SharedLinkRoute = { sharedLink?: true }; @@ -56,13 +56,14 @@ export const FileResponse = () => export const GetLoginDetails = createParamDecorator((data, context: ExecutionContext): LoginDetails => { const request = context.switchToHttp().getRequest(); - const userAgent = UAParser(request.headers['user-agent']); + const { deviceType, deviceOS, appVersion } = getUserAgentDetails(request.headers); return { clientIp: request.ip ?? '', isSecure: request.secure, - deviceType: userAgent.browser.name || userAgent.device.type || (request.headers.devicemodel as string) || '', - deviceOS: userAgent.os.name || (request.headers.devicetype as string) || '', + deviceType, + deviceOS, + appVersion, }; }); @@ -86,7 +87,6 @@ export class AuthGuard implements CanActivate { async canActivate(context: ExecutionContext): Promise { const targets = [context.getHandler()]; - const options = this.reflector.getAllAndOverride(MetadataKey.AuthRoute, targets); if (!options) { return true; diff --git a/server/src/plugins.ts b/server/src/plugins.ts new file mode 100644 index 0000000000..0c69483696 --- /dev/null +++ b/server/src/plugins.ts @@ -0,0 +1,37 @@ +import { PluginContext, PluginTriggerType } from 'src/enum'; +import { JSONSchema } from 'src/types/plugin-schema.types'; + +export type PluginTrigger = { + name: string; + type: PluginTriggerType; + description: string; + context: PluginContext; + schema: JSONSchema | null; +}; + +export const pluginTriggers: PluginTrigger[] = [ + { + name: 'Asset Uploaded', + type: PluginTriggerType.AssetCreate, + description: 'Triggered when a new asset is uploaded', + context: PluginContext.Asset, + schema: { + type: 'object', + properties: { + assetType: { + type: 'string', + description: 'Type of the asset', + default: 'ALL', + enum: ['Image', 'Video', 'All'], + }, + }, + }, + }, + { + name: 'Person Recognized', + type: PluginTriggerType.PersonRecognized, + description: 'Triggered when a person is detected in an asset', + context: PluginContext.Person, + schema: null, + }, +]; diff --git a/server/src/queries/access.repository.sql b/server/src/queries/access.repository.sql index 9aecaafb52..1239260dce 100644 --- a/server/src/queries/access.repository.sql +++ b/server/src/queries/access.repository.sql @@ -25,8 +25,8 @@ select "album"."id" from "album" - left join "album_user" as "albumUsers" on "albumUsers"."albumsId" = "album"."id" - left join "user" on "user"."id" = "albumUsers"."usersId" + left join "album_user" as "albumUsers" on "albumUsers"."albumId" = "album"."id" + left join "user" on "user"."id" = "albumUsers"."userId" and "user"."deletedAt" is null where "album"."id" in ($1) @@ -52,8 +52,8 @@ select "album"."id" from "album" - left join "album_user" on "album_user"."albumsId" = "album"."id" - left join "user" on "user"."id" = "album_user"."usersId" + left join "album_user" on "album_user"."albumId" = "album"."id" + left join "user" on "user"."id" = "album_user"."userId" and "user"."deletedAt" is null where "album"."id" in ($1) @@ -71,19 +71,28 @@ where and "shared_link"."albumId" in ($2) -- AccessRepository.asset.checkAlbumAccess +with + "target" as ( + select + array[$1]::uuid[] as "ids" + ) select "asset"."id", "asset"."livePhotoVideoId" from "album" - inner join "album_asset" as "albumAssets" on "album"."id" = "albumAssets"."albumsId" - inner join "asset" on "asset"."id" = "albumAssets"."assetsId" + inner join "album_asset" as "albumAssets" on "album"."id" = "albumAssets"."albumId" + inner join "asset" on "asset"."id" = "albumAssets"."assetId" and "asset"."deletedAt" is null - left join "album_user" as "albumUsers" on "albumUsers"."albumsId" = "album"."id" - left join "user" on "user"."id" = "albumUsers"."usersId" + left join "album_user" as "albumUsers" on "albumUsers"."albumId" = "album"."id" + left join "user" on "user"."id" = "albumUsers"."userId" and "user"."deletedAt" is null + cross join "target" where - array["asset"."id", "asset"."livePhotoVideoId"] && array[$1]::uuid[] + ( + "asset"."id" = any (target.ids) + or "asset"."livePhotoVideoId" = any (target.ids) + ) and ( "album"."ownerId" = $2 or "user"."id" = $3 @@ -127,11 +136,11 @@ from "shared_link" left join "album" on "album"."id" = "shared_link"."albumId" and "album"."deletedAt" is null - left join "shared_link_asset" on "shared_link_asset"."sharedLinksId" = "shared_link"."id" - left join "asset" on "asset"."id" = "shared_link_asset"."assetsId" + left join "shared_link_asset" on "shared_link_asset"."sharedLinkId" = "shared_link"."id" + left join "asset" on "asset"."id" = "shared_link_asset"."assetId" and "asset"."deletedAt" is null - left join "album_asset" on "album_asset"."albumsId" = "album"."id" - left join "asset" as "albumAssets" on "albumAssets"."id" = "album_asset"."assetsId" + left join "album_asset" on "album_asset"."albumId" = "album"."id" + left join "asset" as "albumAssets" on "albumAssets"."id" = "album_asset"."assetId" and "albumAssets"."deletedAt" is null where "shared_link"."id" = $1 @@ -234,3 +243,12 @@ from where "partner"."sharedById" in ($1) and "partner"."sharedWithId" = $2 + +-- AccessRepository.workflow.checkOwnerAccess +select + "workflow"."id" +from + "workflow" +where + "workflow"."id" in ($1) + and "workflow"."ownerId" = $2 diff --git a/server/src/queries/album.repository.sql b/server/src/queries/album.repository.sql index 36c44414db..f62e769a17 100644 --- a/server/src/queries/album.repository.sql +++ b/server/src/queries/album.repository.sql @@ -43,13 +43,13 @@ select from "user" where - "user"."id" = "album_user"."usersId" + "user"."id" = "album_user"."userId" ) as obj ) as "user" from "album_user" where - "album_user"."albumsId" = "album"."id" + "album_user"."albumId" = "album"."id" ) as agg ) as "albumUsers", ( @@ -76,9 +76,9 @@ select from "asset" left join "asset_exif" on "asset"."id" = "asset_exif"."assetId" - inner join "album_asset" on "album_asset"."assetsId" = "asset"."id" + inner join "album_asset" on "album_asset"."assetId" = "asset"."id" where - "album_asset"."albumsId" = "album"."id" + "album_asset"."albumId" = "album"."id" and "asset"."deletedAt" is null and "asset"."visibility" in ('archive', 'timeline') order by @@ -134,18 +134,18 @@ select from "user" where - "user"."id" = "album_user"."usersId" + "user"."id" = "album_user"."userId" ) as obj ) as "user" from "album_user" where - "album_user"."albumsId" = "album"."id" + "album_user"."albumId" = "album"."id" ) as agg ) as "albumUsers" from "album" - inner join "album_asset" on "album_asset"."albumsId" = "album"."id" + inner join "album_asset" on "album_asset"."albumId" = "album"."id" where ( "album"."ownerId" = $1 @@ -154,11 +154,11 @@ where from "album_user" where - "album_user"."albumsId" = "album"."id" - and "album_user"."usersId" = $2 + "album_user"."albumId" = "album"."id" + and "album_user"."userId" = $2 ) ) - and "album_asset"."assetsId" = $3 + and "album_asset"."assetId" = $3 and "album"."deletedAt" is null order by "album"."createdAt" desc, @@ -166,7 +166,7 @@ order by -- AlbumRepository.getMetadataForIds select - "album_asset"."albumsId" as "albumId", + "album_asset"."albumId" as "albumId", min( ("asset"."localDateTime" AT TIME ZONE 'UTC'::text)::date ) as "startDate", @@ -177,13 +177,13 @@ select count("asset"."id")::int as "assetCount" from "asset" - inner join "album_asset" on "album_asset"."assetsId" = "asset"."id" + inner join "album_asset" on "album_asset"."assetId" = "asset"."id" where "asset"."visibility" in ('archive', 'timeline') - and "album_asset"."albumsId" in ($1) + and "album_asset"."albumId" in ($1) and "asset"."deletedAt" is null group by - "album_asset"."albumsId" + "album_asset"."albumId" -- AlbumRepository.getOwned select @@ -228,13 +228,13 @@ select from "user" where - "user"."id" = "album_user"."usersId" + "user"."id" = "album_user"."userId" ) as obj ) as "user" from "album_user" where - "album_user"."albumsId" = "album"."id" + "album_user"."albumId" = "album"."id" ) as agg ) as "albumUsers", ( @@ -283,13 +283,13 @@ select from "user" where - "user"."id" = "album_user"."usersId" + "user"."id" = "album_user"."userId" ) as obj ) as "user" from "album_user" where - "album_user"."albumsId" = "album"."id" + "album_user"."albumId" = "album"."id" ) as agg ) as "albumUsers", ( @@ -332,10 +332,10 @@ where from "album_user" where - "album_user"."albumsId" = "album"."id" + "album_user"."albumId" = "album"."id" and ( "album"."ownerId" = $1 - or "album_user"."usersId" = $2 + or "album_user"."userId" = $2 ) ) or exists ( @@ -382,7 +382,7 @@ where from "album_user" where - "album_user"."albumsId" = "album"."id" + "album_user"."albumId" = "album"."id" ) and not exists ( select @@ -397,7 +397,7 @@ order by -- AlbumRepository.removeAssetsFromAll delete from "album_asset" where - "album_asset"."assetsId" in ($1) + "album_asset"."assetId" in ($1) -- AlbumRepository.getAssetIds select @@ -405,5 +405,32 @@ select from "album_asset" where - "album_asset"."albumsId" = $1 - and "album_asset"."assetsId" in ($2) + "album_asset"."albumId" = $1 + and "album_asset"."assetId" in ($2) + +-- AlbumRepository.getContributorCounts +select + "asset"."ownerId" as "userId", + count(*) as "assetCount" +from + "album_asset" + inner join "asset" on "asset"."id" = "assetId" +where + "asset"."deletedAt" is null + and "album_asset"."albumId" = $1 +group by + "asset"."ownerId" +order by + "assetCount" desc + +-- AlbumRepository.copyAlbums +insert into + "album_asset" +select + "album_asset"."albumId", + $1 as "assetId" +from + "album_asset" +where + "album_asset"."assetId" = $2 +on conflict do nothing diff --git a/server/src/queries/album.user.repository.sql b/server/src/queries/album.user.repository.sql index e0fc0e7b74..a758ba1cf4 100644 --- a/server/src/queries/album.user.repository.sql +++ b/server/src/queries/album.user.repository.sql @@ -2,12 +2,12 @@ -- AlbumUserRepository.create insert into - "album_user" ("usersId", "albumsId") + "album_user" ("userId", "albumId") values ($1, $2) returning - "usersId", - "albumsId", + "userId", + "albumId", "role" -- AlbumUserRepository.update @@ -15,13 +15,13 @@ update "album_user" set "role" = $1 where - "usersId" = $2 - and "albumsId" = $3 + "userId" = $2 + and "albumId" = $3 returning * -- AlbumUserRepository.delete delete from "album_user" where - "usersId" = $1 - and "albumsId" = $2 + "userId" = $1 + and "albumId" = $2 diff --git a/server/src/queries/asset.job.repository.sql b/server/src/queries/asset.job.repository.sql index 471de1ac34..ebfd1a08c9 100644 --- a/server/src/queries/asset.job.repository.sql +++ b/server/src/queries/asset.job.repository.sql @@ -31,9 +31,9 @@ select "tag"."value" from "tag" - inner join "tag_asset" on "tag"."id" = "tag_asset"."tagsId" + inner join "tag_asset" on "tag"."id" = "tag_asset"."tagId" where - "asset"."id" = "tag_asset"."assetsId" + "asset"."id" = "tag_asset"."assetId" ) as agg ) as "tags" from @@ -285,6 +285,23 @@ from where "asset"."id" = $2 +-- AssetJobRepository.getForOcr +select + "asset"."visibility", + ( + select + "asset_file"."path" + from + "asset_file" + where + "asset_file"."assetId" = "asset"."id" + and "asset_file"."type" = $1 + ) as "previewFile" +from + "asset" +where + "asset"."id" = $2 + -- AssetJobRepository.getForSyncAssets select "asset"."id", @@ -483,6 +500,17 @@ where order by "asset"."fileCreatedAt" desc +-- AssetJobRepository.streamForOcrJob +select + "asset"."id" +from + "asset" + inner join "asset_job_status" on "asset_job_status"."assetId" = "asset"."id" +where + "asset_job_status"."ocrAt" is null + and "asset"."deletedAt" is null + and "asset"."visibility" != $1 + -- AssetJobRepository.streamForMigrationJob select "id" diff --git a/server/src/queries/asset.repository.sql b/server/src/queries/asset.repository.sql index 1283ff0a66..6cf3ec2f54 100644 --- a/server/src/queries/asset.repository.sql +++ b/server/src/queries/asset.repository.sql @@ -64,7 +64,7 @@ with from asset ), - date_part('year', current_date)::int - 1 + $3 ) as "year" ) select @@ -81,21 +81,21 @@ with where "asset_job_status"."previewAt" is not null and (asset."localDateTime" at time zone 'UTC')::date = today.date - and "asset"."ownerId" = any ($3::uuid[]) - and "asset"."visibility" = $4 + and "asset"."ownerId" = any ($4::uuid[]) + and "asset"."visibility" = $5 and exists ( select from "asset_file" where "assetId" = "asset"."id" - and "asset_file"."type" = $5 + and "asset_file"."type" = $6 ) and "asset"."deletedAt" is null order by (asset."localDateTime" at time zone 'UTC')::date desc limit - $6 + $7 ) as "a" on true inner join "asset_exif" on "a"."id" = "asset_exif"."assetId" ) @@ -160,9 +160,9 @@ select "tag"."parentId" from "tag" - inner join "tag_asset" on "tag"."id" = "tag_asset"."tagsId" + inner join "tag_asset" on "tag"."id" = "tag_asset"."tagId" where - "asset"."id" = "tag_asset"."assetsId" + "asset"."id" = "tag_asset"."assetId" ) as agg ) as "tags", to_json("asset_exif") as "exifInfo" @@ -296,7 +296,8 @@ with "asset"."duration", "asset"."id", "asset"."visibility", - "asset"."isFavorite", + asset."isFavorite" + and asset."ownerId" = $1 as "isFavorite", asset.type = 'IMAGE' as "isImage", asset."deletedAt" is not null as "isTrashed", "asset"."livePhotoVideoId", @@ -341,14 +342,14 @@ with where "stacked"."stackId" = "asset"."stackId" and "stacked"."deletedAt" is null - and "stacked"."visibility" = $1 + and "stacked"."visibility" = $2 group by "stacked"."stackId" ) as "stacked_assets" on true where "asset"."deletedAt" is null and "asset"."visibility" in ('archive', 'timeline') - and date_trunc('MONTH', "localDateTime" AT TIME ZONE 'UTC') AT TIME ZONE 'UTC' = $2 + and date_trunc('MONTH', "localDateTime" AT TIME ZONE 'UTC') AT TIME ZONE 'UTC' = $3 and not exists ( select from diff --git a/server/src/queries/map.repository.sql b/server/src/queries/map.repository.sql index df2136a422..d7e98b1cd2 100644 --- a/server/src/queries/map.repository.sql +++ b/server/src/queries/map.repository.sql @@ -23,8 +23,8 @@ where from "album_asset" where - "asset"."id" = "album_asset"."assetsId" - and "album_asset"."albumsId" in ($3) + "asset"."id" = "album_asset"."assetId" + and "album_asset"."albumId" in ($3) ) ) order by diff --git a/server/src/queries/memory.repository.sql b/server/src/queries/memory.repository.sql index b3cc7240ae..da970c2c78 100644 --- a/server/src/queries/memory.repository.sql +++ b/server/src/queries/memory.repository.sql @@ -37,7 +37,7 @@ select "asset".* from "asset" - inner join "memory_asset" on "asset"."id" = "memory_asset"."assetsId" + inner join "memory_asset" on "asset"."id" = "memory_asset"."assetId" where "memory_asset"."memoriesId" = "memory"."id" and "asset"."visibility" = 'timeline' @@ -66,7 +66,7 @@ select "asset".* from "asset" - inner join "memory_asset" on "asset"."id" = "memory_asset"."assetsId" + inner join "memory_asset" on "asset"."id" = "memory_asset"."assetId" where "memory_asset"."memoriesId" = "memory"."id" and "asset"."visibility" = 'timeline' @@ -104,7 +104,7 @@ select "asset".* from "asset" - inner join "memory_asset" on "asset"."id" = "memory_asset"."assetsId" + inner join "memory_asset" on "asset"."id" = "memory_asset"."assetId" where "memory_asset"."memoriesId" = "memory"."id" and "asset"."visibility" = 'timeline' @@ -137,7 +137,7 @@ select "asset".* from "asset" - inner join "memory_asset" on "asset"."id" = "memory_asset"."assetsId" + inner join "memory_asset" on "asset"."id" = "memory_asset"."assetId" where "memory_asset"."memoriesId" = "memory"."id" and "asset"."visibility" = 'timeline' @@ -159,15 +159,15 @@ where -- MemoryRepository.getAssetIds select - "assetsId" + "assetId" from "memory_asset" where "memoriesId" = $1 - and "assetsId" in ($2) + and "assetId" in ($2) -- MemoryRepository.addAssetIds insert into - "memory_asset" ("memoriesId", "assetsId") + "memory_asset" ("memoriesId", "assetId") values ($1, $2) diff --git a/server/src/queries/ocr.repository.sql b/server/src/queries/ocr.repository.sql new file mode 100644 index 0000000000..d9fe049031 --- /dev/null +++ b/server/src/queries/ocr.repository.sql @@ -0,0 +1,68 @@ +-- NOTE: This file is auto generated by ./sql-generator + +-- OcrRepository.getById +select + "asset_ocr".* +from + "asset_ocr" +where + "asset_ocr"."id" = $1 + +-- OcrRepository.getByAssetId +select + "asset_ocr".* +from + "asset_ocr" +where + "asset_ocr"."assetId" = $1 + +-- OcrRepository.upsert +with + "deleted_ocr" as ( + delete from "asset_ocr" + where + "assetId" = $1 + ), + "inserted_ocr" as ( + insert into + "asset_ocr" ( + "assetId", + "x1", + "y1", + "x2", + "y2", + "x3", + "y3", + "x4", + "y4", + "text", + "boxScore", + "textScore" + ) + values + ( + $2, + $3, + $4, + $5, + $6, + $7, + $8, + $9, + $10, + $11, + $12, + $13 + ) + ), + "inserted_search" as ( + insert into + "ocr_search" ("assetId", "text") + values + ($14, $15) + on conflict ("assetId") do update + set + "text" = "excluded"."text" + ) +select + 1 as "dummy" diff --git a/server/src/queries/plugin.repository.sql b/server/src/queries/plugin.repository.sql new file mode 100644 index 0000000000..82c203dafd --- /dev/null +++ b/server/src/queries/plugin.repository.sql @@ -0,0 +1,159 @@ +-- NOTE: This file is auto generated by ./sql-generator + +-- PluginRepository.getPlugin +select + "plugin"."id" as "id", + "plugin"."name" as "name", + "plugin"."title" as "title", + "plugin"."description" as "description", + "plugin"."author" as "author", + "plugin"."version" as "version", + "plugin"."wasmPath" as "wasmPath", + "plugin"."createdAt" as "createdAt", + "plugin"."updatedAt" as "updatedAt", + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + * + from + "plugin_filter" + where + "plugin_filter"."pluginId" = "plugin"."id" + ) as agg + ) as "filters", + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + * + from + "plugin_action" + where + "plugin_action"."pluginId" = "plugin"."id" + ) as agg + ) as "actions" +from + "plugin" +where + "plugin"."id" = $1 + +-- PluginRepository.getPluginByName +select + "plugin"."id" as "id", + "plugin"."name" as "name", + "plugin"."title" as "title", + "plugin"."description" as "description", + "plugin"."author" as "author", + "plugin"."version" as "version", + "plugin"."wasmPath" as "wasmPath", + "plugin"."createdAt" as "createdAt", + "plugin"."updatedAt" as "updatedAt", + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + * + from + "plugin_filter" + where + "plugin_filter"."pluginId" = "plugin"."id" + ) as agg + ) as "filters", + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + * + from + "plugin_action" + where + "plugin_action"."pluginId" = "plugin"."id" + ) as agg + ) as "actions" +from + "plugin" +where + "plugin"."name" = $1 + +-- PluginRepository.getAllPlugins +select + "plugin"."id" as "id", + "plugin"."name" as "name", + "plugin"."title" as "title", + "plugin"."description" as "description", + "plugin"."author" as "author", + "plugin"."version" as "version", + "plugin"."wasmPath" as "wasmPath", + "plugin"."createdAt" as "createdAt", + "plugin"."updatedAt" as "updatedAt", + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + * + from + "plugin_filter" + where + "plugin_filter"."pluginId" = "plugin"."id" + ) as agg + ) as "filters", + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + * + from + "plugin_action" + where + "plugin_action"."pluginId" = "plugin"."id" + ) as agg + ) as "actions" +from + "plugin" +order by + "plugin"."name" + +-- PluginRepository.getFilter +select + * +from + "plugin_filter" +where + "id" = $1 + +-- PluginRepository.getFiltersByPlugin +select + * +from + "plugin_filter" +where + "pluginId" = $1 + +-- PluginRepository.getAction +select + * +from + "plugin_action" +where + "id" = $1 + +-- PluginRepository.getActionsByPlugin +select + * +from + "plugin_action" +where + "pluginId" = $1 diff --git a/server/src/queries/search.repository.sql b/server/src/queries/search.repository.sql index e0aaedfdf3..ef5fbe09be 100644 --- a/server/src/queries/search.repository.sql +++ b/server/src/queries/search.repository.sql @@ -290,3 +290,15 @@ where and "visibility" = $2 and "deletedAt" is null and "model" is not null + +-- SearchRepository.getCameraLensModels +select distinct + on ("lensModel") "lensModel" +from + "asset_exif" + inner join "asset" on "asset"."id" = "asset_exif"."assetId" +where + "ownerId" = any ($1::uuid[]) + and "visibility" = $2 + and "deletedAt" is null + and "lensModel" is not null diff --git a/server/src/queries/session.repository.sql b/server/src/queries/session.repository.sql index 34d25cce8a..b399646409 100644 --- a/server/src/queries/session.repository.sql +++ b/server/src/queries/session.repository.sql @@ -23,6 +23,7 @@ select "session"."id", "session"."updatedAt", "session"."pinExpiresAt", + "session"."appVersion", ( select to_json(obj) @@ -73,6 +74,12 @@ delete from "session" where "id" = $1::uuid +-- SessionRepository.invalidate +delete from "session" +where + "userId" = $1 + and "id" != $2 + -- SessionRepository.lockAll update "session" set diff --git a/server/src/queries/shared.link.asset.repository.sql b/server/src/queries/shared.link.asset.repository.sql new file mode 100644 index 0000000000..7f9ebc03d1 --- /dev/null +++ b/server/src/queries/shared.link.asset.repository.sql @@ -0,0 +1,13 @@ +-- NOTE: This file is auto generated by ./sql-generator + +-- SharedLinkAssetRepository.copySharedLinks +insert into + "shared_link_asset" +select + $1 as "assetId", + "shared_link_asset"."sharedLinkId" +from + "shared_link_asset" +where + "shared_link_asset"."assetId" = $2 +on conflict do nothing diff --git a/server/src/queries/shared.link.repository.sql b/server/src/queries/shared.link.repository.sql index 0f46846c14..8540da91c8 100644 --- a/server/src/queries/shared.link.repository.sql +++ b/server/src/queries/shared.link.repository.sql @@ -19,7 +19,7 @@ from to_json("exifInfo") as "exifInfo" from "shared_link_asset" - inner join "asset" on "asset"."id" = "shared_link_asset"."assetsId" + inner join "asset" on "asset"."id" = "shared_link_asset"."assetId" inner join lateral ( select "asset_exif".* @@ -29,7 +29,7 @@ from "asset_exif"."assetId" = "asset"."id" ) as "exifInfo" on true where - "shared_link"."id" = "shared_link_asset"."sharedLinksId" + "shared_link"."id" = "shared_link_asset"."sharedLinkId" and "asset"."deletedAt" is null order by "asset"."fileCreatedAt" asc @@ -51,7 +51,7 @@ from to_json("owner") as "owner" from "album" - left join "album_asset" on "album_asset"."albumsId" = "album"."id" + left join "album_asset" on "album_asset"."albumId" = "album"."id" left join lateral ( select "asset".*, @@ -67,7 +67,7 @@ from "asset_exif"."assetId" = "asset"."id" ) as "exifInfo" on true where - "album_asset"."assetsId" = "asset"."id" + "album_asset"."assetId" = "asset"."id" and "asset"."deletedAt" is null order by "asset"."fileCreatedAt" asc @@ -108,14 +108,14 @@ select distinct to_json("album") as "album" from "shared_link" - left join "shared_link_asset" on "shared_link_asset"."sharedLinksId" = "shared_link"."id" + left join "shared_link_asset" on "shared_link_asset"."sharedLinkId" = "shared_link"."id" left join lateral ( select json_agg("asset") as "assets" from "asset" where - "asset"."id" = "shared_link_asset"."assetsId" + "asset"."id" = "shared_link_asset"."assetId" and "asset"."deletedAt" is null ) as "assets" on true left join lateral ( diff --git a/server/src/queries/stack.repository.sql b/server/src/queries/stack.repository.sql index 94a24f69e4..64714e5665 100644 --- a/server/src/queries/stack.repository.sql +++ b/server/src/queries/stack.repository.sql @@ -89,9 +89,9 @@ select "tag"."parentId" from "tag" - inner join "tag_asset" on "tag"."id" = "tag_asset"."tagsId" + inner join "tag_asset" on "tag"."id" = "tag_asset"."tagId" where - "tag_asset"."assetsId" = "asset"."id" + "tag_asset"."assetId" = "asset"."id" ) as agg ) as "tags", to_json("exifInfo") as "exifInfo" @@ -153,3 +153,10 @@ from left join "stack" on "stack"."id" = "asset"."stackId" where "asset"."id" = $1 + +-- StackRepository.merge +update "asset" +set + "stackId" = $1 +where + "asset"."stackId" = $2 diff --git a/server/src/queries/sync.repository.sql b/server/src/queries/sync.repository.sql index 809b59df10..7c1dc3b6b4 100644 --- a/server/src/queries/sync.repository.sql +++ b/server/src/queries/sync.repository.sql @@ -2,12 +2,12 @@ -- SyncRepository.album.getCreatedAfter select - "albumsId" as "id", + "albumId" as "id", "createId" from "album_user" where - "usersId" = $1 + "userId" = $1 and "createId" >= $2 and "createId" < $3 order by @@ -40,13 +40,13 @@ select distinct "album"."updateId" from "album" as "album" - left join "album_user" as "album_users" on "album"."id" = "album_users"."albumsId" + left join "album_user" as "album_users" on "album"."id" = "album_users"."albumId" where "album"."updateId" < $1 and "album"."updateId" > $2 and ( "album"."ownerId" = $3 - or "album_users"."usersId" = $4 + or "album_users"."userId" = $4 ) order by "album"."updateId" asc @@ -72,12 +72,12 @@ select "album_asset"."updateId" from "album_asset" as "album_asset" - inner join "asset" on "asset"."id" = "album_asset"."assetsId" + inner join "asset" on "asset"."id" = "album_asset"."assetId" where "album_asset"."updateId" < $1 and "album_asset"."updateId" <= $2 and "album_asset"."updateId" >= $3 - and "album_asset"."albumsId" = $4 + and "album_asset"."albumId" = $4 order by "album_asset"."updateId" asc @@ -102,16 +102,16 @@ select "asset"."updateId" from "asset" as "asset" - inner join "album_asset" on "album_asset"."assetsId" = "asset"."id" - inner join "album" on "album"."id" = "album_asset"."albumsId" - left join "album_user" on "album_user"."albumsId" = "album_asset"."albumsId" + inner join "album_asset" on "album_asset"."assetId" = "asset"."id" + inner join "album" on "album"."id" = "album_asset"."albumId" + left join "album_user" on "album_user"."albumId" = "album_asset"."albumId" where "asset"."updateId" < $1 and "asset"."updateId" > $2 and "album_asset"."updateId" <= $3 and ( "album"."ownerId" = $4 - or "album_user"."usersId" = $5 + or "album_user"."userId" = $5 ) order by "asset"."updateId" asc @@ -137,15 +137,15 @@ select "asset"."libraryId" from "album_asset" as "album_asset" - inner join "asset" on "asset"."id" = "album_asset"."assetsId" - inner join "album" on "album"."id" = "album_asset"."albumsId" - left join "album_user" on "album_user"."albumsId" = "album_asset"."albumsId" + inner join "asset" on "asset"."id" = "album_asset"."assetId" + inner join "album" on "album"."id" = "album_asset"."albumId" + left join "album_user" on "album_user"."albumId" = "album_asset"."albumId" where "album_asset"."updateId" < $1 and "album_asset"."updateId" > $2 and ( "album"."ownerId" = $3 - or "album_user"."usersId" = $4 + or "album_user"."userId" = $4 ) order by "album_asset"."updateId" asc @@ -180,12 +180,12 @@ select "album_asset"."updateId" from "album_asset" as "album_asset" - inner join "asset_exif" on "asset_exif"."assetId" = "album_asset"."assetsId" + inner join "asset_exif" on "asset_exif"."assetId" = "album_asset"."assetId" where "album_asset"."updateId" < $1 and "album_asset"."updateId" <= $2 and "album_asset"."updateId" >= $3 - and "album_asset"."albumsId" = $4 + and "album_asset"."albumId" = $4 order by "album_asset"."updateId" asc @@ -219,16 +219,16 @@ select "asset_exif"."updateId" from "asset_exif" as "asset_exif" - inner join "album_asset" on "album_asset"."assetsId" = "asset_exif"."assetId" - inner join "album" on "album"."id" = "album_asset"."albumsId" - left join "album_user" on "album_user"."albumsId" = "album_asset"."albumsId" + inner join "album_asset" on "album_asset"."assetId" = "asset_exif"."assetId" + inner join "album" on "album"."id" = "album_asset"."albumId" + left join "album_user" on "album_user"."albumId" = "album_asset"."albumId" where "asset_exif"."updateId" < $1 and "asset_exif"."updateId" > $2 and "album_asset"."updateId" <= $3 and ( "album"."ownerId" = $4 - or "album_user"."usersId" = $5 + or "album_user"."userId" = $5 ) order by "asset_exif"."updateId" asc @@ -263,23 +263,23 @@ select "asset_exif"."fps" from "album_asset" as "album_asset" - inner join "asset_exif" on "asset_exif"."assetId" = "album_asset"."assetsId" - inner join "album" on "album"."id" = "album_asset"."albumsId" - left join "album_user" on "album_user"."albumsId" = "album_asset"."albumsId" + inner join "asset_exif" on "asset_exif"."assetId" = "album_asset"."assetId" + inner join "album" on "album"."id" = "album_asset"."albumId" + left join "album_user" on "album_user"."albumId" = "album_asset"."albumId" where "album_asset"."updateId" < $1 and "album_asset"."updateId" > $2 and ( "album"."ownerId" = $3 - or "album_user"."usersId" = $4 + or "album_user"."userId" = $4 ) order by "album_asset"."updateId" asc -- SyncRepository.albumToAsset.getBackfill select - "album_asset"."assetsId" as "assetId", - "album_asset"."albumsId" as "albumId", + "album_asset"."assetId" as "assetId", + "album_asset"."albumId" as "albumId", "album_asset"."updateId" from "album_asset" as "album_asset" @@ -287,7 +287,7 @@ where "album_asset"."updateId" < $1 and "album_asset"."updateId" <= $2 and "album_asset"."updateId" >= $3 - and "album_asset"."albumsId" = $4 + and "album_asset"."albumId" = $4 order by "album_asset"."updateId" asc @@ -311,11 +311,11 @@ where union ( select - "album_user"."albumsId" as "id" + "album_user"."albumId" as "id" from "album_user" where - "album_user"."usersId" = $4 + "album_user"."userId" = $4 ) ) order by @@ -323,27 +323,27 @@ order by -- SyncRepository.albumToAsset.getUpserts select - "album_asset"."assetsId" as "assetId", - "album_asset"."albumsId" as "albumId", + "album_asset"."assetId" as "assetId", + "album_asset"."albumId" as "albumId", "album_asset"."updateId" from "album_asset" as "album_asset" - inner join "album" on "album"."id" = "album_asset"."albumsId" - left join "album_user" on "album_user"."albumsId" = "album_asset"."albumsId" + inner join "album" on "album"."id" = "album_asset"."albumId" + left join "album_user" on "album_user"."albumId" = "album_asset"."albumId" where "album_asset"."updateId" < $1 and "album_asset"."updateId" > $2 and ( "album"."ownerId" = $3 - or "album_user"."usersId" = $4 + or "album_user"."userId" = $4 ) order by "album_asset"."updateId" asc -- SyncRepository.albumUser.getBackfill select - "album_user"."albumsId" as "albumId", - "album_user"."usersId" as "userId", + "album_user"."albumId" as "albumId", + "album_user"."userId" as "userId", "album_user"."role", "album_user"."updateId" from @@ -352,7 +352,7 @@ where "album_user"."updateId" < $1 and "album_user"."updateId" <= $2 and "album_user"."updateId" >= $3 - and "albumsId" = $4 + and "albumId" = $4 order by "album_user"."updateId" asc @@ -376,11 +376,11 @@ where union ( select - "album_user"."albumsId" as "id" + "album_user"."albumId" as "id" from "album_user" where - "album_user"."usersId" = $4 + "album_user"."userId" = $4 ) ) order by @@ -388,8 +388,8 @@ order by -- SyncRepository.albumUser.getUpserts select - "album_user"."albumsId" as "albumId", - "album_user"."usersId" as "userId", + "album_user"."albumId" as "albumId", + "album_user"."userId" as "userId", "album_user"."role", "album_user"."updateId" from @@ -397,7 +397,7 @@ from where "album_user"."updateId" < $1 and "album_user"."updateId" > $2 - and "album_user"."albumsId" in ( + and "album_user"."albumId" in ( select "id" from @@ -407,11 +407,11 @@ where union ( select - "albumUsers"."albumsId" as "id" + "albumUsers"."albumId" as "id" from "album_user" as "albumUsers" where - "albumUsers"."usersId" = $4 + "albumUsers"."userId" = $4 ) ) order by @@ -656,7 +656,7 @@ order by -- SyncRepository.memoryToAsset.getUpserts select "memoriesId" as "memoryId", - "assetsId" as "assetId", + "assetId" as "assetId", "updateId" from "memory_asset" as "memory_asset" diff --git a/server/src/queries/tag.repository.sql b/server/src/queries/tag.repository.sql index ee961f3801..c3b46dd9f3 100644 --- a/server/src/queries/tag.repository.sql +++ b/server/src/queries/tag.repository.sql @@ -84,19 +84,19 @@ where -- TagRepository.addAssetIds insert into - "tag_asset" ("tagsId", "assetsId") + "tag_asset" ("tagId", "assetId") values ($1, $2) -- TagRepository.removeAssetIds delete from "tag_asset" where - "tagsId" = $1 - and "assetsId" in ($2) + "tagId" = $1 + and "assetId" in ($2) -- TagRepository.upsertAssetIds insert into - "tag_asset" ("assetId", "tagsIds") + "tag_asset" ("assetId", "tagIds") values ($1, $2) on conflict do nothing @@ -107,9 +107,9 @@ returning begin delete from "tag_asset" where - "assetsId" = $1 + "assetId" = $1 insert into - "tag_asset" ("tagsId", "assetsId") + "tag_asset" ("tagId", "assetId") values ($1, $2) on conflict do nothing diff --git a/server/src/queries/user.repository.sql b/server/src/queries/user.repository.sql index 6a02654781..c5a4f139a7 100644 --- a/server/src/queries/user.repository.sql +++ b/server/src/queries/user.repository.sql @@ -363,6 +363,14 @@ group by order by "user"."createdAt" asc +-- UserRepository.getCount +select + count(*) as "count" +from + "user" +where + "user"."deletedAt" is null + -- UserRepository.updateUsage update "user" set diff --git a/server/src/queries/workflow.repository.sql b/server/src/queries/workflow.repository.sql new file mode 100644 index 0000000000..3797c5bb06 --- /dev/null +++ b/server/src/queries/workflow.repository.sql @@ -0,0 +1,68 @@ +-- NOTE: This file is auto generated by ./sql-generator + +-- WorkflowRepository.getWorkflow +select + * +from + "workflow" +where + "id" = $1 + +-- WorkflowRepository.getWorkflowsByOwner +select + * +from + "workflow" +where + "ownerId" = $1 +order by + "name" + +-- WorkflowRepository.getWorkflowsByTrigger +select + * +from + "workflow" +where + "triggerType" = $1 + and "enabled" = $2 + +-- WorkflowRepository.getWorkflowByOwnerAndTrigger +select + * +from + "workflow" +where + "ownerId" = $1 + and "triggerType" = $2 + and "enabled" = $3 + +-- WorkflowRepository.deleteWorkflow +delete from "workflow" +where + "id" = $1 + +-- WorkflowRepository.getFilters +select + * +from + "workflow_filter" +where + "workflowId" = $1 +order by + "order" asc + +-- WorkflowRepository.deleteFiltersByWorkflow +delete from "workflow_filter" +where + "workflowId" = $1 + +-- WorkflowRepository.getActions +select + * +from + "workflow_action" +where + "workflowId" = $1 +order by + "order" asc diff --git a/server/src/repositories/access.repository.ts b/server/src/repositories/access.repository.ts index 5cceb6dbe0..533e74a311 100644 --- a/server/src/repositories/access.repository.ts +++ b/server/src/repositories/access.repository.ts @@ -52,8 +52,8 @@ class ActivityAccess { return this.db .selectFrom('album') .select('album.id') - .leftJoin('album_user as albumUsers', 'albumUsers.albumsId', 'album.id') - .leftJoin('user', (join) => join.onRef('user.id', '=', 'albumUsers.usersId').on('user.deletedAt', 'is', null)) + .leftJoin('album_user as albumUsers', 'albumUsers.albumId', 'album.id') + .leftJoin('user', (join) => join.onRef('user.id', '=', 'albumUsers.userId').on('user.deletedAt', 'is', null)) .where('album.id', 'in', [...albumIds]) .where('album.isActivityEnabled', '=', true) .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('user.id', '=', userId)])) @@ -96,8 +96,8 @@ class AlbumAccess { return this.db .selectFrom('album') .select('album.id') - .leftJoin('album_user', 'album_user.albumsId', 'album.id') - .leftJoin('user', (join) => join.onRef('user.id', '=', 'album_user.usersId').on('user.deletedAt', 'is', null)) + .leftJoin('album_user', 'album_user.albumId', 'album.id') + .leftJoin('user', (join) => join.onRef('user.id', '=', 'album_user.userId').on('user.deletedAt', 'is', null)) .where('album.id', 'in', [...albumIds]) .where('album.deletedAt', 'is', null) .where('user.id', '=', userId) @@ -136,18 +136,21 @@ class AssetAccess { } return this.db + .with('target', (qb) => qb.selectNoFrom(sql`array[${sql.join([...assetIds])}]::uuid[]`.as('ids'))) .selectFrom('album') - .innerJoin('album_asset as albumAssets', 'album.id', 'albumAssets.albumsId') + .innerJoin('album_asset as albumAssets', 'album.id', 'albumAssets.albumId') .innerJoin('asset', (join) => - join.onRef('asset.id', '=', 'albumAssets.assetsId').on('asset.deletedAt', 'is', null), + join.onRef('asset.id', '=', 'albumAssets.assetId').on('asset.deletedAt', 'is', null), ) - .leftJoin('album_user as albumUsers', 'albumUsers.albumsId', 'album.id') - .leftJoin('user', (join) => join.onRef('user.id', '=', 'albumUsers.usersId').on('user.deletedAt', 'is', null)) + .leftJoin('album_user as albumUsers', 'albumUsers.albumId', 'album.id') + .leftJoin('user', (join) => join.onRef('user.id', '=', 'albumUsers.userId').on('user.deletedAt', 'is', null)) + .crossJoin('target') .select(['asset.id', 'asset.livePhotoVideoId']) - .where( - sql`array["asset"."id", "asset"."livePhotoVideoId"]`, - '&&', - sql`array[${sql.join([...assetIds])}]::uuid[] `, + .where((eb) => + eb.or([ + eb('asset.id', '=', sql`any(target.ids)`), + eb('asset.livePhotoVideoId', '=', sql`any(target.ids)`), + ]), ) .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('user.id', '=', userId)])) .where('album.deletedAt', 'is', null) @@ -220,13 +223,13 @@ class AssetAccess { return this.db .selectFrom('shared_link') .leftJoin('album', (join) => join.onRef('album.id', '=', 'shared_link.albumId').on('album.deletedAt', 'is', null)) - .leftJoin('shared_link_asset', 'shared_link_asset.sharedLinksId', 'shared_link.id') + .leftJoin('shared_link_asset', 'shared_link_asset.sharedLinkId', 'shared_link.id') .leftJoin('asset', (join) => - join.onRef('asset.id', '=', 'shared_link_asset.assetsId').on('asset.deletedAt', 'is', null), + join.onRef('asset.id', '=', 'shared_link_asset.assetId').on('asset.deletedAt', 'is', null), ) - .leftJoin('album_asset', 'album_asset.albumsId', 'album.id') + .leftJoin('album_asset', 'album_asset.albumId', 'album.id') .leftJoin('asset as albumAssets', (join) => - join.onRef('albumAssets.id', '=', 'album_asset.assetsId').on('albumAssets.deletedAt', 'is', null), + join.onRef('albumAssets.id', '=', 'album_asset.assetId').on('albumAssets.deletedAt', 'is', null), ) .select([ 'asset.id as assetId', @@ -459,6 +462,26 @@ class TagAccess { } } +class WorkflowAccess { + constructor(private db: Kysely) {} + + @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) + @ChunkedSet({ paramIndex: 1 }) + async checkOwnerAccess(userId: string, workflowIds: Set) { + if (workflowIds.size === 0) { + return new Set(); + } + + return this.db + .selectFrom('workflow') + .select('workflow.id') + .where('workflow.id', 'in', [...workflowIds]) + .where('workflow.ownerId', '=', userId) + .execute() + .then((workflows) => new Set(workflows.map((workflow) => workflow.id))); + } +} + @Injectable() export class AccessRepository { activity: ActivityAccess; @@ -473,6 +496,7 @@ export class AccessRepository { stack: StackAccess; tag: TagAccess; timeline: TimelineAccess; + workflow: WorkflowAccess; constructor(@InjectKysely() db: Kysely) { this.activity = new ActivityAccess(db); @@ -487,5 +511,6 @@ export class AccessRepository { this.stack = new StackAccess(db); this.tag = new TagAccess(db); this.timeline = new TimelineAccess(db); + this.workflow = new WorkflowAccess(db); } } diff --git a/server/src/repositories/album-user.repository.ts b/server/src/repositories/album-user.repository.ts index 2fce797aff..1a1e58a77d 100644 --- a/server/src/repositories/album-user.repository.ts +++ b/server/src/repositories/album-user.repository.ts @@ -7,36 +7,36 @@ import { DB } from 'src/schema'; import { AlbumUserTable } from 'src/schema/tables/album-user.table'; export type AlbumPermissionId = { - albumsId: string; - usersId: string; + albumId: string; + userId: string; }; @Injectable() export class AlbumUserRepository { constructor(@InjectKysely() private db: Kysely) {} - @GenerateSql({ params: [{ usersId: DummyValue.UUID, albumsId: DummyValue.UUID }] }) + @GenerateSql({ params: [{ userId: DummyValue.UUID, albumId: DummyValue.UUID }] }) create(albumUser: Insertable) { return this.db .insertInto('album_user') .values(albumUser) - .returning(['usersId', 'albumsId', 'role']) + .returning(['userId', 'albumId', 'role']) .executeTakeFirstOrThrow(); } - @GenerateSql({ params: [{ usersId: DummyValue.UUID, albumsId: DummyValue.UUID }, { role: AlbumUserRole.Viewer }] }) - update({ usersId, albumsId }: AlbumPermissionId, dto: Updateable) { + @GenerateSql({ params: [{ userId: DummyValue.UUID, albumId: DummyValue.UUID }, { role: AlbumUserRole.Viewer }] }) + update({ userId, albumId }: AlbumPermissionId, dto: Updateable) { return this.db .updateTable('album_user') .set(dto) - .where('usersId', '=', usersId) - .where('albumsId', '=', albumsId) + .where('userId', '=', userId) + .where('albumId', '=', albumId) .returningAll() .executeTakeFirstOrThrow(); } - @GenerateSql({ params: [{ usersId: DummyValue.UUID, albumsId: DummyValue.UUID }] }) - async delete({ usersId, albumsId }: AlbumPermissionId): Promise { - await this.db.deleteFrom('album_user').where('usersId', '=', usersId).where('albumsId', '=', albumsId).execute(); + @GenerateSql({ params: [{ userId: DummyValue.UUID, albumId: DummyValue.UUID }] }) + async delete({ userId, albumId }: AlbumPermissionId): Promise { + await this.db.deleteFrom('album_user').where('userId', '=', userId).where('albumId', '=', albumId).execute(); } } diff --git a/server/src/repositories/album.repository.ts b/server/src/repositories/album.repository.ts index b023068f16..100ab908c0 100644 --- a/server/src/repositories/album.repository.ts +++ b/server/src/repositories/album.repository.ts @@ -33,11 +33,11 @@ const withAlbumUsers = (eb: ExpressionBuilder) => { .selectFrom('album_user') .select('album_user.role') .select((eb) => - jsonObjectFrom(eb.selectFrom('user').select(columns.user).whereRef('user.id', '=', 'album_user.usersId')) + jsonObjectFrom(eb.selectFrom('user').select(columns.user).whereRef('user.id', '=', 'album_user.userId')) .$notNull() .as('user'), ) - .whereRef('album_user.albumsId', '=', 'album.id'), + .whereRef('album_user.albumId', '=', 'album.id'), ) .$notNull() .as('albumUsers'); @@ -57,8 +57,8 @@ const withAssets = (eb: ExpressionBuilder) => { .selectAll('asset') .leftJoin('asset_exif', 'asset.id', 'asset_exif.assetId') .select((eb) => eb.table('asset_exif').$castTo().as('exifInfo')) - .innerJoin('album_asset', 'album_asset.assetsId', 'asset.id') - .whereRef('album_asset.albumsId', '=', 'album.id') + .innerJoin('album_asset', 'album_asset.assetId', 'asset.id') + .whereRef('album_asset.albumId', '=', 'album.id') .where('asset.deletedAt', 'is', null) .$call(withDefaultVisibility) .orderBy('asset.fileCreatedAt', 'desc') @@ -92,19 +92,19 @@ export class AlbumRepository { return this.db .selectFrom('album') .selectAll('album') - .innerJoin('album_asset', 'album_asset.albumsId', 'album.id') + .innerJoin('album_asset', 'album_asset.albumId', 'album.id') .where((eb) => eb.or([ eb('album.ownerId', '=', ownerId), eb.exists( eb .selectFrom('album_user') - .whereRef('album_user.albumsId', '=', 'album.id') - .where('album_user.usersId', '=', ownerId), + .whereRef('album_user.albumId', '=', 'album.id') + .where('album_user.userId', '=', ownerId), ), ]), ) - .where('album_asset.assetsId', '=', assetId) + .where('album_asset.assetId', '=', assetId) .where('album.deletedAt', 'is', null) .orderBy('album.createdAt', 'desc') .select(withOwner) @@ -125,16 +125,16 @@ export class AlbumRepository { this.db .selectFrom('asset') .$call(withDefaultVisibility) - .innerJoin('album_asset', 'album_asset.assetsId', 'asset.id') - .select('album_asset.albumsId as albumId') + .innerJoin('album_asset', 'album_asset.assetId', 'asset.id') + .select('album_asset.albumId as albumId') .select((eb) => eb.fn.min(sql`("asset"."localDateTime" AT TIME ZONE 'UTC'::text)::date`).as('startDate')) .select((eb) => eb.fn.max(sql`("asset"."localDateTime" AT TIME ZONE 'UTC'::text)::date`).as('endDate')) // lastModifiedAssetTimestamp is only used in mobile app, please remove if not need .select((eb) => eb.fn.max('asset.updatedAt').as('lastModifiedAssetTimestamp')) .select((eb) => sql`${eb.fn.count('asset.id')}::int`.as('assetCount')) - .where('album_asset.albumsId', 'in', ids) + .where('album_asset.albumId', 'in', ids) .where('asset.deletedAt', 'is', null) - .groupBy('album_asset.albumsId') + .groupBy('album_asset.albumId') .execute() ); } @@ -166,8 +166,8 @@ export class AlbumRepository { eb.exists( eb .selectFrom('album_user') - .whereRef('album_user.albumsId', '=', 'album.id') - .where((eb) => eb.or([eb('album.ownerId', '=', ownerId), eb('album_user.usersId', '=', ownerId)])), + .whereRef('album_user.albumId', '=', 'album.id') + .where((eb) => eb.or([eb('album.ownerId', '=', ownerId), eb('album_user.userId', '=', ownerId)])), ), eb.exists( eb @@ -195,7 +195,7 @@ export class AlbumRepository { .selectAll('album') .where('album.ownerId', '=', ownerId) .where('album.deletedAt', 'is', null) - .where((eb) => eb.not(eb.exists(eb.selectFrom('album_user').whereRef('album_user.albumsId', '=', 'album.id')))) + .where((eb) => eb.not(eb.exists(eb.selectFrom('album_user').whereRef('album_user.albumId', '=', 'album.id')))) .where((eb) => eb.not(eb.exists(eb.selectFrom('shared_link').whereRef('shared_link.albumId', '=', 'album.id')))) .select(withOwner) .orderBy('album.createdAt', 'desc') @@ -217,7 +217,7 @@ export class AlbumRepository { @GenerateSql({ params: [[DummyValue.UUID]] }) @Chunked() async removeAssetsFromAll(assetIds: string[]): Promise { - await this.db.deleteFrom('album_asset').where('album_asset.assetsId', 'in', assetIds).execute(); + await this.db.deleteFrom('album_asset').where('album_asset.assetId', 'in', assetIds).execute(); } @Chunked({ paramIndex: 1 }) @@ -228,8 +228,8 @@ export class AlbumRepository { await this.db .deleteFrom('album_asset') - .where('album_asset.albumsId', '=', albumId) - .where('album_asset.assetsId', 'in', assetIds) + .where('album_asset.albumId', '=', albumId) + .where('album_asset.assetId', 'in', assetIds) .execute(); } @@ -250,10 +250,10 @@ export class AlbumRepository { return this.db .selectFrom('album_asset') .selectAll() - .where('album_asset.albumsId', '=', albumId) - .where('album_asset.assetsId', 'in', assetIds) + .where('album_asset.albumId', '=', albumId) + .where('album_asset.assetId', 'in', assetIds) .execute() - .then((results) => new Set(results.map(({ assetsId }) => assetsId))); + .then((results) => new Set(results.map(({ assetId }) => assetId))); } async addAssetIds(albumId: string, assetIds: string[]): Promise { @@ -276,7 +276,7 @@ export class AlbumRepository { await tx .insertInto('album_user') .values( - albumUsers.map((albumUser) => ({ albumsId: newAlbum.id, usersId: albumUser.userId, role: albumUser.role })), + albumUsers.map((albumUser) => ({ albumId: newAlbum.id, userId: albumUser.userId, role: albumUser.role })), ) .execute(); } @@ -317,12 +317,12 @@ export class AlbumRepository { await db .insertInto('album_asset') - .values(assetIds.map((assetId) => ({ albumsId: albumId, assetsId: assetId }))) + .values(assetIds.map((assetId) => ({ albumId, assetId }))) .execute(); } @Chunked({ chunkSize: 30_000 }) - async addAssetIdsToAlbums(values: { albumsId: string; assetsId: string }[]): Promise { + async addAssetIdsToAlbums(values: { albumId: string; assetId: string }[]): Promise { if (values.length === 0) { return; } @@ -344,7 +344,7 @@ export class AlbumRepository { .updateTable('album') .set((eb) => ({ albumThumbnailAssetId: this.updateThumbnailBuilder(eb) - .select('album_asset.assetsId') + .select('album_asset.assetId') .orderBy('asset.fileCreatedAt', 'desc') .limit(1), })) @@ -360,7 +360,7 @@ export class AlbumRepository { eb.exists( this.updateThumbnailBuilder(eb) .select(sql`1`.as('1')) - .whereRef('album.albumThumbnailAssetId', '=', 'album_asset.assetsId'), // Has invalid assets + .whereRef('album.albumThumbnailAssetId', '=', 'album_asset.assetId'), // Has invalid assets ), ), ]), @@ -375,8 +375,40 @@ export class AlbumRepository { return eb .selectFrom('album_asset') .innerJoin('asset', (join) => - join.onRef('album_asset.assetsId', '=', 'asset.id').on('asset.deletedAt', 'is', null), + join.onRef('album_asset.assetId', '=', 'asset.id').on('asset.deletedAt', 'is', null), ) - .whereRef('album_asset.albumsId', '=', 'album.id'); + .whereRef('album_asset.albumId', '=', 'album.id'); + } + + /** + * Get per-user asset contribution counts for a single album. + * Excludes deleted assets, orders by count desc. + */ + @GenerateSql({ params: [DummyValue.UUID] }) + getContributorCounts(id: string) { + return this.db + .selectFrom('album_asset') + .innerJoin('asset', 'asset.id', 'assetId') + .where('asset.deletedAt', 'is', sql.lit(null)) + .where('album_asset.albumId', '=', id) + .select('asset.ownerId as userId') + .select((eb) => eb.fn.countAll().as('assetCount')) + .groupBy('asset.ownerId') + .orderBy('assetCount', 'desc') + .execute(); + } + + @GenerateSql({ params: [{ sourceAssetId: DummyValue.UUID, targetAssetId: DummyValue.UUID }] }) + async copyAlbums({ sourceAssetId, targetAssetId }: { sourceAssetId: string; targetAssetId: string }) { + return this.db + .insertInto('album_asset') + .expression((eb) => + eb + .selectFrom('album_asset') + .select((eb) => ['album_asset.albumId', eb.val(targetAssetId).as('assetId')]) + .where('album_asset.assetId', '=', sourceAssetId), + ) + .onConflict((oc) => oc.doNothing()) + .execute(); } } diff --git a/server/src/repositories/asset-job.repository.ts b/server/src/repositories/asset-job.repository.ts index ca1291b852..8d54e93c87 100644 --- a/server/src/repositories/asset-job.repository.ts +++ b/server/src/repositories/asset-job.repository.ts @@ -16,6 +16,7 @@ import { withExifInner, withFaces, withFacesAndPeople, + withFilePath, withFiles, } from 'src/utils/database'; @@ -45,8 +46,8 @@ export class AssetJobRepository { eb .selectFrom('tag') .select(['tag.value']) - .innerJoin('tag_asset', 'tag.id', 'tag_asset.tagsId') - .whereRef('asset.id', '=', 'tag_asset.assetsId'), + .innerJoin('tag_asset', 'tag.id', 'tag_asset.tagId') + .whereRef('asset.id', '=', 'tag_asset.assetId'), ).as('tags'), ) .limit(1) @@ -192,6 +193,15 @@ export class AssetJobRepository { .executeTakeFirst(); } + @GenerateSql({ params: [DummyValue.UUID] }) + getForOcr(id: string) { + return this.db + .selectFrom('asset') + .select((eb) => ['asset.visibility', withFilePath(eb, AssetFileType.Preview).as('previewFile')]) + .where('asset.id', '=', id) + .executeTakeFirst(); + } + @GenerateSql({ params: [[DummyValue.UUID]] }) getForSyncAssets(ids: string[]) { return this.db @@ -348,6 +358,21 @@ export class AssetJobRepository { .stream(); } + @GenerateSql({ params: [], stream: true }) + streamForOcrJob(force?: boolean) { + return this.db + .selectFrom('asset') + .select(['asset.id']) + .$if(!force, (qb) => + qb + .innerJoin('asset_job_status', 'asset_job_status.assetId', 'asset.id') + .where('asset_job_status.ocrAt', 'is', null), + ) + .where('asset.deletedAt', 'is', null) + .where('asset.visibility', '!=', AssetVisibility.Hidden) + .stream(); + } + @GenerateSql({ params: [DummyValue.DATE], stream: true }) streamForMigrationJob() { return this.db.selectFrom('asset').select(['id']).where('asset.deletedAt', 'is', null).stream(); diff --git a/server/src/repositories/asset.repository.ts b/server/src/repositories/asset.repository.ts index 5c3bd8996c..d3d9ada80f 100644 --- a/server/src/repositories/asset.repository.ts +++ b/server/src/repositories/asset.repository.ts @@ -4,6 +4,7 @@ import { isEmpty, isUndefined, omitBy } from 'lodash'; import { InjectKysely } from 'nestjs-kysely'; import { Stack } from 'src/database'; import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators'; +import { AuthDto } from 'src/dtos/auth.dto'; import { AssetFileType, AssetMetadataKey, AssetOrder, AssetStatus, AssetType, AssetVisibility } from 'src/enum'; import { DB } from 'src/schema'; import { AssetExifTable } from 'src/schema/tables/asset-exif.table'; @@ -72,9 +73,10 @@ export interface TimeBucketItem { count: number; } -export interface MonthDay { +export interface YearMonthDay { day: number; month: number; + year: number; } interface AssetExploreFieldOptions { @@ -204,6 +206,7 @@ export class AssetRepository { metadataExtractedAt: eb.ref('excluded.metadataExtractedAt'), previewAt: eb.ref('excluded.previewAt'), thumbnailAt: eb.ref('excluded.thumbnailAt'), + ocrAt: eb.ref('excluded.ocrAt'), }, values[0], ), @@ -257,8 +260,8 @@ export class AssetRepository { return this.db.insertInto('asset').values(assets).returningAll().execute(); } - @GenerateSql({ params: [DummyValue.UUID, { day: 1, month: 1 }] }) - getByDayOfYear(ownerIds: string[], { day, month }: MonthDay) { + @GenerateSql({ params: [DummyValue.UUID, { year: 2000, day: 1, month: 1 }] }) + getByDayOfYear(ownerIds: string[], { year, day, month }: YearMonthDay) { return this.db .with('res', (qb) => qb @@ -268,7 +271,7 @@ export class AssetRepository { eb .fn('generate_series', [ sql`(select date_part('year', min(("localDateTime" at time zone 'UTC')::date))::int from asset)`, - sql`date_part('year', current_date)::int - 1`, + sql`${year - 1}`, ]) .as('year'), ) @@ -561,8 +564,8 @@ export class AssetRepository { .$if(!!options.visibility, (qb) => qb.where('asset.visibility', '=', options.visibility!)) .$if(!!options.albumId, (qb) => qb - .innerJoin('album_asset', 'asset.id', 'album_asset.assetsId') - .where('album_asset.albumsId', '=', asUuid(options.albumId!)), + .innerJoin('album_asset', 'asset.id', 'album_asset.assetId') + .where('album_asset.albumId', '=', asUuid(options.albumId!)), ) .$if(!!options.personId, (qb) => hasPeople(qb, [options.personId!])) .$if(!!options.withStacked, (qb) => @@ -589,9 +592,9 @@ export class AssetRepository { } @GenerateSql({ - params: [DummyValue.TIME_BUCKET, { withStacked: true }], + params: [DummyValue.TIME_BUCKET, { withStacked: true }, { user: { id: DummyValue.UUID } }], }) - getTimeBucket(timeBucket: string, options: TimeBucketOptions) { + getTimeBucket(timeBucket: string, options: TimeBucketOptions, auth: AuthDto) { const query = this.db .with('cte', (qb) => qb @@ -601,7 +604,7 @@ export class AssetRepository { 'asset.duration', 'asset.id', 'asset.visibility', - 'asset.isFavorite', + sql`asset."isFavorite" and asset."ownerId" = ${auth.user.id}`.as('isFavorite'), sql`asset.type = 'IMAGE'`.as('isImage'), sql`asset."deletedAt" is not null`.as('isTrashed'), 'asset.livePhotoVideoId', @@ -639,8 +642,8 @@ export class AssetRepository { eb.exists( eb .selectFrom('album_asset') - .whereRef('album_asset.assetsId', '=', 'asset.id') - .where('album_asset.albumsId', '=', asUuid(options.albumId!)), + .whereRef('album_asset.assetId', '=', 'asset.id') + .where('album_asset.albumId', '=', asUuid(options.albumId!)), ), ), ) diff --git a/server/src/repositories/config.repository.ts b/server/src/repositories/config.repository.ts index d5c279099c..05d4bd2ac3 100644 --- a/server/src/repositories/config.repository.ts +++ b/server/src/repositories/config.repository.ts @@ -85,6 +85,7 @@ export interface EnvData { root: string; indexHtml: string; }; + corePlugin: string; }; redis: RedisOptions; @@ -102,6 +103,11 @@ export interface EnvData { workers: ImmichWorker[]; + plugins: { + enabled: boolean; + installFolder?: string; + }; + noColor: boolean; nodeVersion?: string; } @@ -304,6 +310,7 @@ const getEnv = (): EnvData => { root: folders.web, indexHtml: join(folders.web, 'index.html'), }, + corePlugin: join(buildFolder, 'corePlugin'), }, storage: { @@ -319,6 +326,11 @@ const getEnv = (): EnvData => { workers, + plugins: { + enabled: !!dto.IMMICH_PLUGINS_ENABLED, + installFolder: dto.IMMICH_PLUGINS_INSTALL_FOLDER, + }, + noColor: !!dto.NO_COLOR, }; }; diff --git a/server/src/repositories/crypto.repository.ts b/server/src/repositories/crypto.repository.ts index c3136db456..bcd791ade2 100644 --- a/server/src/repositories/crypto.repository.ts +++ b/server/src/repositories/crypto.repository.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; import { compareSync, hash } from 'bcrypt'; +import jwt from 'jsonwebtoken'; import { createHash, createPublicKey, createVerify, randomBytes, randomUUID } from 'node:crypto'; import { createReadStream } from 'node:fs'; @@ -57,4 +58,12 @@ export class CryptoRepository { randomBytesAsText(bytes: number) { return randomBytes(bytes).toString('base64').replaceAll(/\W/g, ''); } + + signJwt(payload: string | object | Buffer, secret: string, options?: jwt.SignOptions): string { + return jwt.sign(payload, secret, { algorithm: 'HS256', ...options }); + } + + verifyJwt(token: string, secret: string): T { + return jwt.verify(token, secret, { algorithms: ['HS256'] }) as T; + } } diff --git a/server/src/repositories/database.repository.ts b/server/src/repositories/database.repository.ts index e5d88339c8..842576fafb 100644 --- a/server/src/repositories/database.repository.ts +++ b/server/src/repositories/database.repository.ts @@ -231,7 +231,7 @@ export class DatabaseRepository { } private async reindexVectors(indexName: VectorIndex, { lists }: { lists?: number } = {}): Promise { - this.logger.log(`Reindexing ${indexName}`); + this.logger.log(`Reindexing ${indexName} (This may take a while, do not restart)`); const table = VECTOR_INDEX_TABLES[indexName]; const vectorExtension = await getVectorExtension(this.db); @@ -360,18 +360,7 @@ export class DatabaseRepository { async runMigrations(): Promise { this.logger.debug('Running migrations'); - const migrator = new Migrator({ - db: this.db, - migrationLockTableName: 'kysely_migrations_lock', - allowUnorderedMigrations: this.configRepository.isDev(), - migrationTableName: 'kysely_migrations', - provider: new FileMigrationProvider({ - fs: { readdir }, - path: { join }, - // eslint-disable-next-line unicorn/prefer-module - migrationFolder: join(__dirname, '..', 'schema/migrations'), - }), - }); + const migrator = this.createMigrator(); const { error, results } = await migrator.migrateToLatest(); @@ -477,4 +466,50 @@ export class DatabaseRepository { private async releaseLock(lock: DatabaseLock, connection: Kysely): Promise { await sql`SELECT pg_advisory_unlock(${lock})`.execute(connection); } + + async revertLastMigration(): Promise { + this.logger.debug('Reverting last migration'); + + const migrator = this.createMigrator(); + const { error, results } = await migrator.migrateDown(); + + for (const result of results ?? []) { + if (result.status === 'Success') { + this.logger.log(`Reverted migration "${result.migrationName}"`); + } + + if (result.status === 'Error') { + this.logger.warn(`Failed to revert migration "${result.migrationName}"`); + } + } + + if (error) { + this.logger.error(`Failed to revert migrations: ${error}`); + throw error; + } + + const reverted = results?.find((result) => result.direction === 'Down' && result.status === 'Success'); + if (!reverted) { + this.logger.debug('No migrations to revert'); + return undefined; + } + + this.logger.debug('Finished reverting migration'); + return reverted.migrationName; + } + + private createMigrator(): Migrator { + return new Migrator({ + db: this.db, + migrationLockTableName: 'kysely_migrations_lock', + allowUnorderedMigrations: this.configRepository.isDev(), + migrationTableName: 'kysely_migrations', + provider: new FileMigrationProvider({ + fs: { readdir }, + path: { join }, + // eslint-disable-next-line unicorn/prefer-module + migrationFolder: join(__dirname, '..', 'schema/migrations'), + }), + }); + } } diff --git a/server/src/repositories/download.repository.ts b/server/src/repositories/download.repository.ts index ecc1e4d3ab..61a0f23d5e 100644 --- a/server/src/repositories/download.repository.ts +++ b/server/src/repositories/download.repository.ts @@ -26,8 +26,8 @@ export class DownloadRepository { downloadAlbumId(albumId: string) { return builder(this.db) - .innerJoin('album_asset', 'asset.id', 'album_asset.assetsId') - .where('album_asset.albumsId', '=', albumId) + .innerJoin('album_asset', 'asset.id', 'album_asset.assetId') + .where('album_asset.albumId', '=', albumId) .stream(); } diff --git a/server/src/repositories/email.repository.ts b/server/src/repositories/email.repository.ts index 78c89b4a9d..1bc4f0981a 100644 --- a/server/src/repositories/email.repository.ts +++ b/server/src/repositories/email.repository.ts @@ -23,6 +23,7 @@ export type SendEmailOptions = { export type SmtpOptions = { host: string; port?: number; + secure?: boolean; username?: string; password?: string; ignoreCert?: boolean; diff --git a/server/src/repositories/event.repository.ts b/server/src/repositories/event.repository.ts index ec4c8a8f52..80d411c5ae 100644 --- a/server/src/repositories/event.repository.ts +++ b/server/src/repositories/event.repository.ts @@ -1,27 +1,16 @@ import { Injectable } from '@nestjs/common'; import { ModuleRef, Reflector } from '@nestjs/core'; -import { - OnGatewayConnection, - OnGatewayDisconnect, - OnGatewayInit, - WebSocketGateway, - WebSocketServer, -} from '@nestjs/websockets'; import { ClassConstructor } from 'class-transformer'; import _ from 'lodash'; -import { Server, Socket } from 'socket.io'; +import { Socket } from 'socket.io'; import { SystemConfig } from 'src/config'; +import { Asset } from 'src/database'; import { EventConfig } from 'src/decorators'; -import { AssetResponseDto } from 'src/dtos/asset-response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; -import { NotificationDto } from 'src/dtos/notification.dto'; -import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto'; -import { SyncAssetExifV1, SyncAssetV1 } from 'src/dtos/sync.dto'; -import { ImmichWorker, MetadataKey, QueueName } from 'src/enum'; +import { ImmichWorker, JobStatus, MetadataKey, QueueName, UserAvatarColor, UserStatus } from 'src/enum'; import { ConfigRepository } from 'src/repositories/config.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { JobItem, JobSource } from 'src/types'; -import { handlePromiseError } from 'src/utils/misc'; type EmitHandlers = Partial<{ [T in EmitEvent]: Array> }>; @@ -53,6 +42,7 @@ type EventMap = { AlbumInvite: [{ id: string; userId: string }]; // asset events + AssetCreate: [{ asset: Asset }]; AssetTag: [{ assetId: string }]; AssetUntag: [{ assetId: string }]; AssetHide: [{ assetId: string; userId: string }]; @@ -66,8 +56,19 @@ type EventMap = { AssetDeleteAll: [{ assetIds: string[]; userId: string }]; AssetRestoreAll: [{ assetIds: string[]; userId: string }]; + /** a worker receives a job and emits this event to run it */ + JobRun: [QueueName, JobItem]; + /** job pre-hook */ JobStart: [QueueName, JobItem]; - JobFailed: [{ job: JobItem; error: Error | any }]; + /** job post-hook */ + JobComplete: [QueueName, JobItem]; + /** job finishes without error */ + JobSuccess: [JobSuccessEvent]; + /** job finishes with error */ + JobError: [JobErrorEvent]; + + // queue events + QueueStart: [QueueStartEvent]; // session events SessionDelete: [{ sessionId: string }]; @@ -82,38 +83,50 @@ type EventMap = { // user events UserSignup: [{ notify: boolean; id: string; password?: string }]; + UserCreate: [UserEvent]; + /** user is soft deleted */ + UserTrash: [UserEvent]; + /** user is permanently deleted */ + UserDelete: [UserEvent]; + UserRestore: [UserEvent]; + + AuthChangePassword: [{ userId: string; currentSessionId?: string; invalidateSessions?: boolean }]; // websocket events WebsocketConnect: [{ userId: string }]; }; -export const serverEvents = ['ConfigUpdate'] as const; -export type ServerEvents = (typeof serverEvents)[number]; +type JobSuccessEvent = { job: JobItem; response?: JobStatus }; +type JobErrorEvent = { job: JobItem; error: Error | any }; + +type QueueStartEvent = { + name: QueueName; +}; + +type UserEvent = { + name: string; + id: string; + createdAt: Date; + updatedAt: Date; + deletedAt: Date | null; + status: UserStatus; + email: string; + profileImagePath: string; + isAdmin: boolean; + shouldChangePassword: boolean; + avatarColor: UserAvatarColor | null; + oauthId: string; + storageLabel: string | null; + quotaSizeInBytes: number | null; + quotaUsageInBytes: number; + profileChangedAt: Date; +}; export type EmitEvent = keyof EventMap; export type EmitHandler = (...args: ArgsOf) => Promise | void; export type ArgOf = EventMap[T][0]; export type ArgsOf = EventMap[T]; -export interface ClientEventMap { - on_upload_success: [AssetResponseDto]; - on_user_delete: [string]; - on_asset_delete: [string]; - on_asset_trash: [string[]]; - on_asset_update: [AssetResponseDto]; - on_asset_hidden: [string]; - on_asset_restore: [string[]]; - on_asset_stack_update: string[]; - on_person_thumbnail: [string]; - on_server_version: [ServerVersionResponseDto]; - on_config_update: []; - on_new_release: [ReleaseNotification]; - on_notification: [NotificationDto]; - on_session_delete: [string]; - - AssetUploadReadyV1: [{ asset: SyncAssetV1; exif: SyncAssetExifV1 }]; -} - export type EventItem = { event: T; handler: EmitHandler; @@ -122,18 +135,9 @@ export type EventItem = { export type AuthFn = (client: Socket) => Promise; -@WebSocketGateway({ - cors: true, - path: '/api/socket.io', - transports: ['websocket'], -}) @Injectable() -export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit { +export class EventRepository { private emitHandlers: EmitHandlers = {}; - private authFn?: AuthFn; - - @WebSocketServer() - private server?: Server; constructor( private moduleRef: ModuleRef, @@ -194,38 +198,6 @@ export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect } } - afterInit(server: Server) { - this.logger.log('Initialized websocket server'); - - for (const event of serverEvents) { - server.on(event, (...args: ArgsOf) => { - this.logger.debug(`Server event: ${event} (receive)`); - handlePromiseError(this.onEvent({ name: event, args, server: true }), this.logger); - }); - } - } - - async handleConnection(client: Socket) { - try { - this.logger.log(`Websocket Connect: ${client.id}`); - const auth = await this.authenticate(client); - await client.join(auth.user.id); - if (auth.session) { - await client.join(auth.session.id); - } - await this.onEvent({ name: 'WebsocketConnect', args: [{ userId: auth.user.id }], server: false }); - } catch (error: Error | any) { - this.logger.error(`Websocket connection error: ${error}`, error?.stack); - client.emit('error', 'unauthorized'); - client.disconnect(); - } - } - - async handleDisconnect(client: Socket) { - this.logger.log(`Websocket Disconnect: ${client.id}`); - await client.leave(client.nsp.name); - } - private addHandler(item: Item): void { const event = item.event; @@ -240,7 +212,7 @@ export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect return this.onEvent({ name: event, args, server: false }); } - private async onEvent(event: { name: T; args: ArgsOf; server: boolean }): Promise { + async onEvent(event: { name: T; args: ArgsOf; server: boolean }): Promise { const handlers = this.emitHandlers[event.name] || []; for (const { handler, server } of handlers) { // exclude handlers that ignore server events @@ -251,29 +223,4 @@ export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect await handler(...event.args); } } - - clientSend(event: T, room: string, ...data: ClientEventMap[T]) { - this.server?.to(room).emit(event, ...data); - } - - clientBroadcast(event: T, ...data: ClientEventMap[T]) { - this.server?.emit(event, ...data); - } - - serverSend(event: T, ...args: ArgsOf): void { - this.logger.debug(`Server event: ${event} (send)`); - this.server?.serverSideEmit(event, ...args); - } - - setAuthFn(fn: (client: Socket) => Promise) { - this.authFn = fn; - } - - private async authenticate(client: Socket) { - if (!this.authFn) { - throw new Error('Auth function not set'); - } - - return this.authFn(client); - } } diff --git a/server/src/repositories/index.ts b/server/src/repositories/index.ts index a01b46f3bd..c69536a327 100644 --- a/server/src/repositories/index.ts +++ b/server/src/repositories/index.ts @@ -25,12 +25,15 @@ import { MetadataRepository } from 'src/repositories/metadata.repository'; import { MoveRepository } from 'src/repositories/move.repository'; import { NotificationRepository } from 'src/repositories/notification.repository'; import { OAuthRepository } from 'src/repositories/oauth.repository'; +import { OcrRepository } from 'src/repositories/ocr.repository'; import { PartnerRepository } from 'src/repositories/partner.repository'; import { PersonRepository } from 'src/repositories/person.repository'; +import { PluginRepository } from 'src/repositories/plugin.repository'; import { ProcessRepository } from 'src/repositories/process.repository'; import { SearchRepository } from 'src/repositories/search.repository'; import { ServerInfoRepository } from 'src/repositories/server-info.repository'; import { SessionRepository } from 'src/repositories/session.repository'; +import { SharedLinkAssetRepository } from 'src/repositories/shared-link-asset.repository'; import { SharedLinkRepository } from 'src/repositories/shared-link.repository'; import { StackRepository } from 'src/repositories/stack.repository'; import { StorageRepository } from 'src/repositories/storage.repository'; @@ -43,6 +46,8 @@ import { TrashRepository } from 'src/repositories/trash.repository'; import { UserRepository } from 'src/repositories/user.repository'; import { VersionHistoryRepository } from 'src/repositories/version-history.repository'; import { ViewRepository } from 'src/repositories/view-repository'; +import { WebsocketRepository } from 'src/repositories/websocket.repository'; +import { WorkflowRepository } from 'src/repositories/workflow.repository'; export const repositories = [ AccessRepository, @@ -72,13 +77,16 @@ export const repositories = [ MoveRepository, NotificationRepository, OAuthRepository, + OcrRepository, PartnerRepository, PersonRepository, + PluginRepository, ProcessRepository, SearchRepository, SessionRepository, ServerInfoRepository, SharedLinkRepository, + SharedLinkAssetRepository, StackRepository, StorageRepository, SyncRepository, @@ -90,4 +98,6 @@ export const repositories = [ UserRepository, ViewRepository, VersionHistoryRepository, + WebsocketRepository, + WorkflowRepository, ]; diff --git a/server/src/repositories/job.repository.ts b/server/src/repositories/job.repository.ts index 5acd8d5746..cf2799a4cf 100644 --- a/server/src/repositories/job.repository.ts +++ b/server/src/repositories/job.repository.ts @@ -89,7 +89,7 @@ export class JobRepository { this.logger.debug(`Starting worker for queue: ${queueName}`); this.workers[queueName] = new Worker( queueName, - (job) => this.eventRepository.emit('JobStart', queueName, job as JobItem), + (job) => this.eventRepository.emit('JobRun', queueName, job as JobItem), { ...bull.config, concurrency: 1 }, ); } diff --git a/server/src/repositories/machine-learning.repository.ts b/server/src/repositories/machine-learning.repository.ts index d148dc782b..49778b5193 100644 --- a/server/src/repositories/machine-learning.repository.ts +++ b/server/src/repositories/machine-learning.repository.ts @@ -15,6 +15,7 @@ export interface BoundingBox { export enum ModelTask { FACIAL_RECOGNITION = 'facial-recognition', SEARCH = 'clip', + OCR = 'ocr', } export enum ModelType { @@ -23,6 +24,7 @@ export enum ModelType { RECOGNITION = 'recognition', TEXTUAL = 'textual', VISUAL = 'visual', + OCR = 'ocr', } export type ModelPayload = { imagePath: string } | { text: string }; @@ -30,7 +32,11 @@ export type ModelPayload = { imagePath: string } | { text: string }; type ModelOptions = { modelName: string }; export type FaceDetectionOptions = ModelOptions & { minScore: number }; - +export type OcrOptions = ModelOptions & { + minDetectionScore: number; + minRecognitionScore: number; + maxResolution: number; +}; type VisualResponse = { imageHeight: number; imageWidth: number }; export type ClipVisualRequest = { [ModelTask.SEARCH]: { [ModelType.VISUAL]: ModelOptions } }; export type ClipVisualResponse = { [ModelTask.SEARCH]: string } & VisualResponse; @@ -38,6 +44,21 @@ export type ClipVisualResponse = { [ModelTask.SEARCH]: string } & VisualResponse export type ClipTextualRequest = { [ModelTask.SEARCH]: { [ModelType.TEXTUAL]: ModelOptions } }; export type ClipTextualResponse = { [ModelTask.SEARCH]: string }; +export type OCR = { + text: string[]; + box: number[]; + boxScore: number[]; + textScore: number[]; +}; + +export type OcrRequest = { + [ModelTask.OCR]: { + [ModelType.DETECTION]: ModelOptions & { options: { minScore: number; maxResolution: number } }; + [ModelType.RECOGNITION]: ModelOptions & { options: { minScore: number } }; + }; +}; +export type OcrResponse = { [ModelTask.OCR]: OCR } & VisualResponse; + export type FacialRecognitionRequest = { [ModelTask.FACIAL_RECOGNITION]: { [ModelType.DETECTION]: ModelOptions & { options: { minScore: number } }; @@ -53,7 +74,7 @@ export interface Face { export type FacialRecognitionResponse = { [ModelTask.FACIAL_RECOGNITION]: Face[] } & VisualResponse; export type DetectedFaces = { faces: Face[] } & VisualResponse; -export type MachineLearningRequest = ClipVisualRequest | ClipTextualRequest | FacialRecognitionRequest; +export type MachineLearningRequest = ClipVisualRequest | ClipTextualRequest | FacialRecognitionRequest | OcrRequest; export type TextEncodingOptions = ModelOptions & { language?: string }; @Injectable() @@ -85,7 +106,7 @@ export class MachineLearningRepository { } } - if (!config.availabilityChecks.enabled) { + if (!config.enabled || !config.availabilityChecks.enabled) { return; } @@ -197,6 +218,17 @@ export class MachineLearningRepository { return response[ModelTask.SEARCH]; } + async ocr(imagePath: string, { modelName, minDetectionScore, minRecognitionScore, maxResolution }: OcrOptions) { + const request = { + [ModelTask.OCR]: { + [ModelType.DETECTION]: { modelName, options: { minScore: minDetectionScore, maxResolution } }, + [ModelType.RECOGNITION]: { modelName, options: { minScore: minRecognitionScore } }, + }, + }; + const response = await this.predict({ imagePath }, request); + return response[ModelTask.OCR]; + } + private async getFormData(payload: ModelPayload, config: MachineLearningRequest): Promise { const formData = new FormData(); formData.append('entries', JSON.stringify(config)); diff --git a/server/src/repositories/map.repository.ts b/server/src/repositories/map.repository.ts index 7f6e2a967a..304cf89c32 100644 --- a/server/src/repositories/map.repository.ts +++ b/server/src/repositories/map.repository.ts @@ -126,8 +126,8 @@ export class MapRepository { eb.exists((eb) => eb .selectFrom('album_asset') - .whereRef('asset.id', '=', 'album_asset.assetsId') - .where('album_asset.albumsId', 'in', albumIds), + .whereRef('asset.id', '=', 'album_asset.assetId') + .where('album_asset.albumId', 'in', albumIds), ), ); } diff --git a/server/src/repositories/media.repository.ts b/server/src/repositories/media.repository.ts index fa36c41798..d98e018efb 100644 --- a/server/src/repositories/media.repository.ts +++ b/server/src/repositories/media.repository.ts @@ -203,6 +203,9 @@ export class MediaRepository { isHDR: stream.color_transfer === 'smpte2084' || stream.color_transfer === 'arib-std-b67', bitrate: this.parseInt(stream.bit_rate), pixelFormat: stream.pix_fmt || 'yuv420p', + colorPrimaries: stream.color_primaries, + colorSpace: stream.color_space, + colorTransfer: stream.color_transfer, })), audioStreams: results.streams .filter((stream) => stream.codec_type === 'audio') diff --git a/server/src/repositories/memory.repository.ts b/server/src/repositories/memory.repository.ts index 65b4cb3df7..e62c083839 100644 --- a/server/src/repositories/memory.repository.ts +++ b/server/src/repositories/memory.repository.ts @@ -1,11 +1,11 @@ import { Injectable } from '@nestjs/common'; -import { Insertable, Kysely, sql, Updateable } from 'kysely'; +import { Insertable, Kysely, OrderByDirection, sql, Updateable } from 'kysely'; import { jsonArrayFrom } from 'kysely/helpers/postgres'; import { DateTime } from 'luxon'; import { InjectKysely } from 'nestjs-kysely'; import { Chunked, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators'; import { MemorySearchDto } from 'src/dtos/memory.dto'; -import { AssetVisibility } from 'src/enum'; +import { AssetOrderWithRandom, AssetVisibility } from 'src/enum'; import { DB } from 'src/schema'; import { MemoryTable } from 'src/schema/tables/memory.table'; import { IBulkAsset } from 'src/types'; @@ -18,7 +18,7 @@ export class MemoryRepository implements IBulkAsset { await this.db .deleteFrom('memory_asset') .using('asset') - .whereRef('memory_asset.assetsId', '=', 'asset.id') + .whereRef('memory_asset.assetId', '=', 'asset.id') .where('asset.visibility', '!=', AssetVisibility.Timeline) .execute(); @@ -64,7 +64,7 @@ export class MemoryRepository implements IBulkAsset { eb .selectFrom('asset') .selectAll('asset') - .innerJoin('memory_asset', 'asset.id', 'memory_asset.assetsId') + .innerJoin('memory_asset', 'asset.id', 'memory_asset.assetId') .whereRef('memory_asset.memoriesId', '=', 'memory.id') .orderBy('asset.fileCreatedAt', 'asc') .where('asset.visibility', '=', sql.lit(AssetVisibility.Timeline)) @@ -72,7 +72,12 @@ export class MemoryRepository implements IBulkAsset { ).as('assets'), ) .selectAll('memory') - .orderBy('memoryAt', 'desc') + .$call((qb) => + dto.order === AssetOrderWithRandom.Random + ? qb.orderBy(sql`RANDOM()`) + : qb.orderBy('memoryAt', (dto.order?.toLowerCase() || 'desc') as OrderByDirection), + ) + .$if(dto.size !== undefined, (qb) => qb.limit(dto.size!)) .execute(); } @@ -86,7 +91,7 @@ export class MemoryRepository implements IBulkAsset { const { id } = await tx.insertInto('memory').values(memory).returning('id').executeTakeFirstOrThrow(); if (assetIds.size > 0) { - const values = [...assetIds].map((assetId) => ({ memoriesId: id, assetsId: assetId })); + const values = [...assetIds].map((assetId) => ({ memoriesId: id, assetId })); await tx.insertInto('memory_asset').values(values).execute(); } @@ -116,12 +121,12 @@ export class MemoryRepository implements IBulkAsset { const results = await this.db .selectFrom('memory_asset') - .select(['assetsId']) + .select(['assetId']) .where('memoriesId', '=', id) - .where('assetsId', 'in', assetIds) + .where('assetId', 'in', assetIds) .execute(); - return new Set(results.map(({ assetsId }) => assetsId)); + return new Set(results.map(({ assetId }) => assetId)); } @GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }) @@ -132,7 +137,7 @@ export class MemoryRepository implements IBulkAsset { await this.db .insertInto('memory_asset') - .values(assetIds.map((assetId) => ({ memoriesId: id, assetsId: assetId }))) + .values(assetIds.map((assetId) => ({ memoriesId: id, assetId }))) .execute(); } @@ -143,7 +148,7 @@ export class MemoryRepository implements IBulkAsset { return; } - await this.db.deleteFrom('memory_asset').where('memoriesId', '=', id).where('assetsId', 'in', assetIds).execute(); + await this.db.deleteFrom('memory_asset').where('memoriesId', '=', id).where('assetId', 'in', assetIds).execute(); } private getByIdBuilder(id: string) { @@ -155,7 +160,7 @@ export class MemoryRepository implements IBulkAsset { eb .selectFrom('asset') .selectAll('asset') - .innerJoin('memory_asset', 'asset.id', 'memory_asset.assetsId') + .innerJoin('memory_asset', 'asset.id', 'memory_asset.assetId') .whereRef('memory_asset.memoriesId', '=', 'memory.id') .orderBy('asset.fileCreatedAt', 'asc') .where('asset.visibility', '=', sql.lit(AssetVisibility.Timeline)) diff --git a/server/src/repositories/metadata.repository.ts b/server/src/repositories/metadata.repository.ts index e2360156e4..32882de0e0 100644 --- a/server/src/repositories/metadata.repository.ts +++ b/server/src/repositories/metadata.repository.ts @@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common'; import { BinaryField, DefaultReadTaskOptions, ExifTool, Tags } from 'exiftool-vendored'; import geotz from 'geo-tz'; import { LoggingRepository } from 'src/repositories/logging.repository'; +import { mimeTypes } from 'src/utils/mime-types'; interface ExifDuration { Value: number; @@ -84,6 +85,7 @@ export class MetadataRepository { numericTags: [...DefaultReadTaskOptions.numericTags, 'FocalLength', 'FileSize'], /* eslint unicorn/no-array-callback-reference: off, unicorn/no-array-method-this-argument: off */ geoTz: (lat, lon) => geotz.find(lat, lon)[0], + geolocation: true, // Enable exiftool LFS to parse metadata for files larger than 2GB. readArgs: ['-api', 'largefilesupport=1'], writeArgs: ['-api', 'largefilesupport=1', '-overwrite_original'], @@ -102,7 +104,8 @@ export class MetadataRepository { } readTags(path: string): Promise { - return this.exiftool.read(path).catch((error) => { + const args = mimeTypes.isVideo(path) ? ['-ee'] : []; + return this.exiftool.read(path, args).catch((error) => { this.logger.warn(`Error reading exif data (${path}): ${error}\n${error?.stack}`); return {}; }) as Promise; diff --git a/server/src/repositories/ocr.repository.ts b/server/src/repositories/ocr.repository.ts new file mode 100644 index 0000000000..1da9a96ec5 --- /dev/null +++ b/server/src/repositories/ocr.repository.ts @@ -0,0 +1,68 @@ +import { Injectable } from '@nestjs/common'; +import { Insertable, Kysely, sql } from 'kysely'; +import { InjectKysely } from 'nestjs-kysely'; +import { DummyValue, GenerateSql } from 'src/decorators'; +import { DB } from 'src/schema'; +import { AssetOcrTable } from 'src/schema/tables/asset-ocr.table'; + +@Injectable() +export class OcrRepository { + constructor(@InjectKysely() private db: Kysely) {} + + @GenerateSql({ params: [DummyValue.UUID] }) + getById(id: string) { + return this.db.selectFrom('asset_ocr').selectAll('asset_ocr').where('asset_ocr.id', '=', id).executeTakeFirst(); + } + + @GenerateSql({ params: [DummyValue.UUID] }) + getByAssetId(id: string) { + return this.db.selectFrom('asset_ocr').selectAll('asset_ocr').where('asset_ocr.assetId', '=', id).execute(); + } + + deleteAll() { + return this.db.transaction().execute(async (trx: Kysely) => { + await sql`truncate ${sql.table('asset_ocr')}`.execute(trx); + await sql`truncate ${sql.table('ocr_search')}`.execute(trx); + }); + } + + @GenerateSql({ + params: [ + DummyValue.UUID, + [ + { + assetId: DummyValue.UUID, + x1: DummyValue.NUMBER, + y1: DummyValue.NUMBER, + x2: DummyValue.NUMBER, + y2: DummyValue.NUMBER, + x3: DummyValue.NUMBER, + y3: DummyValue.NUMBER, + x4: DummyValue.NUMBER, + y4: DummyValue.NUMBER, + text: DummyValue.STRING, + boxScore: DummyValue.NUMBER, + textScore: DummyValue.NUMBER, + }, + ], + ], + }) + upsert(assetId: string, ocrDataList: Insertable[]) { + let query = this.db.with('deleted_ocr', (db) => db.deleteFrom('asset_ocr').where('assetId', '=', assetId)); + if (ocrDataList.length > 0) { + const searchText = ocrDataList.map((item) => item.text.trim()).join(' '); + (query as any) = query + .with('inserted_ocr', (db) => db.insertInto('asset_ocr').values(ocrDataList)) + .with('inserted_search', (db) => + db + .insertInto('ocr_search') + .values({ assetId, text: searchText }) + .onConflict((oc) => oc.column('assetId').doUpdateSet((eb) => ({ text: eb.ref('excluded.text') }))), + ); + } else { + (query as any) = query.with('deleted_search', (db) => db.deleteFrom('ocr_search').where('assetId', '=', assetId)); + } + + return query.selectNoFrom(sql`1`.as('dummy')).execute(); + } +} diff --git a/server/src/repositories/plugin.repository.ts b/server/src/repositories/plugin.repository.ts new file mode 100644 index 0000000000..6217237947 --- /dev/null +++ b/server/src/repositories/plugin.repository.ts @@ -0,0 +1,176 @@ +import { Injectable } from '@nestjs/common'; +import { Kysely } from 'kysely'; +import { jsonArrayFrom } from 'kysely/helpers/postgres'; +import { InjectKysely } from 'nestjs-kysely'; +import { readdir } from 'node:fs/promises'; +import { columns } from 'src/database'; +import { DummyValue, GenerateSql } from 'src/decorators'; +import { PluginManifestDto } from 'src/dtos/plugin-manifest.dto'; +import { DB } from 'src/schema'; + +@Injectable() +export class PluginRepository { + constructor(@InjectKysely() private db: Kysely) {} + + /** + * Loads a plugin from a validated manifest file in a transaction. + * This ensures all plugin, filter, and action operations are atomic. + * @param manifest The validated plugin manifest + * @param basePath The base directory path where the plugin is located + */ + async loadPlugin(manifest: PluginManifestDto, basePath: string) { + return this.db.transaction().execute(async (tx) => { + // Upsert the plugin + const plugin = await tx + .insertInto('plugin') + .values({ + name: manifest.name, + title: manifest.title, + description: manifest.description, + author: manifest.author, + version: manifest.version, + wasmPath: `${basePath}/${manifest.wasm.path}`, + }) + .onConflict((oc) => + oc.column('name').doUpdateSet({ + title: manifest.title, + description: manifest.description, + author: manifest.author, + version: manifest.version, + wasmPath: `${basePath}/${manifest.wasm.path}`, + }), + ) + .returningAll() + .executeTakeFirstOrThrow(); + + const filters = manifest.filters + ? await tx + .insertInto('plugin_filter') + .values( + manifest.filters.map((filter) => ({ + pluginId: plugin.id, + methodName: filter.methodName, + title: filter.title, + description: filter.description, + supportedContexts: filter.supportedContexts, + schema: filter.schema, + })), + ) + .onConflict((oc) => + oc.column('methodName').doUpdateSet((eb) => ({ + pluginId: eb.ref('excluded.pluginId'), + title: eb.ref('excluded.title'), + description: eb.ref('excluded.description'), + supportedContexts: eb.ref('excluded.supportedContexts'), + schema: eb.ref('excluded.schema'), + })), + ) + .returningAll() + .execute() + : []; + + const actions = manifest.actions + ? await tx + .insertInto('plugin_action') + .values( + manifest.actions.map((action) => ({ + pluginId: plugin.id, + methodName: action.methodName, + title: action.title, + description: action.description, + supportedContexts: action.supportedContexts, + schema: action.schema, + })), + ) + .onConflict((oc) => + oc.column('methodName').doUpdateSet((eb) => ({ + pluginId: eb.ref('excluded.pluginId'), + title: eb.ref('excluded.title'), + description: eb.ref('excluded.description'), + supportedContexts: eb.ref('excluded.supportedContexts'), + schema: eb.ref('excluded.schema'), + })), + ) + .returningAll() + .execute() + : []; + + return { plugin, filters, actions }; + }); + } + + async readDirectory(path: string) { + return readdir(path, { withFileTypes: true }); + } + + @GenerateSql({ params: [DummyValue.UUID] }) + getPlugin(id: string) { + return this.db + .selectFrom('plugin') + .select((eb) => [ + ...columns.plugin, + jsonArrayFrom( + eb.selectFrom('plugin_filter').selectAll().whereRef('plugin_filter.pluginId', '=', 'plugin.id'), + ).as('filters'), + jsonArrayFrom( + eb.selectFrom('plugin_action').selectAll().whereRef('plugin_action.pluginId', '=', 'plugin.id'), + ).as('actions'), + ]) + .where('plugin.id', '=', id) + .executeTakeFirst(); + } + + @GenerateSql({ params: [DummyValue.STRING] }) + getPluginByName(name: string) { + return this.db + .selectFrom('plugin') + .select((eb) => [ + ...columns.plugin, + jsonArrayFrom( + eb.selectFrom('plugin_filter').selectAll().whereRef('plugin_filter.pluginId', '=', 'plugin.id'), + ).as('filters'), + jsonArrayFrom( + eb.selectFrom('plugin_action').selectAll().whereRef('plugin_action.pluginId', '=', 'plugin.id'), + ).as('actions'), + ]) + .where('plugin.name', '=', name) + .executeTakeFirst(); + } + + @GenerateSql() + getAllPlugins() { + return this.db + .selectFrom('plugin') + .select((eb) => [ + ...columns.plugin, + jsonArrayFrom( + eb.selectFrom('plugin_filter').selectAll().whereRef('plugin_filter.pluginId', '=', 'plugin.id'), + ).as('filters'), + jsonArrayFrom( + eb.selectFrom('plugin_action').selectAll().whereRef('plugin_action.pluginId', '=', 'plugin.id'), + ).as('actions'), + ]) + .orderBy('plugin.name') + .execute(); + } + + @GenerateSql({ params: [DummyValue.UUID] }) + getFilter(id: string) { + return this.db.selectFrom('plugin_filter').selectAll().where('id', '=', id).executeTakeFirst(); + } + + @GenerateSql({ params: [DummyValue.UUID] }) + getFiltersByPlugin(pluginId: string) { + return this.db.selectFrom('plugin_filter').selectAll().where('pluginId', '=', pluginId).execute(); + } + + @GenerateSql({ params: [DummyValue.UUID] }) + getAction(id: string) { + return this.db.selectFrom('plugin_action').selectAll().where('id', '=', id).executeTakeFirst(); + } + + @GenerateSql({ params: [DummyValue.UUID] }) + getActionsByPlugin(pluginId: string) { + return this.db.selectFrom('plugin_action').selectAll().where('pluginId', '=', pluginId).execute(); + } +} diff --git a/server/src/repositories/search.repository.ts b/server/src/repositories/search.repository.ts index 88de2fb06f..615b35c417 100644 --- a/server/src/repositories/search.repository.ts +++ b/server/src/repositories/search.repository.ts @@ -84,6 +84,10 @@ export interface SearchEmbeddingOptions { userIds: string[]; } +export interface SearchOcrOptions { + ocr?: string; +} + export interface SearchPeopleOptions { personIds?: string[]; } @@ -114,7 +118,8 @@ type BaseAssetSearchOptions = SearchDateOptions & SearchUserIdOptions & SearchPeopleOptions & SearchTagOptions & - SearchAlbumOptions; + SearchAlbumOptions & + SearchOcrOptions; export type AssetSearchOptions = BaseAssetSearchOptions & SearchRelationOptions; @@ -127,7 +132,10 @@ export type SmartSearchOptions = SearchDateOptions & SearchStatusOptions & SearchUserIdOptions & SearchPeopleOptions & - SearchTagOptions; + SearchTagOptions & + SearchOcrOptions; + +export type OcrSearchOptions = SearchDateOptions & SearchOcrOptions; export type LargeAssetSearchOptions = AssetSearchOptions & { minFileSize?: number }; @@ -160,10 +168,17 @@ export interface GetCitiesOptions extends GetStatesOptions { export interface GetCameraModelsOptions { make?: string; + lensModel?: string; } export interface GetCameraMakesOptions { model?: string; + lensModel?: string; +} + +export interface GetCameraLensModelsOptions { + make?: string; + model?: string; } @Injectable() @@ -457,25 +472,40 @@ export class SearchRepository { return res.map((row) => row.city!); } - @GenerateSql({ params: [[DummyValue.UUID], DummyValue.STRING] }) - async getCameraMakes(userIds: string[], { model }: GetCameraMakesOptions): Promise { + @GenerateSql({ params: [[DummyValue.UUID], DummyValue.STRING, DummyValue.STRING] }) + async getCameraMakes(userIds: string[], { model, lensModel }: GetCameraMakesOptions): Promise { const res = await this.getExifField('make', userIds) .$if(!!model, (qb) => qb.where('model', '=', model!)) + .$if(!!lensModel, (qb) => qb.where('lensModel', '=', lensModel!)) .execute(); return res.map((row) => row.make!); } - @GenerateSql({ params: [[DummyValue.UUID], DummyValue.STRING] }) - async getCameraModels(userIds: string[], { make }: GetCameraModelsOptions): Promise { + @GenerateSql({ params: [[DummyValue.UUID], DummyValue.STRING, DummyValue.STRING] }) + async getCameraModels(userIds: string[], { make, lensModel }: GetCameraModelsOptions): Promise { const res = await this.getExifField('model', userIds) .$if(!!make, (qb) => qb.where('make', '=', make!)) + .$if(!!lensModel, (qb) => qb.where('lensModel', '=', lensModel!)) .execute(); return res.map((row) => row.model!); } - private getExifField(field: K, userIds: string[]) { + @GenerateSql({ params: [[DummyValue.UUID], DummyValue.STRING] }) + async getCameraLensModels(userIds: string[], { make, model }: GetCameraLensModelsOptions): Promise { + const res = await this.getExifField('lensModel', userIds) + .$if(!!make, (qb) => qb.where('make', '=', make!)) + .$if(!!model, (qb) => qb.where('model', '=', model!)) + .execute(); + + return res.map((row) => row.lensModel!); + } + + private getExifField( + field: K, + userIds: string[], + ) { return this.db .selectFrom('asset_exif') .select(field) diff --git a/server/src/repositories/session.repository.ts b/server/src/repositories/session.repository.ts index cdc0ab12db..52292b8e4a 100644 --- a/server/src/repositories/session.repository.ts +++ b/server/src/repositories/session.repository.ts @@ -101,6 +101,15 @@ export class SessionRepository { await this.db.deleteFrom('session').where('id', '=', asUuid(id)).execute(); } + @GenerateSql({ params: [{ userId: DummyValue.UUID, excludeId: DummyValue.UUID }] }) + async invalidate({ userId, excludeId }: { userId: string; excludeId?: string }) { + await this.db + .deleteFrom('session') + .where('userId', '=', userId) + .$if(!!excludeId, (qb) => qb.where('id', '!=', excludeId!)) + .execute(); + } + @GenerateSql({ params: [DummyValue.UUID] }) async lockAll(userId: string) { await this.db.updateTable('session').set({ pinExpiresAt: null }).where('userId', '=', userId).execute(); diff --git a/server/src/repositories/shared-link-asset.repository.ts b/server/src/repositories/shared-link-asset.repository.ts new file mode 100644 index 0000000000..1136546455 --- /dev/null +++ b/server/src/repositories/shared-link-asset.repository.ts @@ -0,0 +1,33 @@ +import { Kysely } from 'kysely'; +import { InjectKysely } from 'nestjs-kysely'; +import { DummyValue, GenerateSql } from 'src/decorators'; +import { DB } from 'src/schema'; + +export class SharedLinkAssetRepository { + constructor(@InjectKysely() private db: Kysely) {} + + async remove(sharedLinkId: string, assetId: string[]) { + const deleted = await this.db + .deleteFrom('shared_link_asset') + .where('shared_link_asset.sharedLinkId', '=', sharedLinkId) + .where('shared_link_asset.assetId', 'in', assetId) + .returning('assetId') + .execute(); + + return deleted.map((row) => row.assetId); + } + + @GenerateSql({ params: [{ sourceAssetId: DummyValue.UUID, targetAssetId: DummyValue.UUID }] }) + async copySharedLinks({ sourceAssetId, targetAssetId }: { sourceAssetId: string; targetAssetId: string }) { + return this.db + .insertInto('shared_link_asset') + .expression((eb) => + eb + .selectFrom('shared_link_asset') + .select((eb) => [eb.val(targetAssetId).as('assetId'), 'shared_link_asset.sharedLinkId']) + .where('shared_link_asset.assetId', '=', sourceAssetId), + ) + .onConflict((oc) => oc.doNothing()) + .execute(); + } +} diff --git a/server/src/repositories/shared-link.repository.ts b/server/src/repositories/shared-link.repository.ts index cdade25f76..7bfa9ac6ae 100644 --- a/server/src/repositories/shared-link.repository.ts +++ b/server/src/repositories/shared-link.repository.ts @@ -28,8 +28,8 @@ export class SharedLinkRepository { (eb) => eb .selectFrom('shared_link_asset') - .whereRef('shared_link.id', '=', 'shared_link_asset.sharedLinksId') - .innerJoin('asset', 'asset.id', 'shared_link_asset.assetsId') + .whereRef('shared_link.id', '=', 'shared_link_asset.sharedLinkId') + .innerJoin('asset', 'asset.id', 'shared_link_asset.assetId') .where('asset.deletedAt', 'is', null) .selectAll('asset') .innerJoinLateral( @@ -53,13 +53,13 @@ export class SharedLinkRepository { .selectAll('album') .whereRef('album.id', '=', 'shared_link.albumId') .where('album.deletedAt', 'is', null) - .leftJoin('album_asset', 'album_asset.albumsId', 'album.id') + .leftJoin('album_asset', 'album_asset.albumId', 'album.id') .leftJoinLateral( (eb) => eb .selectFrom('asset') .selectAll('asset') - .whereRef('album_asset.assetsId', '=', 'asset.id') + .whereRef('album_asset.assetId', '=', 'asset.id') .where('asset.deletedAt', 'is', null) .innerJoinLateral( (eb) => @@ -123,13 +123,13 @@ export class SharedLinkRepository { .selectFrom('shared_link') .selectAll('shared_link') .where('shared_link.userId', '=', userId) - .leftJoin('shared_link_asset', 'shared_link_asset.sharedLinksId', 'shared_link.id') + .leftJoin('shared_link_asset', 'shared_link_asset.sharedLinkId', 'shared_link.id') .leftJoinLateral( (eb) => eb .selectFrom('asset') .select((eb) => eb.fn.jsonAgg('asset').as('assets')) - .whereRef('asset.id', '=', 'shared_link_asset.assetsId') + .whereRef('asset.id', '=', 'shared_link_asset.assetId') .where('asset.deletedAt', 'is', null) .as('assets'), (join) => join.onTrue(), @@ -215,7 +215,7 @@ export class SharedLinkRepository { if (entity.assetIds && entity.assetIds.length > 0) { await this.db .insertInto('shared_link_asset') - .values(entity.assetIds!.map((assetsId) => ({ assetsId, sharedLinksId: id }))) + .values(entity.assetIds!.map((assetId) => ({ assetId, sharedLinkId: id }))) .execute(); } @@ -233,7 +233,7 @@ export class SharedLinkRepository { if (entity.assetIds && entity.assetIds.length > 0) { await this.db .insertInto('shared_link_asset') - .values(entity.assetIds!.map((assetsId) => ({ assetsId, sharedLinksId: id }))) + .values(entity.assetIds!.map((assetId) => ({ assetId, sharedLinkId: id }))) .execute(); } @@ -249,12 +249,12 @@ export class SharedLinkRepository { .selectFrom('shared_link') .selectAll('shared_link') .where('shared_link.id', '=', id) - .leftJoin('shared_link_asset', 'shared_link_asset.sharedLinksId', 'shared_link.id') + .leftJoin('shared_link_asset', 'shared_link_asset.sharedLinkId', 'shared_link.id') .leftJoinLateral( (eb) => eb .selectFrom('asset') - .whereRef('asset.id', '=', 'shared_link_asset.assetsId') + .whereRef('asset.id', '=', 'shared_link_asset.assetId') .selectAll('asset') .innerJoinLateral( (eb) => diff --git a/server/src/repositories/stack.repository.ts b/server/src/repositories/stack.repository.ts index ace9468177..d313d682bd 100644 --- a/server/src/repositories/stack.repository.ts +++ b/server/src/repositories/stack.repository.ts @@ -33,8 +33,8 @@ const withAssets = (eb: ExpressionBuilder, withTags = false) => { eb .selectFrom('tag') .select(columns.tag) - .innerJoin('tag_asset', 'tag.id', 'tag_asset.tagsId') - .whereRef('tag_asset.assetsId', '=', 'asset.id'), + .innerJoin('tag_asset', 'tag.id', 'tag_asset.tagId') + .whereRef('tag_asset.assetId', '=', 'asset.id'), ).as('tags'), ), ) @@ -162,4 +162,9 @@ export class StackRepository { .where('asset.id', '=', assetId) .executeTakeFirst(); } + + @GenerateSql({ params: [{ sourceId: DummyValue.UUID, targetId: DummyValue.UUID }] }) + merge({ sourceId, targetId }: { sourceId: string; targetId: string }) { + return this.db.updateTable('asset').set({ stackId: targetId }).where('asset.stackId', '=', sourceId).execute(); + } } diff --git a/server/src/repositories/storage.repository.ts b/server/src/repositories/storage.repository.ts index 7d6b634845..e901273b57 100644 --- a/server/src/repositories/storage.repository.ts +++ b/server/src/repositories/storage.repository.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import archiver from 'archiver'; import chokidar, { ChokidarOptions } from 'chokidar'; import { escapePath, glob, globStream } from 'fast-glob'; -import { constants, createReadStream, createWriteStream, existsSync, mkdirSync } from 'node:fs'; +import { constants, createReadStream, createWriteStream, existsSync, mkdirSync, ReadOptionsWithBuffer } from 'node:fs'; import fs from 'node:fs/promises'; import path from 'node:path'; import { Readable, Writable } from 'node:stream'; @@ -103,16 +103,20 @@ export class StorageRepository { }; } - async readFile(filepath: string, options?: fs.FileReadOptions): Promise { + async readFile(filepath: string, options?: ReadOptionsWithBuffer): Promise { const file = await fs.open(filepath); try { const { buffer } = await file.read(options); - return buffer; + return buffer as Buffer; } finally { await file.close(); } } + async readTextFile(filepath: string): Promise { + return fs.readFile(filepath, 'utf8'); + } + async checkFileExists(filepath: string, mode = constants.F_OK): Promise { try { await fs.access(filepath, mode); diff --git a/server/src/repositories/sync.repository.ts b/server/src/repositories/sync.repository.ts index d8be720f45..437e32da16 100644 --- a/server/src/repositories/sync.repository.ts +++ b/server/src/repositories/sync.repository.ts @@ -143,8 +143,8 @@ class AlbumSync extends BaseSync { getCreatedAfter({ nowId, userId, afterCreateId }: SyncCreatedAfterOptions) { return this.db .selectFrom('album_user') - .select(['albumsId as id', 'createId']) - .where('usersId', '=', userId) + .select(['albumId as id', 'createId']) + .where('userId', '=', userId) .$if(!!afterCreateId, (qb) => qb.where('createId', '>=', afterCreateId!)) .where('createId', '<', nowId) .orderBy('createId', 'asc') @@ -168,8 +168,8 @@ class AlbumSync extends BaseSync { const userId = options.userId; return this.upsertQuery('album', options) .distinctOn(['album.id', 'album.updateId']) - .leftJoin('album_user as album_users', 'album.id', 'album_users.albumsId') - .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_users.usersId', '=', userId)])) + .leftJoin('album_user as album_users', 'album.id', 'album_users.albumId') + .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_users.userId', '=', userId)])) .select([ 'album.id', 'album.ownerId', @@ -190,10 +190,10 @@ class AlbumAssetSync extends BaseSync { @GenerateSql({ params: [dummyBackfillOptions, DummyValue.UUID], stream: true }) getBackfill(options: SyncBackfillOptions, albumId: string) { return this.backfillQuery('album_asset', options) - .innerJoin('asset', 'asset.id', 'album_asset.assetsId') + .innerJoin('asset', 'asset.id', 'album_asset.assetId') .select(columns.syncAsset) .select('album_asset.updateId') - .where('album_asset.albumsId', '=', albumId) + .where('album_asset.albumId', '=', albumId) .stream(); } @@ -201,13 +201,13 @@ class AlbumAssetSync extends BaseSync { getUpdates(options: SyncQueryOptions, albumToAssetAck: SyncAck) { const userId = options.userId; return this.upsertQuery('asset', options) - .innerJoin('album_asset', 'album_asset.assetsId', 'asset.id') + .innerJoin('album_asset', 'album_asset.assetId', 'asset.id') .select(columns.syncAsset) .select('asset.updateId') .where('album_asset.updateId', '<=', albumToAssetAck.updateId) // Ensure we only send updates for assets that the client already knows about - .innerJoin('album', 'album.id', 'album_asset.albumsId') - .leftJoin('album_user', 'album_user.albumsId', 'album_asset.albumsId') - .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.usersId', '=', userId)])) + .innerJoin('album', 'album.id', 'album_asset.albumId') + .leftJoin('album_user', 'album_user.albumId', 'album_asset.albumId') + .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.userId', '=', userId)])) .stream(); } @@ -216,11 +216,11 @@ class AlbumAssetSync extends BaseSync { const userId = options.userId; return this.upsertQuery('album_asset', options) .select('album_asset.updateId') - .innerJoin('asset', 'asset.id', 'album_asset.assetsId') + .innerJoin('asset', 'asset.id', 'album_asset.assetId') .select(columns.syncAsset) - .innerJoin('album', 'album.id', 'album_asset.albumsId') - .leftJoin('album_user', 'album_user.albumsId', 'album_asset.albumsId') - .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.usersId', '=', userId)])) + .innerJoin('album', 'album.id', 'album_asset.albumId') + .leftJoin('album_user', 'album_user.albumId', 'album_asset.albumId') + .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.userId', '=', userId)])) .stream(); } } @@ -229,10 +229,10 @@ class AlbumAssetExifSync extends BaseSync { @GenerateSql({ params: [dummyBackfillOptions, DummyValue.UUID], stream: true }) getBackfill(options: SyncBackfillOptions, albumId: string) { return this.backfillQuery('album_asset', options) - .innerJoin('asset_exif', 'asset_exif.assetId', 'album_asset.assetsId') + .innerJoin('asset_exif', 'asset_exif.assetId', 'album_asset.assetId') .select(columns.syncAssetExif) .select('album_asset.updateId') - .where('album_asset.albumsId', '=', albumId) + .where('album_asset.albumId', '=', albumId) .stream(); } @@ -240,13 +240,13 @@ class AlbumAssetExifSync extends BaseSync { getUpdates(options: SyncQueryOptions, albumToAssetAck: SyncAck) { const userId = options.userId; return this.upsertQuery('asset_exif', options) - .innerJoin('album_asset', 'album_asset.assetsId', 'asset_exif.assetId') + .innerJoin('album_asset', 'album_asset.assetId', 'asset_exif.assetId') .select(columns.syncAssetExif) .select('asset_exif.updateId') .where('album_asset.updateId', '<=', albumToAssetAck.updateId) // Ensure we only send exif updates for assets that the client already knows about - .innerJoin('album', 'album.id', 'album_asset.albumsId') - .leftJoin('album_user', 'album_user.albumsId', 'album_asset.albumsId') - .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.usersId', '=', userId)])) + .innerJoin('album', 'album.id', 'album_asset.albumId') + .leftJoin('album_user', 'album_user.albumId', 'album_asset.albumId') + .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.userId', '=', userId)])) .stream(); } @@ -255,11 +255,11 @@ class AlbumAssetExifSync extends BaseSync { const userId = options.userId; return this.upsertQuery('album_asset', options) .select('album_asset.updateId') - .innerJoin('asset_exif', 'asset_exif.assetId', 'album_asset.assetsId') + .innerJoin('asset_exif', 'asset_exif.assetId', 'album_asset.assetId') .select(columns.syncAssetExif) - .innerJoin('album', 'album.id', 'album_asset.albumsId') - .leftJoin('album_user', 'album_user.albumsId', 'album_asset.albumsId') - .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.usersId', '=', userId)])) + .innerJoin('album', 'album.id', 'album_asset.albumId') + .leftJoin('album_user', 'album_user.albumId', 'album_asset.albumId') + .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.userId', '=', userId)])) .stream(); } } @@ -268,8 +268,8 @@ class AlbumToAssetSync extends BaseSync { @GenerateSql({ params: [dummyBackfillOptions, DummyValue.UUID], stream: true }) getBackfill(options: SyncBackfillOptions, albumId: string) { return this.backfillQuery('album_asset', options) - .select(['album_asset.assetsId as assetId', 'album_asset.albumsId as albumId', 'album_asset.updateId']) - .where('album_asset.albumsId', '=', albumId) + .select(['album_asset.assetId as assetId', 'album_asset.albumId as albumId', 'album_asset.updateId']) + .where('album_asset.albumId', '=', albumId) .stream(); } @@ -290,8 +290,8 @@ class AlbumToAssetSync extends BaseSync { eb.parens( eb .selectFrom('album_user') - .select(['album_user.albumsId as id']) - .where('album_user.usersId', '=', userId), + .select(['album_user.albumId as id']) + .where('album_user.userId', '=', userId), ), ), ), @@ -307,10 +307,10 @@ class AlbumToAssetSync extends BaseSync { getUpserts(options: SyncQueryOptions) { const userId = options.userId; return this.upsertQuery('album_asset', options) - .select(['album_asset.assetsId as assetId', 'album_asset.albumsId as albumId', 'album_asset.updateId']) - .innerJoin('album', 'album.id', 'album_asset.albumsId') - .leftJoin('album_user', 'album_user.albumsId', 'album_asset.albumsId') - .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.usersId', '=', userId)])) + .select(['album_asset.assetId as assetId', 'album_asset.albumId as albumId', 'album_asset.updateId']) + .innerJoin('album', 'album.id', 'album_asset.albumId') + .leftJoin('album_user', 'album_user.albumId', 'album_asset.albumId') + .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.userId', '=', userId)])) .stream(); } } @@ -321,7 +321,7 @@ class AlbumUserSync extends BaseSync { return this.backfillQuery('album_user', options) .select(columns.syncAlbumUser) .select('album_user.updateId') - .where('albumsId', '=', albumId) + .where('albumId', '=', albumId) .stream(); } @@ -342,8 +342,8 @@ class AlbumUserSync extends BaseSync { eb.parens( eb .selectFrom('album_user') - .select(['album_user.albumsId as id']) - .where('album_user.usersId', '=', userId), + .select(['album_user.albumId as id']) + .where('album_user.userId', '=', userId), ), ), ), @@ -363,7 +363,7 @@ class AlbumUserSync extends BaseSync { .select('album_user.updateId') .where((eb) => eb( - 'album_user.albumsId', + 'album_user.albumId', 'in', eb .selectFrom('album') @@ -373,8 +373,8 @@ class AlbumUserSync extends BaseSync { eb.parens( eb .selectFrom('album_user as albumUsers') - .select(['albumUsers.albumsId as id']) - .where('albumUsers.usersId', '=', userId), + .select(['albumUsers.albumId as id']) + .where('albumUsers.userId', '=', userId), ), ), ), @@ -550,7 +550,7 @@ class MemoryToAssetSync extends BaseSync { @GenerateSql({ params: [dummyQueryOptions], stream: true }) getUpserts(options: SyncQueryOptions) { return this.upsertQuery('memory_asset', options) - .select(['memoriesId as memoryId', 'assetsId as assetId']) + .select(['memoriesId as memoryId', 'assetId as assetId']) .select('updateId') .where('memoriesId', 'in', (eb) => eb.selectFrom('memory').select('id').where('ownerId', '=', options.userId)) .stream(); diff --git a/server/src/repositories/tag.repository.ts b/server/src/repositories/tag.repository.ts index 9bbb62bd8b..d4572886af 100644 --- a/server/src/repositories/tag.repository.ts +++ b/server/src/repositories/tag.repository.ts @@ -97,9 +97,9 @@ export class TagRepository { const results = await this.db .selectFrom('tag_asset') - .select(['assetsId as assetId']) - .where('tagsId', '=', tagId) - .where('assetsId', 'in', assetIds) + .select(['assetId as assetId']) + .where('tagId', '=', tagId) + .where('assetId', 'in', assetIds) .execute(); return new Set(results.map(({ assetId }) => assetId)); @@ -114,7 +114,7 @@ export class TagRepository { await this.db .insertInto('tag_asset') - .values(assetIds.map((assetId) => ({ tagsId: tagId, assetsId: assetId }))) + .values(assetIds.map((assetId) => ({ tagId, assetId }))) .execute(); } @@ -125,10 +125,10 @@ export class TagRepository { return; } - await this.db.deleteFrom('tag_asset').where('tagsId', '=', tagId).where('assetsId', 'in', assetIds).execute(); + await this.db.deleteFrom('tag_asset').where('tagId', '=', tagId).where('assetId', 'in', assetIds).execute(); } - @GenerateSql({ params: [[{ assetId: DummyValue.UUID, tagsIds: [DummyValue.UUID] }]] }) + @GenerateSql({ params: [[{ assetId: DummyValue.UUID, tagIds: DummyValue.UUID }]] }) @Chunked() upsertAssetIds(items: Insertable[]) { if (items.length === 0) { @@ -147,7 +147,7 @@ export class TagRepository { @Chunked({ paramIndex: 1 }) replaceAssetTags(assetId: string, tagIds: string[]) { return this.db.transaction().execute(async (tx) => { - await tx.deleteFrom('tag_asset').where('assetsId', '=', assetId).execute(); + await tx.deleteFrom('tag_asset').where('assetId', '=', assetId).execute(); if (tagIds.length === 0) { return; @@ -155,7 +155,7 @@ export class TagRepository { return tx .insertInto('tag_asset') - .values(tagIds.map((tagId) => ({ tagsId: tagId, assetsId: assetId }))) + .values(tagIds.map((tagId) => ({ tagId, assetId }))) .onConflict((oc) => oc.doNothing()) .returningAll() .execute(); @@ -163,22 +163,22 @@ export class TagRepository { } async deleteEmptyTags() { - // TODO rewrite as a single statement - await this.db.transaction().execute(async (tx) => { - const result = await tx - .selectFrom('asset') - .innerJoin('tag_asset', 'tag_asset.assetsId', 'asset.id') - .innerJoin('tag_closure', 'tag_closure.id_descendant', 'tag_asset.tagsId') - .innerJoin('tag', 'tag.id', 'tag_closure.id_descendant') - .select((eb) => ['tag.id', eb.fn.count('asset.id').as('count')]) - .groupBy('tag.id') - .execute(); + const result = await this.db + .deleteFrom('tag') + .where(({ not, exists, selectFrom }) => + not( + exists( + selectFrom('tag_closure') + .whereRef('tag.id', '=', 'tag_closure.id_ancestor') + .innerJoin('tag_asset', 'tag_closure.id_descendant', 'tag_asset.tagId'), + ), + ), + ) + .executeTakeFirst(); - const ids = result.filter(({ count }) => count === 0).map(({ id }) => id); - if (ids.length > 0) { - await this.db.deleteFrom('tag').where('id', 'in', ids).execute(); - this.logger.log(`Deleted ${ids.length} empty tags`); - } - }); + const deletedRows = Number(result.numDeletedRows); + if (deletedRows > 0) { + this.logger.log(`Deleted ${deletedRows} empty tags`); + } } } diff --git a/server/src/repositories/user.repository.ts b/server/src/repositories/user.repository.ts index 44f4a2bb9c..20b41c80f8 100644 --- a/server/src/repositories/user.repository.ts +++ b/server/src/repositories/user.repository.ts @@ -286,6 +286,16 @@ export class UserRepository { .execute(); } + @GenerateSql() + async getCount(): Promise { + const result = await this.db + .selectFrom('user') + .select((eb) => eb.fn.countAll().as('count')) + .where('user.deletedAt', 'is', null) + .executeTakeFirstOrThrow(); + return Number(result.count); + } + @GenerateSql({ params: [DummyValue.UUID, DummyValue.NUMBER] }) async updateUsage(id: string, delta: number): Promise { await this.db diff --git a/server/src/repositories/websocket.repository.ts b/server/src/repositories/websocket.repository.ts new file mode 100644 index 0000000000..030659772d --- /dev/null +++ b/server/src/repositories/websocket.repository.ts @@ -0,0 +1,118 @@ +import { Injectable } from '@nestjs/common'; +import { + OnGatewayConnection, + OnGatewayDisconnect, + OnGatewayInit, + WebSocketGateway, + WebSocketServer, +} from '@nestjs/websockets'; +import { Server, Socket } from 'socket.io'; +import { AssetResponseDto } from 'src/dtos/asset-response.dto'; +import { AuthDto } from 'src/dtos/auth.dto'; +import { NotificationDto } from 'src/dtos/notification.dto'; +import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto'; +import { SyncAssetExifV1, SyncAssetV1 } from 'src/dtos/sync.dto'; +import { ArgsOf, EventRepository } from 'src/repositories/event.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; +import { handlePromiseError } from 'src/utils/misc'; + +export const serverEvents = ['ConfigUpdate'] as const; +export type ServerEvents = (typeof serverEvents)[number]; + +export interface ClientEventMap { + on_upload_success: [AssetResponseDto]; + on_user_delete: [string]; + on_asset_delete: [string]; + on_asset_trash: [string[]]; + on_asset_update: [AssetResponseDto]; + on_asset_hidden: [string]; + on_asset_restore: [string[]]; + on_asset_stack_update: string[]; + on_person_thumbnail: [string]; + on_server_version: [ServerVersionResponseDto]; + on_config_update: []; + on_new_release: [ReleaseNotification]; + on_notification: [NotificationDto]; + on_session_delete: [string]; + + AssetUploadReadyV1: [{ asset: SyncAssetV1; exif: SyncAssetExifV1 }]; +} + +export type AuthFn = (client: Socket) => Promise; + +@WebSocketGateway({ + cors: true, + path: '/api/socket.io', + transports: ['websocket'], +}) +@Injectable() +export class WebsocketRepository implements OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit { + private authFn?: AuthFn; + + @WebSocketServer() + private server?: Server; + + constructor( + private eventRepository: EventRepository, + private logger: LoggingRepository, + ) { + this.logger.setContext(WebsocketRepository.name); + } + + afterInit(server: Server) { + this.logger.log('Initialized websocket server'); + + for (const event of serverEvents) { + server.on(event, (...args: ArgsOf) => { + this.logger.debug(`Server event: ${event} (receive)`); + handlePromiseError(this.eventRepository.onEvent({ name: event, args, server: true }), this.logger); + }); + } + } + + async handleConnection(client: Socket) { + try { + this.logger.log(`Websocket Connect: ${client.id}`); + const auth = await this.authenticate(client); + await client.join(auth.user.id); + if (auth.session) { + await client.join(auth.session.id); + } + await this.eventRepository.emit('WebsocketConnect', { userId: auth.user.id }); + } catch (error: Error | any) { + this.logger.error(`Websocket connection error: ${error}`, error?.stack); + client.emit('error', 'unauthorized'); + client.disconnect(); + } + } + + async handleDisconnect(client: Socket) { + this.logger.log(`Websocket Disconnect: ${client.id}`); + await client.leave(client.nsp.name); + } + + clientSend(event: T, room: string, ...data: ClientEventMap[T]) { + this.server?.to(room).emit(event, ...data); + } + + clientBroadcast(event: T, ...data: ClientEventMap[T]) { + this.server?.emit(event, ...data); + } + + serverSend(event: T, ...args: ArgsOf): void { + this.logger.debug(`Server event: ${event} (send)`); + this.server?.serverSideEmit(event, ...args); + } + + setAuthFn(fn: (client: Socket) => Promise) { + this.authFn = fn; + } + + private async authenticate(client: Socket) { + if (!this.authFn) { + throw new Error('Auth function not set'); + } + + return this.authFn(client); + } +} diff --git a/server/src/repositories/workflow.repository.ts b/server/src/repositories/workflow.repository.ts new file mode 100644 index 0000000000..4ae657cfbf --- /dev/null +++ b/server/src/repositories/workflow.repository.ts @@ -0,0 +1,139 @@ +import { Injectable } from '@nestjs/common'; +import { Insertable, Kysely, Updateable } from 'kysely'; +import { InjectKysely } from 'nestjs-kysely'; +import { DummyValue, GenerateSql } from 'src/decorators'; +import { PluginTriggerType } from 'src/enum'; +import { DB } from 'src/schema'; +import { WorkflowActionTable, WorkflowFilterTable, WorkflowTable } from 'src/schema/tables/workflow.table'; + +@Injectable() +export class WorkflowRepository { + constructor(@InjectKysely() private db: Kysely) {} + + @GenerateSql({ params: [DummyValue.UUID] }) + getWorkflow(id: string) { + return this.db.selectFrom('workflow').selectAll().where('id', '=', id).executeTakeFirst(); + } + + @GenerateSql({ params: [DummyValue.UUID] }) + getWorkflowsByOwner(ownerId: string) { + return this.db.selectFrom('workflow').selectAll().where('ownerId', '=', ownerId).orderBy('name').execute(); + } + + @GenerateSql({ params: [PluginTriggerType.AssetCreate] }) + getWorkflowsByTrigger(type: PluginTriggerType) { + return this.db + .selectFrom('workflow') + .selectAll() + .where('triggerType', '=', type) + .where('enabled', '=', true) + .execute(); + } + + @GenerateSql({ params: [DummyValue.UUID, PluginTriggerType.AssetCreate] }) + getWorkflowByOwnerAndTrigger(ownerId: string, type: PluginTriggerType) { + return this.db + .selectFrom('workflow') + .selectAll() + .where('ownerId', '=', ownerId) + .where('triggerType', '=', type) + .where('enabled', '=', true) + .execute(); + } + + async createWorkflow( + workflow: Insertable, + filters: Insertable[], + actions: Insertable[], + ) { + return await this.db.transaction().execute(async (tx) => { + const createdWorkflow = await tx.insertInto('workflow').values(workflow).returningAll().executeTakeFirstOrThrow(); + + if (filters.length > 0) { + const newFilters = filters.map((filter) => ({ + ...filter, + workflowId: createdWorkflow.id, + })); + + await tx.insertInto('workflow_filter').values(newFilters).execute(); + } + + if (actions.length > 0) { + const newActions = actions.map((action) => ({ + ...action, + workflowId: createdWorkflow.id, + })); + await tx.insertInto('workflow_action').values(newActions).execute(); + } + + return createdWorkflow; + }); + } + + async updateWorkflow( + id: string, + workflow: Updateable, + filters: Insertable[] | undefined, + actions: Insertable[] | undefined, + ) { + return await this.db.transaction().execute(async (trx) => { + if (Object.keys(workflow).length > 0) { + await trx.updateTable('workflow').set(workflow).where('id', '=', id).execute(); + } + + if (filters !== undefined) { + await trx.deleteFrom('workflow_filter').where('workflowId', '=', id).execute(); + if (filters.length > 0) { + const filtersWithWorkflowId = filters.map((filter) => ({ + ...filter, + workflowId: id, + })); + await trx.insertInto('workflow_filter').values(filtersWithWorkflowId).execute(); + } + } + + if (actions !== undefined) { + await trx.deleteFrom('workflow_action').where('workflowId', '=', id).execute(); + if (actions.length > 0) { + const actionsWithWorkflowId = actions.map((action) => ({ + ...action, + workflowId: id, + })); + await trx.insertInto('workflow_action').values(actionsWithWorkflowId).execute(); + } + } + + return await trx.selectFrom('workflow').selectAll().where('id', '=', id).executeTakeFirstOrThrow(); + }); + } + + @GenerateSql({ params: [DummyValue.UUID] }) + async deleteWorkflow(id: string) { + await this.db.deleteFrom('workflow').where('id', '=', id).execute(); + } + + @GenerateSql({ params: [DummyValue.UUID] }) + getFilters(workflowId: string) { + return this.db + .selectFrom('workflow_filter') + .selectAll() + .where('workflowId', '=', workflowId) + .orderBy('order', 'asc') + .execute(); + } + + @GenerateSql({ params: [DummyValue.UUID] }) + async deleteFiltersByWorkflow(workflowId: string) { + await this.db.deleteFrom('workflow_filter').where('workflowId', '=', workflowId).execute(); + } + + @GenerateSql({ params: [DummyValue.UUID] }) + getActions(workflowId: string) { + return this.db + .selectFrom('workflow_action') + .selectAll() + .where('workflowId', '=', workflowId) + .orderBy('order', 'asc') + .execute(); + } +} diff --git a/server/src/schema/functions.ts b/server/src/schema/functions.ts index e255742b5d..385db37cf8 100644 --- a/server/src/schema/functions.ts +++ b/server/src/schema/functions.ts @@ -29,7 +29,7 @@ export const album_user_after_insert = registerFunction({ body: ` BEGIN UPDATE album SET "updatedAt" = clock_timestamp(), "updateId" = immich_uuid_v7(clock_timestamp()) - WHERE "id" IN (SELECT DISTINCT "albumsId" FROM inserted_rows); + WHERE "id" IN (SELECT DISTINCT "albumId" FROM inserted_rows); RETURN NULL; END`, }); @@ -139,8 +139,8 @@ export const album_asset_delete_audit = registerFunction({ body: ` BEGIN INSERT INTO album_asset_audit ("albumId", "assetId") - SELECT "albumsId", "assetsId" FROM OLD - WHERE "albumsId" IN (SELECT "id" FROM album WHERE "id" IN (SELECT "albumsId" FROM OLD)); + SELECT "albumId", "assetId" FROM OLD + WHERE "albumId" IN (SELECT "id" FROM album WHERE "id" IN (SELECT "albumId" FROM OLD)); RETURN NULL; END`, }); @@ -152,12 +152,12 @@ export const album_user_delete_audit = registerFunction({ body: ` BEGIN INSERT INTO album_audit ("albumId", "userId") - SELECT "albumsId", "usersId" + SELECT "albumId", "userId" FROM OLD; IF pg_trigger_depth() = 1 THEN INSERT INTO album_user_audit ("albumId", "userId") - SELECT "albumsId", "usersId" + SELECT "albumId", "userId" FROM OLD; END IF; @@ -185,7 +185,7 @@ export const memory_asset_delete_audit = registerFunction({ body: ` BEGIN INSERT INTO memory_asset_audit ("memoryId", "assetId") - SELECT "memoriesId", "assetsId" FROM OLD + SELECT "memoriesId", "assetId" FROM OLD WHERE "memoriesId" IN (SELECT "id" FROM memory WHERE "id" IN (SELECT "memoriesId" FROM OLD)); RETURN NULL; END`, diff --git a/server/src/schema/index.ts b/server/src/schema/index.ts index c8474cda03..9e206826e6 100644 --- a/server/src/schema/index.ts +++ b/server/src/schema/index.ts @@ -35,6 +35,7 @@ import { AssetFileTable } from 'src/schema/tables/asset-file.table'; import { AssetJobStatusTable } from 'src/schema/tables/asset-job-status.table'; import { AssetMetadataAuditTable } from 'src/schema/tables/asset-metadata-audit.table'; import { AssetMetadataTable } from 'src/schema/tables/asset-metadata.table'; +import { AssetOcrTable } from 'src/schema/tables/asset-ocr.table'; import { AssetTable } from 'src/schema/tables/asset.table'; import { AuditTable } from 'src/schema/tables/audit.table'; import { FaceSearchTable } from 'src/schema/tables/face-search.table'; @@ -47,10 +48,12 @@ import { MemoryTable } from 'src/schema/tables/memory.table'; import { MoveTable } from 'src/schema/tables/move.table'; import { NaturalEarthCountriesTable } from 'src/schema/tables/natural-earth-countries.table'; import { NotificationTable } from 'src/schema/tables/notification.table'; +import { OcrSearchTable } from 'src/schema/tables/ocr-search.table'; import { PartnerAuditTable } from 'src/schema/tables/partner-audit.table'; import { PartnerTable } from 'src/schema/tables/partner.table'; import { PersonAuditTable } from 'src/schema/tables/person-audit.table'; import { PersonTable } from 'src/schema/tables/person.table'; +import { PluginActionTable, PluginFilterTable, PluginTable } from 'src/schema/tables/plugin.table'; import { SessionTable } from 'src/schema/tables/session.table'; import { SharedLinkAssetTable } from 'src/schema/tables/shared-link-asset.table'; import { SharedLinkTable } from 'src/schema/tables/shared-link.table'; @@ -67,6 +70,7 @@ import { UserMetadataAuditTable } from 'src/schema/tables/user-metadata-audit.ta import { UserMetadataTable } from 'src/schema/tables/user-metadata.table'; import { UserTable } from 'src/schema/tables/user.table'; import { VersionHistoryTable } from 'src/schema/tables/version-history.table'; +import { WorkflowActionTable, WorkflowFilterTable, WorkflowTable } from 'src/schema/tables/workflow.table'; import { Database, Extensions, Generated, Int8 } from 'src/sql-tools'; @Extensions(['uuid-ossp', 'unaccent', 'cube', 'earthdistance', 'pg_trgm', 'plpgsql']) @@ -87,6 +91,7 @@ export class ImmichDatabase { AssetMetadataTable, AssetMetadataAuditTable, AssetJobStatusTable, + AssetOcrTable, AssetTable, AssetFileTable, AuditTable, @@ -101,6 +106,7 @@ export class ImmichDatabase { MoveTable, NaturalEarthCountriesTable, NotificationTable, + OcrSearchTable, PartnerAuditTable, PartnerTable, PersonTable, @@ -121,6 +127,12 @@ export class ImmichDatabase { UserMetadataAuditTable, UserTable, VersionHistoryTable, + PluginTable, + PluginFilterTable, + PluginActionTable, + WorkflowTable, + WorkflowFilterTable, + WorkflowActionTable, ]; functions = [ @@ -174,6 +186,8 @@ export interface DB { asset_metadata: AssetMetadataTable; asset_metadata_audit: AssetMetadataAuditTable; asset_job_status: AssetJobStatusTable; + asset_ocr: AssetOcrTable; + ocr_search: OcrSearchTable; audit: AuditTable; @@ -225,4 +239,12 @@ export interface DB { user_metadata_audit: UserMetadataAuditTable; version_history: VersionHistoryTable; + + plugin: PluginTable; + plugin_filter: PluginFilterTable; + plugin_action: PluginActionTable; + + workflow: WorkflowTable; + workflow_filter: WorkflowFilterTable; + workflow_action: WorkflowActionTable; } diff --git a/server/src/schema/migrations/1744910873969-InitialMigration.ts b/server/src/schema/migrations/1744910873969-InitialMigration.ts index 53a55d860e..b703a47536 100644 --- a/server/src/schema/migrations/1744910873969-InitialMigration.ts +++ b/server/src/schema/migrations/1744910873969-InitialMigration.ts @@ -16,7 +16,9 @@ export async function up(db: Kysely): Promise { rows: [lastMigration], } = await lastMigrationSql.execute(db); if (lastMigration?.name !== 'AddMissingIndex1744910873956') { - throw new Error('Invalid upgrade path. For more information, see https://immich.app/errors#typeorm-upgrade'); + throw new Error( + 'Invalid upgrade path. For more information, see https://docs.immich.app/errors/#typeorm-upgrade', + ); } logger.log('Database has up to date TypeORM migrations, skipping initial Kysely migration'); return; diff --git a/server/src/schema/migrations/1758705774125-CreateAssetOCRTable.ts b/server/src/schema/migrations/1758705774125-CreateAssetOCRTable.ts new file mode 100644 index 0000000000..611aac8398 --- /dev/null +++ b/server/src/schema/migrations/1758705774125-CreateAssetOCRTable.ts @@ -0,0 +1,16 @@ +import { Kysely, sql } from 'kysely'; + +export async function up(db: Kysely): Promise { + await sql`CREATE TABLE "asset_ocr" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "assetId" uuid NOT NULL, "x1" real NOT NULL, "y1" real NOT NULL, "x2" real NOT NULL, "y2" real NOT NULL, "x3" real NOT NULL, "y3" real NOT NULL, "x4" real NOT NULL, "y4" real NOT NULL, "boxScore" real NOT NULL, "textScore" real NOT NULL, "text" text NOT NULL);`.execute( + db, + ); + await sql`ALTER TABLE "asset_ocr" ADD CONSTRAINT "asset_ocr_pkey" PRIMARY KEY ("id");`.execute(db); + await sql`ALTER TABLE "asset_ocr" ADD CONSTRAINT "asset_ocr_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "asset" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( + db, + ); + await sql`CREATE INDEX "asset_ocr_assetId_idx" ON "asset_ocr" ("assetId")`.execute(db); +} + +export async function down(db: Kysely): Promise { + await sql`DROP TABLE "asset_ocr";`.execute(db); +} diff --git a/server/src/schema/migrations/1758705789125-CreateOCRSearchTable.ts b/server/src/schema/migrations/1758705789125-CreateOCRSearchTable.ts new file mode 100644 index 0000000000..1b3eefd57d --- /dev/null +++ b/server/src/schema/migrations/1758705789125-CreateOCRSearchTable.ts @@ -0,0 +1,20 @@ +import { Kysely, sql } from 'kysely'; + +export async function up(db: Kysely): Promise { + await sql`CREATE TABLE "ocr_search" ("assetId" uuid NOT NULL, "text" text NOT NULL);`.execute(db); + await sql`ALTER TABLE "ocr_search" ADD CONSTRAINT "ocr_search_pkey" PRIMARY KEY ("assetId");`.execute(db); + await sql`ALTER TABLE "ocr_search" ADD CONSTRAINT "ocr_search_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "asset" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( + db, + ); + await sql`CREATE INDEX "idx_ocr_search_text" ON "ocr_search" USING gin (f_unaccent("text") gin_trgm_ops);`.execute( + db, + ); + await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_idx_ocr_search_text', '{"type":"index","name":"idx_ocr_search_text","sql":"CREATE INDEX \\"idx_ocr_search_text\\" ON \\"ocr_search\\" USING gin (f_unaccent(\\"text\\") gin_trgm_ops);"}'::jsonb);`.execute( + db, + ); +} + +export async function down(db: Kysely): Promise { + await sql`DROP TABLE "ocr_search";`.execute(db); + await sql`DELETE FROM "migration_overrides" WHERE "name" = 'index_idx_ocr_search_text';`.execute(db); +} diff --git a/server/src/schema/migrations/1758705804128-UpsertOcrAssetJobStatus.ts b/server/src/schema/migrations/1758705804128-UpsertOcrAssetJobStatus.ts new file mode 100644 index 0000000000..c7c3ba4449 --- /dev/null +++ b/server/src/schema/migrations/1758705804128-UpsertOcrAssetJobStatus.ts @@ -0,0 +1,9 @@ +import { Kysely, sql } from 'kysely'; + +export async function up(db: Kysely): Promise { + await sql`ALTER TABLE "asset_job_status" ADD "ocrAt" timestamp with time zone;`.execute(db); +} + +export async function down(db: Kysely): Promise { + await sql`ALTER TABLE "asset_job_status" DROP COLUMN "ocrAt";`.execute(db); +} diff --git a/server/src/schema/migrations/1761078763279-AddAppVersionColumnToSession.ts b/server/src/schema/migrations/1761078763279-AddAppVersionColumnToSession.ts new file mode 100644 index 0000000000..8175788517 --- /dev/null +++ b/server/src/schema/migrations/1761078763279-AddAppVersionColumnToSession.ts @@ -0,0 +1,9 @@ +import { Kysely, sql } from 'kysely'; + +export async function up(db: Kysely): Promise { + await sql`ALTER TABLE "session" ADD "appVersion" character varying;`.execute(db); +} + +export async function down(db: Kysely): Promise { + await sql`ALTER TABLE "session" DROP COLUMN "appVersion";`.execute(db); +} diff --git a/server/src/schema/migrations/1761755618862-FixColumnNames.ts b/server/src/schema/migrations/1761755618862-FixColumnNames.ts new file mode 100644 index 0000000000..25131a1640 --- /dev/null +++ b/server/src/schema/migrations/1761755618862-FixColumnNames.ts @@ -0,0 +1,99 @@ +import { Kysely, sql } from 'kysely'; + +export async function up(db: Kysely): Promise { + // rename columns + await sql`ALTER TABLE "album_asset" RENAME COLUMN "albumsId" TO "albumId";`.execute(db); + await sql`ALTER TABLE "album_asset" RENAME COLUMN "assetsId" TO "assetId";`.execute(db); + await sql`ALTER TABLE "album_user" RENAME COLUMN "albumsId" TO "albumId";`.execute(db); + await sql`ALTER TABLE "album_user" RENAME COLUMN "usersId" TO "userId";`.execute(db); + await sql`ALTER TABLE "memory_asset" RENAME COLUMN "assetsId" TO "assetId";`.execute(db); + await sql`ALTER TABLE "shared_link_asset" RENAME COLUMN "assetsId" TO "assetId";`.execute(db); + await sql`ALTER TABLE "shared_link_asset" RENAME COLUMN "sharedLinksId" TO "sharedLinkId";`.execute(db); + await sql`ALTER TABLE "tag_asset" RENAME COLUMN "assetsId" TO "assetId";`.execute(db); + await sql`ALTER TABLE "tag_asset" RENAME COLUMN "tagsId" TO "tagId";`.execute(db); + + // rename constraints + await sql`ALTER TABLE "album_asset" RENAME CONSTRAINT "album_asset_albumsId_fkey" TO "album_asset_albumId_fkey";`.execute(db); + await sql`ALTER TABLE "album_asset" RENAME CONSTRAINT "album_asset_assetsId_fkey" TO "album_asset_assetId_fkey";`.execute(db); + await sql`ALTER TABLE "album_user" RENAME CONSTRAINT "album_user_albumsId_fkey" TO "album_user_albumId_fkey";`.execute(db); + await sql`ALTER TABLE "album_user" RENAME CONSTRAINT "album_user_usersId_fkey" TO "album_user_userId_fkey";`.execute(db); + await sql`ALTER TABLE "memory_asset" RENAME CONSTRAINT "memory_asset_assetsId_fkey" TO "memory_asset_assetId_fkey";`.execute(db); + await sql`ALTER TABLE "shared_link_asset" RENAME CONSTRAINT "shared_link_asset_assetsId_fkey" TO "shared_link_asset_assetId_fkey";`.execute(db); + await sql`ALTER TABLE "shared_link_asset" RENAME CONSTRAINT "shared_link_asset_sharedLinksId_fkey" TO "shared_link_asset_sharedLinkId_fkey";`.execute(db); + await sql`ALTER TABLE "tag_asset" RENAME CONSTRAINT "tag_asset_assetsId_fkey" TO "tag_asset_assetId_fkey";`.execute(db); + await sql`ALTER TABLE "tag_asset" RENAME CONSTRAINT "tag_asset_tagsId_fkey" TO "tag_asset_tagId_fkey";`.execute(db); + + // rename indexes + await sql`ALTER INDEX "album_asset_albumsId_idx" RENAME TO "album_asset_albumId_idx";`.execute(db); + await sql`ALTER INDEX "album_asset_assetsId_idx" RENAME TO "album_asset_assetId_idx";`.execute(db); + await sql`ALTER INDEX "album_user_usersId_idx" RENAME TO "album_user_userId_idx";`.execute(db); + await sql`ALTER INDEX "album_user_albumsId_idx" RENAME TO "album_user_albumId_idx";`.execute(db); + await sql`ALTER INDEX "memory_asset_assetsId_idx" RENAME TO "memory_asset_assetId_idx";`.execute(db); + await sql`ALTER INDEX "shared_link_asset_sharedLinksId_idx" RENAME TO "shared_link_asset_sharedLinkId_idx";`.execute(db); + await sql`ALTER INDEX "shared_link_asset_assetsId_idx" RENAME TO "shared_link_asset_assetId_idx";`.execute(db); + await sql`ALTER INDEX "tag_asset_assetsId_idx" RENAME TO "tag_asset_assetId_idx";`.execute(db); + await sql`ALTER INDEX "tag_asset_tagsId_idx" RENAME TO "tag_asset_tagId_idx";`.execute(db); + await sql`ALTER INDEX "tag_asset_assetsId_tagsId_idx" RENAME TO "tag_asset_assetId_tagId_idx";`.execute(db); + + // update triggers and functions + await sql`CREATE OR REPLACE FUNCTION album_user_after_insert() + RETURNS TRIGGER + LANGUAGE PLPGSQL + AS $$ + BEGIN + UPDATE album SET "updatedAt" = clock_timestamp(), "updateId" = immich_uuid_v7(clock_timestamp()) + WHERE "id" IN (SELECT DISTINCT "albumId" FROM inserted_rows); + RETURN NULL; + END + $$;`.execute(db); + await sql`CREATE OR REPLACE FUNCTION album_asset_delete_audit() + RETURNS TRIGGER + LANGUAGE PLPGSQL + AS $$ + BEGIN + INSERT INTO album_asset_audit ("albumId", "assetId") + SELECT "albumId", "assetId" FROM OLD + WHERE "albumId" IN (SELECT "id" FROM album WHERE "id" IN (SELECT "albumId" FROM OLD)); + RETURN NULL; + END + $$;`.execute(db); + await sql`CREATE OR REPLACE FUNCTION album_user_delete_audit() + RETURNS TRIGGER + LANGUAGE PLPGSQL + AS $$ + BEGIN + INSERT INTO album_audit ("albumId", "userId") + SELECT "albumId", "userId" + FROM OLD; + + IF pg_trigger_depth() = 1 THEN + INSERT INTO album_user_audit ("albumId", "userId") + SELECT "albumId", "userId" + FROM OLD; + END IF; + + RETURN NULL; + END + $$;`.execute(db); + await sql`CREATE OR REPLACE FUNCTION memory_asset_delete_audit() + RETURNS TRIGGER + LANGUAGE PLPGSQL + AS $$ + BEGIN + INSERT INTO memory_asset_audit ("memoryId", "assetId") + SELECT "memoriesId", "assetId" FROM OLD + WHERE "memoriesId" IN (SELECT "id" FROM memory WHERE "id" IN (SELECT "memoriesId" FROM OLD)); + RETURN NULL; + END + $$;`.execute(db); + + // update overrides + await sql`UPDATE "migration_overrides" SET "value" = '{"type":"function","name":"album_user_after_insert","sql":"CREATE OR REPLACE FUNCTION album_user_after_insert()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n UPDATE album SET \\"updatedAt\\" = clock_timestamp(), \\"updateId\\" = immich_uuid_v7(clock_timestamp())\\n WHERE \\"id\\" IN (SELECT DISTINCT \\"albumId\\" FROM inserted_rows);\\n RETURN NULL;\\n END\\n $$;"}'::jsonb WHERE "name" = 'function_album_user_after_insert';`.execute(db); + await sql`UPDATE "migration_overrides" SET "value" = '{"type":"function","name":"album_asset_delete_audit","sql":"CREATE OR REPLACE FUNCTION album_asset_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO album_asset_audit (\\"albumId\\", \\"assetId\\")\\n SELECT \\"albumId\\", \\"assetId\\" FROM OLD\\n WHERE \\"albumId\\" IN (SELECT \\"id\\" FROM album WHERE \\"id\\" IN (SELECT \\"albumId\\" FROM OLD));\\n RETURN NULL;\\n END\\n $$;"}'::jsonb WHERE "name" = 'function_album_asset_delete_audit';`.execute(db); + await sql`UPDATE "migration_overrides" SET "value" = '{"type":"function","name":"album_user_delete_audit","sql":"CREATE OR REPLACE FUNCTION album_user_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO album_audit (\\"albumId\\", \\"userId\\")\\n SELECT \\"albumId\\", \\"userId\\"\\n FROM OLD;\\n\\n IF pg_trigger_depth() = 1 THEN\\n INSERT INTO album_user_audit (\\"albumId\\", \\"userId\\")\\n SELECT \\"albumId\\", \\"userId\\"\\n FROM OLD;\\n END IF;\\n\\n RETURN NULL;\\n END\\n $$;"}'::jsonb WHERE "name" = 'function_album_user_delete_audit';`.execute(db); + await sql`UPDATE "migration_overrides" SET "value" = '{"type":"function","name":"memory_asset_delete_audit","sql":"CREATE OR REPLACE FUNCTION memory_asset_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO memory_asset_audit (\\"memoryId\\", \\"assetId\\")\\n SELECT \\"memoriesId\\", \\"assetId\\" FROM OLD\\n WHERE \\"memoriesId\\" IN (SELECT \\"id\\" FROM memory WHERE \\"id\\" IN (SELECT \\"memoriesId\\" FROM OLD));\\n RETURN NULL;\\n END\\n $$;"}'::jsonb WHERE "name" = 'function_memory_asset_delete_audit';`.execute(db); +} + +export function down() { + // not implemented +} diff --git a/server/src/schema/migrations/1762297277677-AddPluginAndWorkflowTables.ts b/server/src/schema/migrations/1762297277677-AddPluginAndWorkflowTables.ts new file mode 100644 index 0000000000..6dacc1056b --- /dev/null +++ b/server/src/schema/migrations/1762297277677-AddPluginAndWorkflowTables.ts @@ -0,0 +1,113 @@ +import { Kysely, sql } from 'kysely'; + +export async function up(db: Kysely): Promise { + await sql`CREATE TABLE "plugin" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "name" character varying NOT NULL, + "title" character varying NOT NULL, + "description" character varying NOT NULL, + "author" character varying NOT NULL, + "version" character varying NOT NULL, + "wasmPath" character varying NOT NULL, + "createdAt" timestamp with time zone NOT NULL DEFAULT now(), + "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), + CONSTRAINT "plugin_name_uq" UNIQUE ("name"), + CONSTRAINT "plugin_pkey" PRIMARY KEY ("id") +);`.execute(db); + await sql`CREATE INDEX "plugin_name_idx" ON "plugin" ("name");`.execute(db); + await sql`CREATE TABLE "plugin_filter" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "pluginId" uuid NOT NULL, + "methodName" character varying NOT NULL, + "title" character varying NOT NULL, + "description" character varying NOT NULL, + "supportedContexts" character varying[] NOT NULL, + "schema" jsonb, + CONSTRAINT "plugin_filter_pluginId_fkey" FOREIGN KEY ("pluginId") REFERENCES "plugin" ("id") ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT "plugin_filter_methodName_uq" UNIQUE ("methodName"), + CONSTRAINT "plugin_filter_pkey" PRIMARY KEY ("id") +);`.execute(db); + await sql`CREATE INDEX "plugin_filter_supportedContexts_idx" ON "plugin_filter" USING gin ("supportedContexts");`.execute( + db, + ); + await sql`CREATE INDEX "plugin_filter_pluginId_idx" ON "plugin_filter" ("pluginId");`.execute(db); + await sql`CREATE INDEX "plugin_filter_methodName_idx" ON "plugin_filter" ("methodName");`.execute(db); + await sql`CREATE TABLE "plugin_action" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "pluginId" uuid NOT NULL, + "methodName" character varying NOT NULL, + "title" character varying NOT NULL, + "description" character varying NOT NULL, + "supportedContexts" character varying[] NOT NULL, + "schema" jsonb, + CONSTRAINT "plugin_action_pluginId_fkey" FOREIGN KEY ("pluginId") REFERENCES "plugin" ("id") ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT "plugin_action_methodName_uq" UNIQUE ("methodName"), + CONSTRAINT "plugin_action_pkey" PRIMARY KEY ("id") +);`.execute(db); + await sql`CREATE INDEX "plugin_action_supportedContexts_idx" ON "plugin_action" USING gin ("supportedContexts");`.execute( + db, + ); + await sql`CREATE INDEX "plugin_action_pluginId_idx" ON "plugin_action" ("pluginId");`.execute(db); + await sql`CREATE INDEX "plugin_action_methodName_idx" ON "plugin_action" ("methodName");`.execute(db); + await sql`CREATE TABLE "workflow" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "ownerId" uuid NOT NULL, + "triggerType" character varying NOT NULL, + "name" character varying, + "description" character varying NOT NULL, + "createdAt" timestamp with time zone NOT NULL DEFAULT now(), + "enabled" boolean NOT NULL DEFAULT true, + CONSTRAINT "workflow_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "user" ("id") ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT "workflow_pkey" PRIMARY KEY ("id") +);`.execute(db); + await sql`CREATE INDEX "workflow_ownerId_idx" ON "workflow" ("ownerId");`.execute(db); + await sql`CREATE TABLE "workflow_filter" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "workflowId" uuid NOT NULL, + "filterId" uuid NOT NULL, + "filterConfig" jsonb, + "order" integer NOT NULL, + CONSTRAINT "workflow_filter_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow" ("id") ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT "workflow_filter_filterId_fkey" FOREIGN KEY ("filterId") REFERENCES "plugin_filter" ("id") ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT "workflow_filter_pkey" PRIMARY KEY ("id") +);`.execute(db); + await sql`CREATE INDEX "workflow_filter_filterId_idx" ON "workflow_filter" ("filterId");`.execute(db); + await sql`CREATE INDEX "workflow_filter_workflowId_order_idx" ON "workflow_filter" ("workflowId", "order");`.execute( + db, + ); + await sql`CREATE INDEX "workflow_filter_workflowId_idx" ON "workflow_filter" ("workflowId");`.execute(db); + await sql`CREATE TABLE "workflow_action" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "workflowId" uuid NOT NULL, + "actionId" uuid NOT NULL, + "actionConfig" jsonb, + "order" integer NOT NULL, + CONSTRAINT "workflow_action_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow" ("id") ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT "workflow_action_actionId_fkey" FOREIGN KEY ("actionId") REFERENCES "plugin_action" ("id") ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT "workflow_action_pkey" PRIMARY KEY ("id") +);`.execute(db); + await sql`CREATE INDEX "workflow_action_actionId_idx" ON "workflow_action" ("actionId");`.execute(db); + await sql`CREATE INDEX "workflow_action_workflowId_order_idx" ON "workflow_action" ("workflowId", "order");`.execute( + db, + ); + await sql`CREATE INDEX "workflow_action_workflowId_idx" ON "workflow_action" ("workflowId");`.execute(db); + await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_plugin_filter_supportedContexts_idx', '{"type":"index","name":"plugin_filter_supportedContexts_idx","sql":"CREATE INDEX \\"plugin_filter_supportedContexts_idx\\" ON \\"plugin_filter\\" (\\"supportedContexts\\") USING gin;"}'::jsonb);`.execute( + db, + ); + await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_plugin_action_supportedContexts_idx', '{"type":"index","name":"plugin_action_supportedContexts_idx","sql":"CREATE INDEX \\"plugin_action_supportedContexts_idx\\" ON \\"plugin_action\\" (\\"supportedContexts\\") USING gin;"}'::jsonb);`.execute( + db, + ); +} + +export async function down(db: Kysely): Promise { + await sql`DROP TABLE "workflow";`.execute(db); + await sql`DROP TABLE "workflow_filter";`.execute(db); + await sql`DROP TABLE "workflow_action";`.execute(db); + + await sql`DROP TABLE "plugin";`.execute(db); + await sql`DROP TABLE "plugin_filter";`.execute(db); + await sql`DROP TABLE "plugin_action";`.execute(db); + + await sql`DELETE FROM "migration_overrides" WHERE "name" = 'index_plugin_filter_supportedContexts_idx';`.execute(db); + await sql`DELETE FROM "migration_overrides" WHERE "name" = 'index_plugin_action_supportedContexts_idx';`.execute(db); +} diff --git a/server/src/schema/tables/activity.table.ts b/server/src/schema/tables/activity.table.ts index 128cf2eabd..dfa7c98e42 100644 --- a/server/src/schema/tables/activity.table.ts +++ b/server/src/schema/tables/activity.table.ts @@ -32,7 +32,7 @@ import { @ForeignKeyConstraint({ columns: ['albumId', 'assetId'], referenceTable: () => AlbumAssetTable, - referenceColumns: ['albumsId', 'assetsId'], + referenceColumns: ['albumId', 'assetId'], onUpdate: 'NO ACTION', onDelete: 'CASCADE', }) diff --git a/server/src/schema/tables/album-asset.table.ts b/server/src/schema/tables/album-asset.table.ts index c34546c3f3..dea271239b 100644 --- a/server/src/schema/tables/album-asset.table.ts +++ b/server/src/schema/tables/album-asset.table.ts @@ -22,10 +22,10 @@ import { }) export class AlbumAssetTable { @ForeignKeyColumn(() => AlbumTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false, primary: true }) - albumsId!: string; + albumId!: string; @ForeignKeyColumn(() => AssetTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false, primary: true }) - assetsId!: string; + assetId!: string; @CreateDateColumn() createdAt!: Generated; diff --git a/server/src/schema/tables/album-user.table.ts b/server/src/schema/tables/album-user.table.ts index 94383218da..761aabc1af 100644 --- a/server/src/schema/tables/album-user.table.ts +++ b/server/src/schema/tables/album-user.table.ts @@ -37,7 +37,7 @@ export class AlbumUserTable { nullable: false, primary: true, }) - albumsId!: string; + albumId!: string; @ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', @@ -45,7 +45,7 @@ export class AlbumUserTable { nullable: false, primary: true, }) - usersId!: string; + userId!: string; @Column({ type: 'character varying', default: AlbumUserRole.Editor }) role!: Generated; diff --git a/server/src/schema/tables/asset-job-status.table.ts b/server/src/schema/tables/asset-job-status.table.ts index 5ae10edbfa..d68dbcb761 100644 --- a/server/src/schema/tables/asset-job-status.table.ts +++ b/server/src/schema/tables/asset-job-status.table.ts @@ -20,4 +20,7 @@ export class AssetJobStatusTable { @Column({ type: 'timestamp with time zone', nullable: true }) thumbnailAt!: Timestamp | null; + + @Column({ type: 'timestamp with time zone', nullable: true }) + ocrAt!: Timestamp | null; } diff --git a/server/src/schema/tables/asset-ocr.table.ts b/server/src/schema/tables/asset-ocr.table.ts new file mode 100644 index 0000000000..6ab159b531 --- /dev/null +++ b/server/src/schema/tables/asset-ocr.table.ts @@ -0,0 +1,45 @@ +import { AssetTable } from 'src/schema/tables/asset.table'; +import { Column, ForeignKeyColumn, Generated, PrimaryGeneratedColumn, Table } from 'src/sql-tools'; + +@Table('asset_ocr') +export class AssetOcrTable { + @PrimaryGeneratedColumn() + id!: Generated; + + @ForeignKeyColumn(() => AssetTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + assetId!: string; + + // box positions are normalized, with values between 0 and 1 + @Column({ type: 'real' }) + x1!: number; + + @Column({ type: 'real' }) + y1!: number; + + @Column({ type: 'real' }) + x2!: number; + + @Column({ type: 'real' }) + y2!: number; + + @Column({ type: 'real' }) + x3!: number; + + @Column({ type: 'real' }) + y3!: number; + + @Column({ type: 'real' }) + x4!: number; + + @Column({ type: 'real' }) + y4!: number; + + @Column({ type: 'real' }) + boxScore!: number; + + @Column({ type: 'real' }) + textScore!: number; + + @Column({ type: 'text' }) + text!: string; +} diff --git a/server/src/schema/tables/memory-asset.table.ts b/server/src/schema/tables/memory-asset.table.ts index f535155233..b162000ca0 100644 --- a/server/src/schema/tables/memory-asset.table.ts +++ b/server/src/schema/tables/memory-asset.table.ts @@ -25,7 +25,7 @@ export class MemoryAssetTable { memoriesId!: string; @ForeignKeyColumn(() => AssetTable, { onUpdate: 'CASCADE', onDelete: 'CASCADE', primary: true }) - assetsId!: string; + assetId!: string; @CreateDateColumn() createdAt!: Generated; diff --git a/server/src/schema/tables/ocr-search.table.ts b/server/src/schema/tables/ocr-search.table.ts new file mode 100644 index 0000000000..3449725adb --- /dev/null +++ b/server/src/schema/tables/ocr-search.table.ts @@ -0,0 +1,20 @@ +import { AssetTable } from 'src/schema/tables/asset.table'; +import { Column, ForeignKeyColumn, Index, Table } from 'src/sql-tools'; + +@Table('ocr_search') +@Index({ + name: 'idx_ocr_search_text', + using: 'gin', + expression: 'f_unaccent("text") gin_trgm_ops', +}) +export class OcrSearchTable { + @ForeignKeyColumn(() => AssetTable, { + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + primary: true, + }) + assetId!: string; + + @Column({ type: 'text' }) + text!: string; +} diff --git a/server/src/schema/tables/plugin.table.ts b/server/src/schema/tables/plugin.table.ts new file mode 100644 index 0000000000..3de7ca63c9 --- /dev/null +++ b/server/src/schema/tables/plugin.table.ts @@ -0,0 +1,95 @@ +import { PluginContext } from 'src/enum'; +import { + Column, + CreateDateColumn, + ForeignKeyColumn, + Generated, + Index, + PrimaryGeneratedColumn, + Table, + Timestamp, + UpdateDateColumn, +} from 'src/sql-tools'; +import type { JSONSchema } from 'src/types/plugin-schema.types'; + +@Table('plugin') +export class PluginTable { + @PrimaryGeneratedColumn('uuid') + id!: Generated; + + @Column({ index: true, unique: true }) + name!: string; + + @Column() + title!: string; + + @Column() + description!: string; + + @Column() + author!: string; + + @Column() + version!: string; + + @Column() + wasmPath!: string; + + @CreateDateColumn() + createdAt!: Generated; + + @UpdateDateColumn() + updatedAt!: Generated; +} + +@Index({ columns: ['supportedContexts'], using: 'gin' }) +@Table('plugin_filter') +export class PluginFilterTable { + @PrimaryGeneratedColumn('uuid') + id!: Generated; + + @ForeignKeyColumn(() => PluginTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + @Column({ index: true }) + pluginId!: string; + + @Column({ index: true, unique: true }) + methodName!: string; + + @Column() + title!: string; + + @Column() + description!: string; + + @Column({ type: 'character varying', array: true }) + supportedContexts!: Generated; + + @Column({ type: 'jsonb', nullable: true }) + schema!: JSONSchema | null; +} + +@Index({ columns: ['supportedContexts'], using: 'gin' }) +@Table('plugin_action') +export class PluginActionTable { + @PrimaryGeneratedColumn('uuid') + id!: Generated; + + @ForeignKeyColumn(() => PluginTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + @Column({ index: true }) + pluginId!: string; + + @Column({ index: true, unique: true }) + methodName!: string; + + @Column() + title!: string; + + @Column() + description!: string; + + @Column({ type: 'character varying', array: true }) + supportedContexts!: Generated; + + @Column({ type: 'jsonb', nullable: true }) + schema!: JSONSchema | null; +} diff --git a/server/src/schema/tables/session.table.ts b/server/src/schema/tables/session.table.ts index 706abdf887..466152d35d 100644 --- a/server/src/schema/tables/session.table.ts +++ b/server/src/schema/tables/session.table.ts @@ -42,6 +42,9 @@ export class SessionTable { @Column({ default: '' }) deviceOS!: Generated; + @Column({ nullable: true }) + appVersion!: string | null; + @UpdateIdColumn({ index: true }) updateId!: Generated; diff --git a/server/src/schema/tables/shared-link-asset.table.ts b/server/src/schema/tables/shared-link-asset.table.ts index 37b652c4ab..37e6a3d9f0 100644 --- a/server/src/schema/tables/shared-link-asset.table.ts +++ b/server/src/schema/tables/shared-link-asset.table.ts @@ -5,8 +5,8 @@ import { ForeignKeyColumn, Table } from 'src/sql-tools'; @Table('shared_link_asset') export class SharedLinkAssetTable { @ForeignKeyColumn(() => AssetTable, { onUpdate: 'CASCADE', onDelete: 'CASCADE', primary: true }) - assetsId!: string; + assetId!: string; @ForeignKeyColumn(() => SharedLinkTable, { onUpdate: 'CASCADE', onDelete: 'CASCADE', primary: true }) - sharedLinksId!: string; + sharedLinkId!: string; } diff --git a/server/src/schema/tables/tag-asset.table.ts b/server/src/schema/tables/tag-asset.table.ts index bc02129217..3ea2361b4f 100644 --- a/server/src/schema/tables/tag-asset.table.ts +++ b/server/src/schema/tables/tag-asset.table.ts @@ -2,12 +2,12 @@ import { AssetTable } from 'src/schema/tables/asset.table'; import { TagTable } from 'src/schema/tables/tag.table'; import { ForeignKeyColumn, Index, Table } from 'src/sql-tools'; -@Index({ columns: ['assetsId', 'tagsId'] }) +@Index({ columns: ['assetId', 'tagId'] }) @Table('tag_asset') export class TagAssetTable { @ForeignKeyColumn(() => AssetTable, { onUpdate: 'CASCADE', onDelete: 'CASCADE', primary: true, index: true }) - assetsId!: string; + assetId!: string; @ForeignKeyColumn(() => TagTable, { onUpdate: 'CASCADE', onDelete: 'CASCADE', primary: true, index: true }) - tagsId!: string; + tagId!: string; } diff --git a/server/src/schema/tables/workflow.table.ts b/server/src/schema/tables/workflow.table.ts new file mode 100644 index 0000000000..8f7c9adb0d --- /dev/null +++ b/server/src/schema/tables/workflow.table.ts @@ -0,0 +1,78 @@ +import { PluginTriggerType } from 'src/enum'; +import { PluginActionTable, PluginFilterTable } from 'src/schema/tables/plugin.table'; +import { UserTable } from 'src/schema/tables/user.table'; +import { + Column, + CreateDateColumn, + ForeignKeyColumn, + Generated, + Index, + PrimaryGeneratedColumn, + Table, + Timestamp, +} from 'src/sql-tools'; +import type { ActionConfig, FilterConfig } from 'src/types/plugin-schema.types'; + +@Table('workflow') +export class WorkflowTable { + @PrimaryGeneratedColumn() + id!: Generated; + + @ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false }) + ownerId!: string; + + @Column() + triggerType!: PluginTriggerType; + + @Column({ nullable: true }) + name!: string | null; + + @Column() + description!: string; + + @CreateDateColumn() + createdAt!: Generated; + + @Column({ type: 'boolean', default: true }) + enabled!: boolean; +} + +@Index({ columns: ['workflowId', 'order'] }) +@Index({ columns: ['filterId'] }) +@Table('workflow_filter') +export class WorkflowFilterTable { + @PrimaryGeneratedColumn('uuid') + id!: Generated; + + @ForeignKeyColumn(() => WorkflowTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + workflowId!: Generated; + + @ForeignKeyColumn(() => PluginFilterTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + filterId!: string; + + @Column({ type: 'jsonb', nullable: true }) + filterConfig!: FilterConfig | null; + + @Column({ type: 'integer' }) + order!: number; +} + +@Index({ columns: ['workflowId', 'order'] }) +@Index({ columns: ['actionId'] }) +@Table('workflow_action') +export class WorkflowActionTable { + @PrimaryGeneratedColumn('uuid') + id!: Generated; + + @ForeignKeyColumn(() => WorkflowTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + workflowId!: Generated; + + @ForeignKeyColumn(() => PluginActionTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + actionId!: string; + + @Column({ type: 'jsonb', nullable: true }) + actionConfig!: ActionConfig | null; + + @Column({ type: 'integer' }) + order!: number; +} diff --git a/server/src/services/album.service.spec.ts b/server/src/services/album.service.spec.ts index e22d486bba..fa8a9c6450 100644 --- a/server/src/services/album.service.spec.ts +++ b/server/src/services/album.service.spec.ts @@ -402,16 +402,16 @@ describe(AlbumService.name, () => { mocks.album.update.mockResolvedValue(albumStub.sharedWithAdmin); mocks.user.get.mockResolvedValue(userStub.user2); mocks.albumUser.create.mockResolvedValue({ - usersId: userStub.user2.id, - albumsId: albumStub.sharedWithAdmin.id, + userId: userStub.user2.id, + albumId: albumStub.sharedWithAdmin.id, role: AlbumUserRole.Editor, }); await sut.addUsers(authStub.user1, albumStub.sharedWithAdmin.id, { albumUsers: [{ userId: authStub.user2.user.id }], }); expect(mocks.albumUser.create).toHaveBeenCalledWith({ - usersId: authStub.user2.user.id, - albumsId: albumStub.sharedWithAdmin.id, + userId: authStub.user2.user.id, + albumId: albumStub.sharedWithAdmin.id, }); expect(mocks.event.emit).toHaveBeenCalledWith('AlbumInvite', { id: albumStub.sharedWithAdmin.id, @@ -439,8 +439,8 @@ describe(AlbumService.name, () => { expect(mocks.albumUser.delete).toHaveBeenCalledTimes(1); expect(mocks.albumUser.delete).toHaveBeenCalledWith({ - albumsId: albumStub.sharedWithUser.id, - usersId: userStub.user1.id, + albumId: albumStub.sharedWithUser.id, + userId: userStub.user1.id, }); expect(mocks.album.getById).toHaveBeenCalledWith(albumStub.sharedWithUser.id, { withAssets: false }); }); @@ -467,8 +467,8 @@ describe(AlbumService.name, () => { expect(mocks.albumUser.delete).toHaveBeenCalledTimes(1); expect(mocks.albumUser.delete).toHaveBeenCalledWith({ - albumsId: albumStub.sharedWithUser.id, - usersId: authStub.user1.user.id, + albumId: albumStub.sharedWithUser.id, + userId: authStub.user1.user.id, }); }); @@ -480,8 +480,8 @@ describe(AlbumService.name, () => { expect(mocks.albumUser.delete).toHaveBeenCalledTimes(1); expect(mocks.albumUser.delete).toHaveBeenCalledWith({ - albumsId: albumStub.sharedWithUser.id, - usersId: authStub.user1.user.id, + albumId: albumStub.sharedWithUser.id, + userId: authStub.user1.user.id, }); }); @@ -515,7 +515,7 @@ describe(AlbumService.name, () => { role: AlbumUserRole.Editor, }); expect(mocks.albumUser.update).toHaveBeenCalledWith( - { albumsId: albumStub.sharedWithAdmin.id, usersId: userStub.admin.id }, + { albumId: albumStub.sharedWithAdmin.id, userId: userStub.admin.id }, { role: AlbumUserRole.Editor }, ); }); @@ -804,12 +804,12 @@ describe(AlbumService.name, () => { albumThumbnailAssetId: 'asset-1', }); expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([ - { albumsId: 'album-123', assetsId: 'asset-1' }, - { albumsId: 'album-123', assetsId: 'asset-2' }, - { albumsId: 'album-123', assetsId: 'asset-3' }, - { albumsId: 'album-321', assetsId: 'asset-1' }, - { albumsId: 'album-321', assetsId: 'asset-2' }, - { albumsId: 'album-321', assetsId: 'asset-3' }, + { albumId: 'album-123', assetId: 'asset-1' }, + { albumId: 'album-123', assetId: 'asset-2' }, + { albumId: 'album-123', assetId: 'asset-3' }, + { albumId: 'album-321', assetId: 'asset-1' }, + { albumId: 'album-321', assetId: 'asset-2' }, + { albumId: 'album-321', assetId: 'asset-3' }, ]); }); @@ -840,12 +840,12 @@ describe(AlbumService.name, () => { albumThumbnailAssetId: 'asset-id', }); expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([ - { albumsId: 'album-123', assetsId: 'asset-1' }, - { albumsId: 'album-123', assetsId: 'asset-2' }, - { albumsId: 'album-123', assetsId: 'asset-3' }, - { albumsId: 'album-321', assetsId: 'asset-1' }, - { albumsId: 'album-321', assetsId: 'asset-2' }, - { albumsId: 'album-321', assetsId: 'asset-3' }, + { albumId: 'album-123', assetId: 'asset-1' }, + { albumId: 'album-123', assetId: 'asset-2' }, + { albumId: 'album-123', assetId: 'asset-3' }, + { albumId: 'album-321', assetId: 'asset-1' }, + { albumId: 'album-321', assetId: 'asset-2' }, + { albumId: 'album-321', assetId: 'asset-3' }, ]); }); @@ -876,12 +876,12 @@ describe(AlbumService.name, () => { albumThumbnailAssetId: 'asset-1', }); expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([ - { albumsId: 'album-123', assetsId: 'asset-1' }, - { albumsId: 'album-123', assetsId: 'asset-2' }, - { albumsId: 'album-123', assetsId: 'asset-3' }, - { albumsId: 'album-321', assetsId: 'asset-1' }, - { albumsId: 'album-321', assetsId: 'asset-2' }, - { albumsId: 'album-321', assetsId: 'asset-3' }, + { albumId: 'album-123', assetId: 'asset-1' }, + { albumId: 'album-123', assetId: 'asset-2' }, + { albumId: 'album-123', assetId: 'asset-3' }, + { albumId: 'album-321', assetId: 'asset-1' }, + { albumId: 'album-321', assetId: 'asset-2' }, + { albumId: 'album-321', assetId: 'asset-3' }, ]); expect(mocks.event.emit).toHaveBeenCalledWith('AlbumUpdate', { id: 'album-123', @@ -936,9 +936,9 @@ describe(AlbumService.name, () => { albumThumbnailAssetId: 'asset-1', }); expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([ - { albumsId: 'album-123', assetsId: 'asset-1' }, - { albumsId: 'album-123', assetsId: 'asset-2' }, - { albumsId: 'album-123', assetsId: 'asset-3' }, + { albumId: 'album-123', assetId: 'asset-1' }, + { albumId: 'album-123', assetId: 'asset-2' }, + { albumId: 'album-123', assetId: 'asset-3' }, ]); expect(mocks.event.emit).toHaveBeenCalledWith('AlbumUpdate', { id: 'album-123', @@ -977,12 +977,12 @@ describe(AlbumService.name, () => { albumThumbnailAssetId: 'asset-1', }); expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([ - { albumsId: 'album-123', assetsId: 'asset-1' }, - { albumsId: 'album-123', assetsId: 'asset-2' }, - { albumsId: 'album-123', assetsId: 'asset-3' }, - { albumsId: 'album-321', assetsId: 'asset-1' }, - { albumsId: 'album-321', assetsId: 'asset-2' }, - { albumsId: 'album-321', assetsId: 'asset-3' }, + { albumId: 'album-123', assetId: 'asset-1' }, + { albumId: 'album-123', assetId: 'asset-2' }, + { albumId: 'album-123', assetId: 'asset-3' }, + { albumId: 'album-321', assetId: 'asset-1' }, + { albumId: 'album-321', assetId: 'asset-2' }, + { albumId: 'album-321', assetId: 'asset-3' }, ]); expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith( authStub.admin.user.id, @@ -1014,9 +1014,9 @@ describe(AlbumService.name, () => { albumThumbnailAssetId: 'asset-1', }); expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([ - { albumsId: 'album-321', assetsId: 'asset-1' }, - { albumsId: 'album-321', assetsId: 'asset-2' }, - { albumsId: 'album-321', assetsId: 'asset-3' }, + { albumId: 'album-321', assetId: 'asset-1' }, + { albumId: 'album-321', assetId: 'asset-2' }, + { albumId: 'album-321', assetId: 'asset-3' }, ]); }); diff --git a/server/src/services/album.service.ts b/server/src/services/album.service.ts index d7b857d666..18747dbc3a 100644 --- a/server/src/services/album.service.ts +++ b/server/src/services/album.service.ts @@ -79,12 +79,17 @@ export class AlbumService extends BaseService { const album = await this.findOrFail(id, { withAssets }); const [albumMetadataForIds] = await this.albumRepository.getMetadataForIds([album.id]); + const hasSharedUsers = album.albumUsers && album.albumUsers.length > 0; + const hasSharedLink = album.sharedLinks && album.sharedLinks.length > 0; + const isShared = hasSharedUsers || hasSharedLink; + return { ...mapAlbum(album, withAssets, auth), startDate: albumMetadataForIds?.startDate ?? undefined, endDate: albumMetadataForIds?.endDate ?? undefined, assetCount: albumMetadataForIds?.assetCount ?? 0, lastModifiedAssetTimestamp: albumMetadataForIds?.lastModifiedAssetTimestamp ?? undefined, + contributorCounts: isShared ? await this.albumRepository.getContributorCounts(album.id) : undefined, }; } @@ -210,7 +215,7 @@ export class AlbumService extends BaseService { return results; } - const albumAssetValues: { albumsId: string; assetsId: string }[] = []; + const albumAssetValues: { albumId: string; assetId: string }[] = []; const events: { id: string; recipients: string[] }[] = []; for (const albumId of allowedAlbumIds) { const existingAssetIds = await this.albumRepository.getAssetIds(albumId, [...allowedAssetIds]); @@ -223,7 +228,7 @@ export class AlbumService extends BaseService { results.success = true; for (const assetId of notPresentAssetIds) { - albumAssetValues.push({ albumsId: albumId, assetsId: assetId }); + albumAssetValues.push({ albumId, assetId }); } await this.albumRepository.update(albumId, { id: albumId, @@ -284,7 +289,7 @@ export class AlbumService extends BaseService { throw new BadRequestException('User not found'); } - await this.albumUserRepository.create({ usersId: userId, albumsId: id, role }); + await this.albumUserRepository.create({ userId, albumId: id, role }); await this.eventRepository.emit('AlbumInvite', { id, userId }); } @@ -312,12 +317,12 @@ export class AlbumService extends BaseService { await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [id] }); } - await this.albumUserRepository.delete({ albumsId: id, usersId: userId }); + await this.albumUserRepository.delete({ albumId: id, userId }); } async updateUser(auth: AuthDto, id: string, userId: string, dto: UpdateAlbumUserDto): Promise { await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [id] }); - await this.albumUserRepository.update({ albumsId: id, usersId: userId }, { role: dto.role }); + await this.albumUserRepository.update({ albumId: id, userId }, { role: dto.role }); } private async findOrFail(id: string, options: AlbumInfoOptions) { diff --git a/server/src/services/api.service.ts b/server/src/services/api.service.ts index 143b470750..0ec2a65f92 100644 --- a/server/src/services/api.service.ts +++ b/server/src/services/api.service.ts @@ -7,7 +7,6 @@ import { ONE_HOUR } from 'src/constants'; import { ConfigRepository } from 'src/repositories/config.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { AuthService } from 'src/services/auth.service'; -import { JobService } from 'src/services/job.service'; import { SharedLinkService } from 'src/services/shared-link.service'; import { VersionService } from 'src/services/version.service'; import { OpenGraphTags } from 'src/utils/misc'; @@ -40,7 +39,6 @@ const render = (index: string, meta: OpenGraphTags) => { export class ApiService { constructor( private authService: AuthService, - private jobService: JobService, private sharedLinkService: SharedLinkService, private versionService: VersionService, private configRepository: ConfigRepository, @@ -65,9 +63,10 @@ export class ApiService { } return async (request: Request, res: Response, next: NextFunction) => { + const method = request.method.toLowerCase(); if ( request.url.startsWith('/api') || - request.method.toLowerCase() !== 'get' || + (method !== 'get' && method !== 'head') || excludePaths.some((item) => request.url.startsWith(item)) ) { return next(); diff --git a/server/src/services/asset-media.service.spec.ts b/server/src/services/asset-media.service.spec.ts index a338c30d78..f32385b937 100644 --- a/server/src/services/asset-media.service.spec.ts +++ b/server/src/services/asset-media.service.spec.ts @@ -12,6 +12,7 @@ import { MapAsset } from 'src/dtos/asset-response.dto'; import { AssetFileType, AssetStatus, AssetType, AssetVisibility, CacheControl, JobName } from 'src/enum'; import { AuthRequest } from 'src/middleware/auth.guard'; import { AssetMediaService } from 'src/services/asset-media.service'; +import { UploadBody } from 'src/types'; import { ASSET_CHECKSUM_CONSTRAINT } from 'src/utils/database'; import { ImmichFileResponse } from 'src/utils/file'; import { assetStub } from 'test/fixtures/asset.stub'; @@ -35,10 +36,10 @@ const uploadFile = { size: 1000, }, }, - filename: (fieldName: UploadFieldName, filename: string) => { + filename: (fieldName: UploadFieldName, filename: string, body?: UploadBody) => { return { auth: authStub.admin, - body: {}, + body: body || {}, fieldName, file: { uuid: 'random-uuid', @@ -263,6 +264,15 @@ describe(AssetMediaService.name, () => { }); }); } + + it('should prefer filename from body over name from path', () => { + const pathFilename = 'invalid-file-name'; + const body = { filename: 'video.mov' }; + expect(() => sut.canUploadFile(uploadFile.filename(UploadFieldName.ASSET_DATA, pathFilename))).toThrowError( + BadRequestException, + ); + expect(sut.canUploadFile(uploadFile.filename(UploadFieldName.ASSET_DATA, pathFilename, body))).toEqual(true); + }); }); describe('getUploadFilename', () => { diff --git a/server/src/services/asset-media.service.ts b/server/src/services/asset-media.service.ts index 0747bd7b7b..4db60c349f 100644 --- a/server/src/services/asset-media.service.ts +++ b/server/src/services/asset-media.service.ts @@ -51,10 +51,10 @@ export class AssetMediaService extends BaseService { return { id: assetId, status: AssetMediaStatus.DUPLICATE }; } - canUploadFile({ auth, fieldName, file }: UploadRequest): true { + canUploadFile({ auth, fieldName, file, body }: UploadRequest): true { requireUploadAccess(auth); - const filename = file.originalName; + const filename = body.filename || file.originalName; switch (fieldName) { case UploadFieldName.ASSET_DATA: { @@ -426,6 +426,9 @@ export class AssetMediaService extends BaseService { } await this.storageRepository.utimes(file.originalPath, new Date(), new Date(dto.fileModifiedAt)); await this.assetRepository.upsertExif({ assetId: asset.id, fileSizeInByte: file.size }); + + await this.eventRepository.emit('AssetCreate', { asset }); + await this.jobRepository.queue({ name: JobName.AssetExtractMetadata, data: { id: asset.id, source: 'upload' } }); return asset; diff --git a/server/src/services/asset.service.spec.ts b/server/src/services/asset.service.spec.ts index 93861149c3..4b0086c957 100755 --- a/server/src/services/asset.service.spec.ts +++ b/server/src/services/asset.service.spec.ts @@ -700,6 +700,42 @@ describe(AssetService.name, () => { }); }); + describe('getOcr', () => { + it('should require asset read permission', async () => { + mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set()); + + await expect(sut.getOcr(authStub.admin, 'asset-1')).rejects.toBeInstanceOf(BadRequestException); + + expect(mocks.ocr.getByAssetId).not.toHaveBeenCalled(); + }); + + it('should return OCR data for an asset', async () => { + const ocr1 = factory.assetOcr({ text: 'Hello World' }); + const ocr2 = factory.assetOcr({ text: 'Test Image' }); + + mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); + mocks.ocr.getByAssetId.mockResolvedValue([ocr1, ocr2]); + + await expect(sut.getOcr(authStub.admin, 'asset-1')).resolves.toEqual([ocr1, ocr2]); + + expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith( + authStub.admin.user.id, + new Set(['asset-1']), + undefined, + ); + expect(mocks.ocr.getByAssetId).toHaveBeenCalledWith('asset-1'); + }); + + it('should return empty array when no OCR data exists', async () => { + mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); + mocks.ocr.getByAssetId.mockResolvedValue([]); + + await expect(sut.getOcr(authStub.admin, 'asset-1')).resolves.toEqual([]); + + expect(mocks.ocr.getByAssetId).toHaveBeenCalledWith('asset-1'); + }); + }); + describe('run', () => { it('should run the refresh faces job', async () => { mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); diff --git a/server/src/services/asset.service.ts b/server/src/services/asset.service.ts index 6cb0219745..23cc6791dd 100644 --- a/server/src/services/asset.service.ts +++ b/server/src/services/asset.service.ts @@ -7,6 +7,7 @@ import { AssetResponseDto, MapAsset, SanitizedAssetResponseDto, mapAsset } from import { AssetBulkDeleteDto, AssetBulkUpdateDto, + AssetCopyDto, AssetJobName, AssetJobsDto, AssetMetadataResponseDto, @@ -16,6 +17,7 @@ import { mapStats, } from 'src/dtos/asset.dto'; import { AuthDto } from 'src/dtos/auth.dto'; +import { AssetOcrResponseDto } from 'src/dtos/ocr.dto'; import { AssetMetadataKey, AssetStatus, AssetVisibility, JobName, JobStatus, Permission, QueueName } from 'src/enum'; import { BaseService } from 'src/services/base.service'; import { ISidecarWriteJob, JobItem, JobOf } from 'src/types'; @@ -182,6 +184,90 @@ export class AssetService extends BaseService { } } + async copy( + auth: AuthDto, + { + sourceId, + targetId, + albums = true, + sidecar = true, + sharedLinks = true, + stack = true, + favorite = true, + }: AssetCopyDto, + ) { + await this.requireAccess({ auth, permission: Permission.AssetCopy, ids: [sourceId, targetId] }); + const sourceAsset = await this.assetRepository.getById(sourceId); + const targetAsset = await this.assetRepository.getById(targetId); + + if (!sourceAsset || !targetAsset) { + throw new BadRequestException('Both assets must exist'); + } + + if (sourceId === targetId) { + throw new BadRequestException('Source and target id must be distinct'); + } + + if (albums) { + await this.albumRepository.copyAlbums({ sourceAssetId: sourceId, targetAssetId: targetId }); + } + + if (sharedLinks) { + await this.sharedLinkAssetRepository.copySharedLinks({ sourceAssetId: sourceId, targetAssetId: targetId }); + } + + if (stack) { + await this.copyStack({ sourceAsset, targetAsset }); + } + + if (favorite) { + await this.assetRepository.update({ id: targetId, isFavorite: sourceAsset.isFavorite }); + } + + if (sidecar) { + await this.copySidecar({ sourceAsset, targetAsset }); + } + } + + private async copyStack({ + sourceAsset, + targetAsset, + }: { + sourceAsset: { id: string; stackId: string | null }; + targetAsset: { id: string; stackId: string | null }; + }) { + if (!sourceAsset.stackId) { + return; + } + + if (targetAsset.stackId) { + await this.stackRepository.merge({ sourceId: sourceAsset.stackId, targetId: targetAsset.stackId }); + await this.stackRepository.delete(sourceAsset.stackId); + } else { + await this.assetRepository.update({ id: targetAsset.id, stackId: sourceAsset.stackId }); + } + } + + private async copySidecar({ + sourceAsset, + targetAsset, + }: { + sourceAsset: { sidecarPath: string | null }; + targetAsset: { id: string; sidecarPath: string | null; originalPath: string }; + }) { + if (!sourceAsset.sidecarPath) { + return; + } + + if (targetAsset.sidecarPath) { + await this.storageRepository.unlink(targetAsset.sidecarPath); + } + + await this.storageRepository.copyFile(sourceAsset.sidecarPath, `${targetAsset.originalPath}.xmp`); + await this.assetRepository.update({ id: targetAsset.id, sidecarPath: `${targetAsset.originalPath}.xmp` }); + await this.jobRepository.queue({ name: JobName.AssetExtractMetadata, data: { id: targetAsset.id } }); + } + @OnJob({ name: JobName.AssetDeleteCheck, queue: QueueName.BackgroundTask }) async handleAssetDeletionCheck(): Promise { const config = await this.getConfig({ withCache: false }); @@ -289,6 +375,11 @@ export class AssetService extends BaseService { return this.assetRepository.getMetadata(id); } + async getOcr(auth: AuthDto, id: string): Promise { + await this.requireAccess({ auth, permission: Permission.AssetRead, ids: [id] }); + return this.ocrRepository.getByAssetId(id); + } + async upsertMetadata(auth: AuthDto, id: string, dto: AssetMetadataUpsertDto): Promise { await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: [id] }); return this.assetRepository.upsertMetadata(id, dto.items); diff --git a/server/src/services/auth.service.spec.ts b/server/src/services/auth.service.spec.ts index d2b287cd5e..a34efedfb0 100644 --- a/server/src/services/auth.service.spec.ts +++ b/server/src/services/auth.service.spec.ts @@ -41,6 +41,7 @@ const loginDetails = { clientIp: '127.0.0.1', deviceOS: '', deviceType: '', + appVersion: null, }; const fixtures = { @@ -123,6 +124,11 @@ describe(AuthService.name, () => { expect(mocks.user.getForChangePassword).toHaveBeenCalledWith(user.id); expect(mocks.crypto.compareBcrypt).toHaveBeenCalledWith('old-password', 'hash-password'); + expect(mocks.event.emit).toHaveBeenCalledWith('AuthChangePassword', { + userId: user.id, + currentSessionId: auth.session?.id, + shouldLogoutSessions: undefined, + }); }); it('should throw when password does not match existing password', async () => { @@ -146,6 +152,25 @@ describe(AuthService.name, () => { await expect(sut.changePassword(auth, dto)).rejects.toBeInstanceOf(BadRequestException); }); + + it('should change the password and logout other sessions', async () => { + const user = factory.userAdmin(); + const auth = factory.auth({ user }); + const dto = { password: 'old-password', newPassword: 'new-password', invalidateSessions: true }; + + mocks.user.getForChangePassword.mockResolvedValue({ id: user.id, password: 'hash-password' }); + mocks.user.update.mockResolvedValue(user); + + await sut.changePassword(auth, dto); + + expect(mocks.user.getForChangePassword).toHaveBeenCalledWith(user.id); + expect(mocks.crypto.compareBcrypt).toHaveBeenCalledWith('old-password', 'hash-password'); + expect(mocks.event.emit).toHaveBeenCalledWith('AuthChangePassword', { + userId: user.id, + invalidateSessions: true, + currentSessionId: auth.session?.id, + }); + }); }); describe('logout', () => { @@ -243,6 +268,7 @@ describe(AuthService.name, () => { updatedAt: session.updatedAt, user: factory.authUser(), pinExpiresAt: null, + appVersion: null, }; mocks.session.getByToken.mockResolvedValue(sessionWithToken); @@ -408,6 +434,7 @@ describe(AuthService.name, () => { updatedAt: session.updatedAt, user: factory.authUser(), pinExpiresAt: null, + appVersion: null, }; mocks.session.getByToken.mockResolvedValue(sessionWithToken); @@ -435,6 +462,7 @@ describe(AuthService.name, () => { user: factory.authUser(), isPendingSyncReset: false, pinExpiresAt: null, + appVersion: null, }; mocks.session.getByToken.mockResolvedValue(sessionWithToken); @@ -456,6 +484,7 @@ describe(AuthService.name, () => { user: factory.authUser(), isPendingSyncReset: false, pinExpiresAt: null, + appVersion: null, }; mocks.session.getByToken.mockResolvedValue(sessionWithToken); diff --git a/server/src/services/auth.service.ts b/server/src/services/auth.service.ts index 535df779cd..1a68bbfce7 100644 --- a/server/src/services/auth.service.ts +++ b/server/src/services/auth.service.ts @@ -29,11 +29,13 @@ import { BaseService } from 'src/services/base.service'; import { isGranted } from 'src/utils/access'; import { HumanReadableSize } from 'src/utils/bytes'; import { mimeTypes } from 'src/utils/mime-types'; +import { getUserAgentDetails } from 'src/utils/request'; export interface LoginDetails { isSecure: boolean; clientIp: string; deviceType: string; deviceOS: string; + appVersion: string | null; } interface ClaimOptions { @@ -102,6 +104,12 @@ export class AuthService extends BaseService { const updatedUser = await this.userRepository.update(user.id, { password: hashedPassword }); + await this.eventRepository.emit('AuthChangePassword', { + userId: user.id, + currentSessionId: auth.session?.id, + invalidateSessions: dto.invalidateSessions, + }); + return mapUserAdmin(updatedUser); } @@ -218,7 +226,7 @@ export class AuthService extends BaseService { } if (session) { - return this.validateSession(session); + return this.validateSession(session, headers); } if (apiKey) { @@ -463,15 +471,22 @@ export class AuthService extends BaseService { return this.cryptoRepository.compareBcrypt(inputSecret, existingHash); } - private async validateSession(tokenValue: string): Promise { + private async validateSession(tokenValue: string, headers: IncomingHttpHeaders): Promise { const hashedToken = this.cryptoRepository.hashSha256(tokenValue); const session = await this.sessionRepository.getByToken(hashedToken); if (session?.user) { + const { appVersion, deviceOS, deviceType } = getUserAgentDetails(headers); const now = DateTime.now(); const updatedAt = DateTime.fromJSDate(session.updatedAt); const diff = now.diff(updatedAt, ['hours']); - if (diff.hours > 1) { - await this.sessionRepository.update(session.id, { id: session.id, updatedAt: new Date() }); + if (diff.hours > 1 || appVersion != session.appVersion) { + await this.sessionRepository.update(session.id, { + id: session.id, + updatedAt: new Date(), + appVersion, + deviceOS, + deviceType, + }); } // Pin check @@ -529,6 +544,7 @@ export class AuthService extends BaseService { token: tokenHashed, deviceOS: loginDetails.deviceOS, deviceType: loginDetails.deviceType, + appVersion: loginDetails.appVersion, userId: user.id, }); diff --git a/server/src/services/backup.service.spec.ts b/server/src/services/backup.service.spec.ts index ad60e30425..8aa20aa868 100644 --- a/server/src/services/backup.service.spec.ts +++ b/server/src/services/backup.service.spec.ts @@ -209,6 +209,7 @@ describe(BackupService.name, () => { ${'15.3.3'} | ${15} ${'16.4.2'} | ${16} ${'17.15.1'} | ${17} + ${'18.0.0'} | ${18} `( `should use pg_dumpall $expectedVersion with postgres version $postgresVersion`, async ({ postgresVersion, expectedVersion }) => { @@ -224,7 +225,7 @@ describe(BackupService.name, () => { it.each` postgresVersion ${'13.99.99'} - ${'18.0.0'} + ${'19.0.0'} `(`should fail if postgres version $postgresVersion is not supported`, async ({ postgresVersion }) => { mocks.database.getPostgresVersion.mockResolvedValue(postgresVersion); const result = await sut.handleBackupDatabase(); diff --git a/server/src/services/backup.service.ts b/server/src/services/backup.service.ts index 3d99b6e522..6f8cc0e34a 100644 --- a/server/src/services/backup.service.ts +++ b/server/src/services/backup.service.ts @@ -103,7 +103,7 @@ export class BackupService extends BaseService { const databaseSemver = semver.coerce(databaseVersion); const databaseMajorVersion = databaseSemver?.major; - if (!databaseMajorVersion || !databaseSemver || !semver.satisfies(databaseSemver, '>=14.0.0 <18.0.0')) { + if (!databaseMajorVersion || !databaseSemver || !semver.satisfies(databaseSemver, '>=14.0.0 <19.0.0')) { this.logger.error(`Database Backup Failure: Unsupported PostgreSQL version: ${databaseVersion}`); return JobStatus.Failed; } diff --git a/server/src/services/base.service.ts b/server/src/services/base.service.ts index 2f0e272883..2c6d07b635 100644 --- a/server/src/services/base.service.ts +++ b/server/src/services/base.service.ts @@ -32,12 +32,15 @@ import { MetadataRepository } from 'src/repositories/metadata.repository'; import { MoveRepository } from 'src/repositories/move.repository'; import { NotificationRepository } from 'src/repositories/notification.repository'; import { OAuthRepository } from 'src/repositories/oauth.repository'; +import { OcrRepository } from 'src/repositories/ocr.repository'; import { PartnerRepository } from 'src/repositories/partner.repository'; import { PersonRepository } from 'src/repositories/person.repository'; +import { PluginRepository } from 'src/repositories/plugin.repository'; import { ProcessRepository } from 'src/repositories/process.repository'; import { SearchRepository } from 'src/repositories/search.repository'; import { ServerInfoRepository } from 'src/repositories/server-info.repository'; import { SessionRepository } from 'src/repositories/session.repository'; +import { SharedLinkAssetRepository } from 'src/repositories/shared-link-asset.repository'; import { SharedLinkRepository } from 'src/repositories/shared-link.repository'; import { StackRepository } from 'src/repositories/stack.repository'; import { StorageRepository } from 'src/repositories/storage.repository'; @@ -50,6 +53,8 @@ import { TrashRepository } from 'src/repositories/trash.repository'; import { UserRepository } from 'src/repositories/user.repository'; import { VersionHistoryRepository } from 'src/repositories/version-history.repository'; import { ViewRepository } from 'src/repositories/view-repository'; +import { WebsocketRepository } from 'src/repositories/websocket.repository'; +import { WorkflowRepository } from 'src/repositories/workflow.repository'; import { UserTable } from 'src/schema/tables/user.table'; import { AccessRequest, checkAccess, requireAccess } from 'src/utils/access'; import { getConfig, updateConfig } from 'src/utils/config'; @@ -82,13 +87,16 @@ export const BASE_SERVICE_DEPENDENCIES = [ MoveRepository, NotificationRepository, OAuthRepository, + OcrRepository, PartnerRepository, PersonRepository, + PluginRepository, ProcessRepository, SearchRepository, ServerInfoRepository, SessionRepository, SharedLinkRepository, + SharedLinkAssetRepository, StackRepository, StorageRepository, SyncRepository, @@ -100,6 +108,8 @@ export const BASE_SERVICE_DEPENDENCIES = [ UserRepository, VersionHistoryRepository, ViewRepository, + WebsocketRepository, + WorkflowRepository, ]; @Injectable() @@ -134,13 +144,16 @@ export class BaseService { protected moveRepository: MoveRepository, protected notificationRepository: NotificationRepository, protected oauthRepository: OAuthRepository, + protected ocrRepository: OcrRepository, protected partnerRepository: PartnerRepository, protected personRepository: PersonRepository, + protected pluginRepository: PluginRepository, protected processRepository: ProcessRepository, protected searchRepository: SearchRepository, protected serverInfoRepository: ServerInfoRepository, protected sessionRepository: SessionRepository, protected sharedLinkRepository: SharedLinkRepository, + protected sharedLinkAssetRepository: SharedLinkAssetRepository, protected stackRepository: StackRepository, protected storageRepository: StorageRepository, protected syncRepository: SyncRepository, @@ -152,6 +165,8 @@ export class BaseService { protected userRepository: UserRepository, protected versionRepository: VersionHistoryRepository, protected viewRepository: ViewRepository, + protected websocketRepository: WebsocketRepository, + protected workflowRepository: WorkflowRepository, ) { this.logger.setContext(this.constructor.name); this.storageCore = StorageCore.create( @@ -195,8 +210,8 @@ export class BaseService { } async createUser(dto: Insertable & { email: string }): Promise { - const user = await this.userRepository.getByEmail(dto.email); - if (user) { + const exists = await this.userRepository.getByEmail(dto.email); + if (exists) { throw new BadRequestException('User exists'); } @@ -215,6 +230,10 @@ export class BaseService { payload.storageLabel = sanitize(payload.storageLabel.replaceAll('.', '')); } - return this.userRepository.create(payload); + const user = await this.userRepository.create(payload); + + await this.eventRepository.emit('UserCreate', user); + + return user; } } diff --git a/server/src/services/database.service.ts b/server/src/services/database.service.ts index 758198a197..2ff0e0ca27 100644 --- a/server/src/services/database.service.ts +++ b/server/src/services/database.service.ts @@ -22,7 +22,7 @@ const messages = { The ${name} extension version is ${version}, which means it is a nightly release. Please run 'DROP EXTENSION IF EXISTS ${extension}' and switch to a release version. - See https://immich.app/docs/guides/database-queries for how to query the database.`, + See https://docs.immich.app/guides/database-queries for how to query the database.`, outOfRange: ({ name, version, range }: OutOfRangeArgs) => `The ${name} extension version is ${version}, but Immich only supports ${range}. Please change ${name} to a compatible version in the Postgres instance.`, @@ -32,20 +32,20 @@ const messages = { If the Postgres instance already has ${name} installed, Immich may not have the necessary permissions to activate it. In this case, please run 'CREATE EXTENSION IF NOT EXISTS ${extension} CASCADE' manually as a superuser. - See https://immich.app/docs/guides/database-queries for how to query the database.`, + See https://docs.immich.app/guides/database-queries for how to query the database.`, updateFailed: ({ name, extension, availableVersion }: UpdateFailedArgs) => `The ${name} extension can be updated to ${availableVersion}. Immich attempted to update the extension, but failed to do so. This may be because Immich does not have the necessary permissions to update the extension. Please run 'ALTER EXTENSION ${extension} UPDATE' manually as a superuser. - See https://immich.app/docs/guides/database-queries for how to query the database.`, + See https://docs.immich.app/guides/database-queries for how to query the database.`, dropFailed: ({ name, extension }: DropFailedArgs) => `The ${name} extension is no longer needed, but could not be dropped. This may be because Immich does not have the necessary permissions to drop the extension. Please run 'DROP EXTENSION ${extension};' manually as a superuser. - See https://immich.app/docs/guides/database-queries for how to query the database.`, + See https://docs.immich.app/guides/database-queries for how to query the database.`, restartRequired: ({ name, availableVersion }: RestartRequiredArgs) => `The ${name} extension has been updated to ${availableVersion}. Please restart the Postgres instance to complete the update.`, @@ -55,7 +55,7 @@ const messages = { If ${name} ${installedVersion} is compatible with Immich, please ensure the Postgres instance has this available.`, deprecatedExtension: (name: string) => `DEPRECATION WARNING: The ${name} extension is deprecated and support for it will be removed very soon. - See https://immich.app/docs/install/upgrading#migrating-to-vectorchord in order to switch to the VectorChord extension instead.`, + See https://docs.immich.app/install/upgrading#migrating-to-vectorchord in order to switch to the VectorChord extension instead.`, }; @Injectable() diff --git a/server/src/services/index.ts b/server/src/services/index.ts index cad38ca1f4..9d09bdaa53 100644 --- a/server/src/services/index.ts +++ b/server/src/services/index.ts @@ -20,8 +20,11 @@ import { MemoryService } from 'src/services/memory.service'; import { MetadataService } from 'src/services/metadata.service'; import { NotificationAdminService } from 'src/services/notification-admin.service'; import { NotificationService } from 'src/services/notification.service'; +import { OcrService } from 'src/services/ocr.service'; import { PartnerService } from 'src/services/partner.service'; import { PersonService } from 'src/services/person.service'; +import { PluginService } from 'src/services/plugin.service'; +import { QueueService } from 'src/services/queue.service'; import { SearchService } from 'src/services/search.service'; import { ServerService } from 'src/services/server.service'; import { SessionService } from 'src/services/session.service'; @@ -34,12 +37,14 @@ import { SyncService } from 'src/services/sync.service'; import { SystemConfigService } from 'src/services/system-config.service'; import { SystemMetadataService } from 'src/services/system-metadata.service'; import { TagService } from 'src/services/tag.service'; +import { TelemetryService } from 'src/services/telemetry.service'; import { TimelineService } from 'src/services/timeline.service'; import { TrashService } from 'src/services/trash.service'; import { UserAdminService } from 'src/services/user-admin.service'; import { UserService } from 'src/services/user.service'; import { VersionService } from 'src/services/version.service'; import { ViewService } from 'src/services/view.service'; +import { WorkflowService } from 'src/services/workflow.service'; export const services = [ ApiKeyService, @@ -64,8 +69,11 @@ export const services = [ MetadataService, NotificationService, NotificationAdminService, + OcrService, PartnerService, PersonService, + PluginService, + QueueService, SearchService, ServerService, SessionService, @@ -78,10 +86,12 @@ export const services = [ SystemConfigService, SystemMetadataService, TagService, + TelemetryService, TimelineService, TrashService, UserAdminService, UserService, VersionService, ViewService, + WorkflowService, ]; diff --git a/server/src/services/job.service.spec.ts b/server/src/services/job.service.spec.ts index 6b85cdff4d..c23b4f05df 100644 --- a/server/src/services/job.service.spec.ts +++ b/server/src/services/job.service.spec.ts @@ -1,6 +1,4 @@ -import { BadRequestException } from '@nestjs/common'; -import { defaults, SystemConfig } from 'src/config'; -import { ImmichWorker, JobCommand, JobName, JobStatus, QueueName } from 'src/enum'; +import { ImmichWorker, JobName, JobStatus, QueueName } from 'src/enum'; import { JobService } from 'src/services/job.service'; import { JobItem } from 'src/types'; import { assetStub } from 'test/fixtures/asset.stub'; @@ -20,220 +18,16 @@ describe(JobService.name, () => { expect(sut).toBeDefined(); }); - describe('onConfigUpdate', () => { - it('should update concurrency', () => { - sut.onConfigUpdate({ newConfig: defaults, oldConfig: {} as SystemConfig }); - - expect(mocks.job.setConcurrency).toHaveBeenCalledTimes(15); - expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(5, QueueName.FacialRecognition, 1); - expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(7, QueueName.DuplicateDetection, 1); - expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(8, QueueName.BackgroundTask, 5); - expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(9, QueueName.StorageTemplateMigration, 1); - }); - }); - - describe('handleNightlyJobs', () => { - it('should run the scheduled jobs', async () => { - await sut.handleNightlyJobs(); - - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { name: JobName.AssetDeleteCheck }, - { name: JobName.UserDeleteCheck }, - { name: JobName.PersonCleanup }, - { name: JobName.MemoryCleanup }, - { name: JobName.SessionCleanup }, - { name: JobName.AuditTableCleanup }, - { name: JobName.AuditLogCleanup }, - { name: JobName.MemoryGenerate }, - { name: JobName.UserSyncUsage }, - { name: JobName.AssetGenerateThumbnailsQueueAll, data: { force: false } }, - { name: JobName.FacialRecognitionQueueAll, data: { force: false, nightly: true } }, - ]); - }); - }); - - describe('getAllJobStatus', () => { - it('should get all job statuses', async () => { - mocks.job.getJobCounts.mockResolvedValue({ - active: 1, - completed: 1, - failed: 1, - delayed: 1, - waiting: 1, - paused: 1, - }); - mocks.job.getQueueStatus.mockResolvedValue({ - isActive: true, - isPaused: true, - }); - - const expectedJobStatus = { - jobCounts: { - active: 1, - completed: 1, - delayed: 1, - failed: 1, - waiting: 1, - paused: 1, - }, - queueStatus: { - isActive: true, - isPaused: true, - }, - }; - - await expect(sut.getAllJobsStatus()).resolves.toEqual({ - [QueueName.BackgroundTask]: expectedJobStatus, - [QueueName.DuplicateDetection]: expectedJobStatus, - [QueueName.SmartSearch]: expectedJobStatus, - [QueueName.MetadataExtraction]: expectedJobStatus, - [QueueName.Search]: expectedJobStatus, - [QueueName.StorageTemplateMigration]: expectedJobStatus, - [QueueName.Migration]: expectedJobStatus, - [QueueName.ThumbnailGeneration]: expectedJobStatus, - [QueueName.VideoConversion]: expectedJobStatus, - [QueueName.FaceDetection]: expectedJobStatus, - [QueueName.FacialRecognition]: expectedJobStatus, - [QueueName.Sidecar]: expectedJobStatus, - [QueueName.Library]: expectedJobStatus, - [QueueName.Notification]: expectedJobStatus, - [QueueName.BackupDatabase]: expectedJobStatus, - }); - }); - }); - - describe('handleCommand', () => { - it('should handle a pause command', async () => { - await sut.handleCommand(QueueName.MetadataExtraction, { command: JobCommand.Pause, force: false }); - - expect(mocks.job.pause).toHaveBeenCalledWith(QueueName.MetadataExtraction); - }); - - it('should handle a resume command', async () => { - await sut.handleCommand(QueueName.MetadataExtraction, { command: JobCommand.Resume, force: false }); - - expect(mocks.job.resume).toHaveBeenCalledWith(QueueName.MetadataExtraction); - }); - - it('should handle an empty command', async () => { - await sut.handleCommand(QueueName.MetadataExtraction, { command: JobCommand.Empty, force: false }); - - expect(mocks.job.empty).toHaveBeenCalledWith(QueueName.MetadataExtraction); - }); - - it('should not start a job that is already running', async () => { - mocks.job.getQueueStatus.mockResolvedValue({ isActive: true, isPaused: false }); - - await expect( - sut.handleCommand(QueueName.VideoConversion, { command: JobCommand.Start, force: false }), - ).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.job.queue).not.toHaveBeenCalled(); - expect(mocks.job.queueAll).not.toHaveBeenCalled(); - }); - - it('should handle a start video conversion command', async () => { - mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); - - await sut.handleCommand(QueueName.VideoConversion, { command: JobCommand.Start, force: false }); - - expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.AssetEncodeVideoQueueAll, data: { force: false } }); - }); - - it('should handle a start storage template migration command', async () => { - mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); - - await sut.handleCommand(QueueName.StorageTemplateMigration, { command: JobCommand.Start, force: false }); - - expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.StorageTemplateMigration }); - }); - - it('should handle a start smart search command', async () => { - mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); - - await sut.handleCommand(QueueName.SmartSearch, { command: JobCommand.Start, force: false }); - - expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.SmartSearchQueueAll, data: { force: false } }); - }); - - it('should handle a start metadata extraction command', async () => { - mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); - - await sut.handleCommand(QueueName.MetadataExtraction, { command: JobCommand.Start, force: false }); - - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.AssetExtractMetadataQueueAll, - data: { force: false }, - }); - }); - - it('should handle a start sidecar command', async () => { - mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); - - await sut.handleCommand(QueueName.Sidecar, { command: JobCommand.Start, force: false }); - - expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.SidecarQueueAll, data: { force: false } }); - }); - - it('should handle a start thumbnail generation command', async () => { - mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); - - await sut.handleCommand(QueueName.ThumbnailGeneration, { command: JobCommand.Start, force: false }); - - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.AssetGenerateThumbnailsQueueAll, - data: { force: false }, - }); - }); - - it('should handle a start face detection command', async () => { - mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); - - await sut.handleCommand(QueueName.FaceDetection, { command: JobCommand.Start, force: false }); - - expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.AssetDetectFacesQueueAll, data: { force: false } }); - }); - - it('should handle a start facial recognition command', async () => { - mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); - - await sut.handleCommand(QueueName.FacialRecognition, { command: JobCommand.Start, force: false }); - - expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.FacialRecognitionQueueAll, data: { force: false } }); - }); - - it('should handle a start backup database command', async () => { - mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); - - await sut.handleCommand(QueueName.BackupDatabase, { command: JobCommand.Start, force: false }); - - expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.DatabaseBackup, data: { force: false } }); - }); - - it('should throw a bad request when an invalid queue is used', async () => { - mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); - - await expect( - sut.handleCommand(QueueName.BackgroundTask, { command: JobCommand.Start, force: false }), - ).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.job.queue).not.toHaveBeenCalled(); - expect(mocks.job.queueAll).not.toHaveBeenCalled(); - }); - }); - - describe('onJobStart', () => { + describe('onJobRun', () => { it('should process a successful job', async () => { mocks.job.run.mockResolvedValue(JobStatus.Success); - await sut.onJobStart(QueueName.BackgroundTask, { - name: JobName.FileDelete, - data: { files: ['path/to/file'] }, - }); + const job: JobItem = { name: JobName.FileDelete, data: { files: ['path/to/file'] } }; + await sut.onJobRun(QueueName.BackgroundTask, job); - expect(mocks.telemetry.jobs.addToGauge).toHaveBeenCalledWith('immich.queues.background_task.active', 1); - expect(mocks.telemetry.jobs.addToGauge).toHaveBeenCalledWith('immich.queues.background_task.active', -1); - expect(mocks.telemetry.jobs.addToCounter).toHaveBeenCalledWith('immich.jobs.file_delete.success', 1); + expect(mocks.event.emit).toHaveBeenCalledWith('JobStart', QueueName.BackgroundTask, job); + expect(mocks.event.emit).toHaveBeenCalledWith('JobSuccess', { job, response: JobStatus.Success }); + expect(mocks.event.emit).toHaveBeenCalledWith('JobComplete', QueueName.BackgroundTask, job); expect(mocks.logger.error).not.toHaveBeenCalled(); }); @@ -270,12 +64,12 @@ describe(JobService.name, () => { }, { item: { name: JobName.AssetGenerateThumbnails, data: { id: 'asset-1', source: 'upload' } }, - jobs: [JobName.SmartSearch, JobName.AssetDetectFaces], + jobs: [JobName.SmartSearch, JobName.AssetDetectFaces, JobName.Ocr], stub: [assetStub.livePhotoStillAsset], }, { item: { name: JobName.AssetGenerateThumbnails, data: { id: 'asset-1', source: 'upload' } }, - jobs: [JobName.SmartSearch, JobName.AssetDetectFaces, JobName.AssetEncodeVideo], + jobs: [JobName.SmartSearch, JobName.AssetDetectFaces, JobName.Ocr, JobName.AssetEncodeVideo], stub: [assetStub.video], }, { @@ -300,7 +94,7 @@ describe(JobService.name, () => { mocks.job.run.mockResolvedValue(JobStatus.Success); - await sut.onJobStart(QueueName.BackgroundTask, item); + await sut.onJobRun(QueueName.BackgroundTask, item); if (jobs.length > 1) { expect(mocks.job.queueAll).toHaveBeenCalledWith( @@ -317,7 +111,7 @@ describe(JobService.name, () => { it(`should not queue any jobs when ${item.name} fails`, async () => { mocks.job.run.mockResolvedValue(JobStatus.Failed); - await sut.onJobStart(QueueName.BackgroundTask, item); + await sut.onJobRun(QueueName.BackgroundTask, item); expect(mocks.job.queueAll).not.toHaveBeenCalled(); }); diff --git a/server/src/services/job.service.ts b/server/src/services/job.service.ts index dc48c03bd1..b57a203788 100644 --- a/server/src/services/job.service.ts +++ b/server/src/services/job.service.ts @@ -1,29 +1,12 @@ import { BadRequestException, Injectable } from '@nestjs/common'; -import { ClassConstructor } from 'class-transformer'; -import { snakeCase } from 'lodash'; -import { SystemConfig } from 'src/config'; import { OnEvent } from 'src/decorators'; import { mapAsset } from 'src/dtos/asset-response.dto'; -import { AllJobStatusResponseDto, JobCommandDto, JobCreateDto, JobStatusDto } from 'src/dtos/job.dto'; -import { - AssetType, - AssetVisibility, - BootstrapEventPriority, - CronJob, - DatabaseLock, - ImmichWorker, - JobCommand, - JobName, - JobStatus, - ManualJobName, - QueueCleanType, - QueueName, -} from 'src/enum'; -import { ArgOf, ArgsOf } from 'src/repositories/event.repository'; +import { JobCreateDto } from 'src/dtos/job.dto'; +import { AssetType, AssetVisibility, JobName, JobStatus, ManualJobName } from 'src/enum'; +import { ArgsOf } from 'src/repositories/event.repository'; import { BaseService } from 'src/services/base.service'; -import { ConcurrentQueueName, JobItem } from 'src/types'; +import { JobItem } from 'src/types'; import { hexOrBufferToBase64 } from 'src/utils/bytes'; -import { handlePromiseError } from 'src/utils/misc'; const asJobItem = (dto: JobCreateDto): JobItem => { switch (dto.name) { @@ -57,254 +40,28 @@ const asJobItem = (dto: JobCreateDto): JobItem => { } }; -const asNightlyTasksCron = (config: SystemConfig) => { - const [hours, minutes] = config.nightlyTasks.startTime.split(':').map(Number); - return `${minutes} ${hours} * * *`; -}; - @Injectable() export class JobService extends BaseService { - private services: ClassConstructor[] = []; - private nightlyJobsLock = false; - - @OnEvent({ name: 'ConfigInit' }) - async onConfigInit({ newConfig: config }: ArgOf<'ConfigInit'>) { - if (this.worker === ImmichWorker.Microservices) { - this.updateQueueConcurrency(config); - return; - } - - this.nightlyJobsLock = await this.databaseRepository.tryLock(DatabaseLock.NightlyJobs); - if (this.nightlyJobsLock) { - const cronExpression = asNightlyTasksCron(config); - this.logger.debug(`Scheduling nightly jobs for ${cronExpression}`); - this.cronRepository.create({ - name: CronJob.NightlyJobs, - expression: cronExpression, - start: true, - onTick: () => handlePromiseError(this.handleNightlyJobs(), this.logger), - }); - } - } - - @OnEvent({ name: 'ConfigUpdate', server: true }) - onConfigUpdate({ newConfig: config }: ArgOf<'ConfigUpdate'>) { - if (this.worker === ImmichWorker.Microservices) { - this.updateQueueConcurrency(config); - return; - } - - if (this.nightlyJobsLock) { - const cronExpression = asNightlyTasksCron(config); - this.logger.debug(`Scheduling nightly jobs for ${cronExpression}`); - this.cronRepository.update({ name: CronJob.NightlyJobs, expression: cronExpression, start: true }); - } - } - - @OnEvent({ name: 'AppBootstrap', priority: BootstrapEventPriority.JobService }) - onBootstrap() { - this.jobRepository.setup(this.services); - if (this.worker === ImmichWorker.Microservices) { - this.jobRepository.startWorkers(); - } - } - - private updateQueueConcurrency(config: SystemConfig) { - this.logger.debug(`Updating queue concurrency settings`); - for (const queueName of Object.values(QueueName)) { - let concurrency = 1; - if (this.isConcurrentQueue(queueName)) { - concurrency = config.job[queueName].concurrency; - } - this.logger.debug(`Setting ${queueName} concurrency to ${concurrency}`); - this.jobRepository.setConcurrency(queueName, concurrency); - } - } - - setServices(services: ClassConstructor[]) { - this.services = services; - } - async create(dto: JobCreateDto): Promise { await this.jobRepository.queue(asJobItem(dto)); } - async handleCommand(queueName: QueueName, dto: JobCommandDto): Promise { - this.logger.debug(`Handling command: queue=${queueName},command=${dto.command},force=${dto.force}`); - - switch (dto.command) { - case JobCommand.Start: { - await this.start(queueName, dto); - break; - } - - case JobCommand.Pause: { - await this.jobRepository.pause(queueName); - break; - } - - case JobCommand.Resume: { - await this.jobRepository.resume(queueName); - break; - } - - case JobCommand.Empty: { - await this.jobRepository.empty(queueName); - break; - } - - case JobCommand.ClearFailed: { - const failedJobs = await this.jobRepository.clear(queueName, QueueCleanType.Failed); - this.logger.debug(`Cleared failed jobs: ${failedJobs}`); - break; - } - } - - return this.getJobStatus(queueName); - } - - async getJobStatus(queueName: QueueName): Promise { - const [jobCounts, queueStatus] = await Promise.all([ - this.jobRepository.getJobCounts(queueName), - this.jobRepository.getQueueStatus(queueName), - ]); - - return { jobCounts, queueStatus }; - } - - async getAllJobsStatus(): Promise { - const response = new AllJobStatusResponseDto(); - for (const queueName of Object.values(QueueName)) { - response[queueName] = await this.getJobStatus(queueName); - } - return response; - } - - private async start(name: QueueName, { force }: JobCommandDto): Promise { - const { isActive } = await this.jobRepository.getQueueStatus(name); - if (isActive) { - throw new BadRequestException(`Job is already running`); - } - - this.telemetryRepository.jobs.addToCounter(`immich.queues.${snakeCase(name)}.started`, 1); - - switch (name) { - case QueueName.VideoConversion: { - return this.jobRepository.queue({ name: JobName.AssetEncodeVideoQueueAll, data: { force } }); - } - - case QueueName.StorageTemplateMigration: { - return this.jobRepository.queue({ name: JobName.StorageTemplateMigration }); - } - - case QueueName.Migration: { - return this.jobRepository.queue({ name: JobName.FileMigrationQueueAll }); - } - - case QueueName.SmartSearch: { - return this.jobRepository.queue({ name: JobName.SmartSearchQueueAll, data: { force } }); - } - - case QueueName.DuplicateDetection: { - return this.jobRepository.queue({ name: JobName.AssetDetectDuplicatesQueueAll, data: { force } }); - } - - case QueueName.MetadataExtraction: { - return this.jobRepository.queue({ name: JobName.AssetExtractMetadataQueueAll, data: { force } }); - } - - case QueueName.Sidecar: { - return this.jobRepository.queue({ name: JobName.SidecarQueueAll, data: { force } }); - } - - case QueueName.ThumbnailGeneration: { - return this.jobRepository.queue({ name: JobName.AssetGenerateThumbnailsQueueAll, data: { force } }); - } - - case QueueName.FaceDetection: { - return this.jobRepository.queue({ name: JobName.AssetDetectFacesQueueAll, data: { force } }); - } - - case QueueName.FacialRecognition: { - return this.jobRepository.queue({ name: JobName.FacialRecognitionQueueAll, data: { force } }); - } - - case QueueName.Library: { - return this.jobRepository.queue({ name: JobName.LibraryScanQueueAll, data: { force } }); - } - - case QueueName.BackupDatabase: { - return this.jobRepository.queue({ name: JobName.DatabaseBackup, data: { force } }); - } - - default: { - throw new BadRequestException(`Invalid job name: ${name}`); - } - } - } - - @OnEvent({ name: 'JobStart' }) - async onJobStart(...[queueName, job]: ArgsOf<'JobStart'>) { - const queueMetric = `immich.queues.${snakeCase(queueName)}.active`; - this.telemetryRepository.jobs.addToGauge(queueMetric, 1); + @OnEvent({ name: 'JobRun' }) + async onJobRun(...[queueName, job]: ArgsOf<'JobRun'>) { try { - const status = await this.jobRepository.run(job); - const jobMetric = `immich.jobs.${snakeCase(job.name)}.${status}`; - this.telemetryRepository.jobs.addToCounter(jobMetric, 1); - if (status === JobStatus.Success || status == JobStatus.Skipped) { + await this.eventRepository.emit('JobStart', queueName, job); + const response = await this.jobRepository.run(job); + await this.eventRepository.emit('JobSuccess', { job, response }); + if (response && typeof response === 'string' && [JobStatus.Success, JobStatus.Skipped].includes(response)) { await this.onDone(job); } } catch (error: Error | any) { - await this.eventRepository.emit('JobFailed', { job, error }); + await this.eventRepository.emit('JobError', { job, error }); } finally { - this.telemetryRepository.jobs.addToGauge(queueMetric, -1); + await this.eventRepository.emit('JobComplete', queueName, job); } } - private isConcurrentQueue(name: QueueName): name is ConcurrentQueueName { - return ![ - QueueName.FacialRecognition, - QueueName.StorageTemplateMigration, - QueueName.DuplicateDetection, - QueueName.BackupDatabase, - ].includes(name); - } - - async handleNightlyJobs() { - const config = await this.getConfig({ withCache: false }); - const jobs: JobItem[] = []; - - if (config.nightlyTasks.databaseCleanup) { - jobs.push( - { name: JobName.AssetDeleteCheck }, - { name: JobName.UserDeleteCheck }, - { name: JobName.PersonCleanup }, - { name: JobName.MemoryCleanup }, - { name: JobName.SessionCleanup }, - { name: JobName.AuditTableCleanup }, - { name: JobName.AuditLogCleanup }, - ); - } - - if (config.nightlyTasks.generateMemories) { - jobs.push({ name: JobName.MemoryGenerate }); - } - - if (config.nightlyTasks.syncQuotaUsage) { - jobs.push({ name: JobName.UserSyncUsage }); - } - - if (config.nightlyTasks.missingThumbnails) { - jobs.push({ name: JobName.AssetGenerateThumbnailsQueueAll, data: { force: false } }); - } - - if (config.nightlyTasks.clusterNewFaces) { - jobs.push({ name: JobName.FacialRecognitionQueueAll, data: { force: false, nightly: true } }); - } - - await this.jobRepository.queueAll(jobs); - } - /** * Queue follow up jobs */ @@ -334,7 +91,7 @@ export class JobService extends BaseService { const { id } = item.data; const person = await this.personRepository.getById(id); if (person) { - this.eventRepository.clientSend('on_person_thumbnail', person.ownerId, person.id); + this.websocketRepository.clientSend('on_person_thumbnail', person.ownerId, person.id); } break; } @@ -353,6 +110,7 @@ export class JobService extends BaseService { const jobs: JobItem[] = [ { name: JobName.SmartSearch, data: item.data }, { name: JobName.AssetDetectFaces, data: item.data }, + { name: JobName.Ocr, data: item.data }, ]; if (asset.type === AssetType.Video) { @@ -361,10 +119,10 @@ export class JobService extends BaseService { await this.jobRepository.queueAll(jobs); if (asset.visibility === AssetVisibility.Timeline || asset.visibility === AssetVisibility.Archive) { - this.eventRepository.clientSend('on_upload_success', asset.ownerId, mapAsset(asset)); + this.websocketRepository.clientSend('on_upload_success', asset.ownerId, mapAsset(asset)); if (asset.exifInfo) { const exif = asset.exifInfo; - this.eventRepository.clientSend('AssetUploadReadyV1', asset.ownerId, { + this.websocketRepository.clientSend('AssetUploadReadyV1', asset.ownerId, { // TODO remove `on_upload_success` and then modify the query to select only the required fields) asset: { id: asset.id, @@ -424,11 +182,6 @@ export class JobService extends BaseService { } break; } - - case JobName.UserDelete: { - this.eventRepository.clientBroadcast('on_user_delete', item.data.id); - break; - } } } } diff --git a/server/src/services/media.service.spec.ts b/server/src/services/media.service.spec.ts index e494dd9bcc..ad52b0e8b0 100644 --- a/server/src/services/media.service.spec.ts +++ b/server/src/services/media.service.spec.ts @@ -445,6 +445,7 @@ describe(MediaService.name, () => { }), ); }); + it('should not skip intra frames for MTS file', async () => { mocks.media.probe.mockResolvedValue(probeStub.videoStreamMTS); mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.video); @@ -462,6 +463,25 @@ describe(MediaService.name, () => { ); }); + it('should override reserved color metadata', async () => { + mocks.media.probe.mockResolvedValue(probeStub.videoStreamReserved); + mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.video); + await sut.handleGenerateThumbnails({ id: assetStub.video.id }); + + expect(mocks.media.transcode).toHaveBeenCalledWith( + '/original/path.ext', + expect.any(String), + expect.objectContaining({ + inputOptions: expect.arrayContaining([ + '-bsf:v hevc_metadata=colour_primaries=1:matrix_coefficients=1:transfer_characteristics=1', + ]), + outputOptions: expect.any(Array), + progress: expect.any(Object), + twoPass: false, + }), + ); + }); + it('should use scaling divisible by 2 even when using quick sync', async () => { mocks.media.probe.mockResolvedValue(probeStub.videoStream2160p); mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { accel: TranscodeHardwareAcceleration.Qsv } }); @@ -841,6 +861,37 @@ describe(MediaService.name, () => { ); }); + it('should always generate full-size preview from non-web-friendly panoramas', async () => { + mocks.systemMetadata.get.mockResolvedValue({ image: { fullsize: { enabled: false } } }); + mocks.media.extract.mockResolvedValue({ buffer: extractedBuffer, format: RawExtractedFormat.Jpeg }); + mocks.media.getImageDimensions.mockResolvedValue({ width: 3840, height: 2160 }); + + mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.panoramaTif); + + await sut.handleGenerateThumbnails({ id: assetStub.image.id }); + + expect(mocks.media.decodeImage).toHaveBeenCalledOnce(); + expect(mocks.media.decodeImage).toHaveBeenCalledWith(assetStub.panoramaTif.originalPath, { + colorspace: Colorspace.Srgb, + orientation: undefined, + processInvalidImages: false, + size: undefined, + }); + + expect(mocks.media.generateThumbnail).toHaveBeenCalledTimes(3); + expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( + rawBuffer, + { + colorspace: Colorspace.Srgb, + format: ImageFormat.Jpeg, + quality: 80, + processInvalidImages: false, + raw: rawInfo, + }, + expect.any(String), + ); + }); + it('should respect encoding options when generating full-size preview', async () => { mocks.systemMetadata.get.mockResolvedValue({ image: { fullsize: { enabled: true, format: ImageFormat.Webp, quality: 90 } }, diff --git a/server/src/services/media.service.ts b/server/src/services/media.service.ts index a2c3c5ed42..6caa682f5e 100644 --- a/server/src/services/media.service.ts +++ b/server/src/services/media.service.ts @@ -271,7 +271,9 @@ export class MediaService extends BaseService { // Handle embedded preview extraction for RAW files const extractEmbedded = image.extractEmbedded && mimeTypes.isRaw(asset.originalFileName); const extracted = extractEmbedded ? await this.extractImage(asset.originalPath, image.preview.size) : null; - const generateFullsize = image.fullsize.enabled && !mimeTypes.isWebSupportedImage(asset.originalPath); + const generateFullsize = + (image.fullsize.enabled || asset.exifInfo.projectionType == 'EQUIRECTANGULAR') && + !mimeTypes.isWebSupportedImage(asset.originalPath); const convertFullsize = generateFullsize && (!extracted || !mimeTypes.isWebSupportedImage(` .${extracted.format}`)); const { info, data, colorspace } = await this.decodeImage( diff --git a/server/src/services/metadata.service.spec.ts b/server/src/services/metadata.service.spec.ts index 0adb390f6a..220216a2c8 100644 --- a/server/src/services/metadata.service.spec.ts +++ b/server/src/services/metadata.service.spec.ts @@ -1,4 +1,5 @@ import { BinaryField, ExifDateTime } from 'exiftool-vendored'; +import { DateTime } from 'luxon'; import { randomBytes } from 'node:crypto'; import { Stats } from 'node:fs'; import { defaults } from 'src/config'; @@ -231,7 +232,7 @@ describe(MetadataService.name, () => { }); }); - it('should account for the server being in a non-UTC timezone', async () => { + it('should determine dateTimeOriginal regardless of the server time zone', async () => { process.env.TZ = 'America/Los_Angeles'; mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.sidecar); mockReadTags({ DateTimeOriginal: '2022:01:01 00:00:00' }); @@ -239,7 +240,7 @@ describe(MetadataService.name, () => { await sut.handleMetadataExtraction({ id: assetStub.image.id }); expect(mocks.asset.upsertExif).toHaveBeenCalledWith( expect.objectContaining({ - dateTimeOriginal: new Date('2022-01-01T08:00:00.000Z'), + dateTimeOriginal: new Date('2022-01-01T00:00:00.000Z'), }), ); @@ -856,6 +857,7 @@ describe(MetadataService.name, () => { tz: 'UTC-11:30', Rating: 3, }; + mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.image); mockReadTags(tags); @@ -897,7 +899,7 @@ describe(MetadataService.name, () => { id: assetStub.image.id, duration: null, fileCreatedAt: dateForTest, - localDateTime: dateForTest, + localDateTime: DateTime.fromISO('1970-01-01T00:00:00.000Z').toJSDate(), }), ); }); @@ -1595,7 +1597,7 @@ describe(MetadataService.name, () => { const result = firstDateTime(tags); expect(result?.tag).toBe('SonyDateTime2'); - expect(result?.dateTime?.toDate()?.toISOString()).toBe('2023-07-07T07:00:00.000Z'); + expect(result?.dateTime?.toISOString()).toBe('2023-07-07T07:00:00'); }); it('should respect full priority order with all date tags present', () => { @@ -1624,7 +1626,7 @@ describe(MetadataService.name, () => { const result = firstDateTime(tags); // Should use SubSecDateTimeOriginal as it has highest priority expect(result?.tag).toBe('SubSecDateTimeOriginal'); - expect(result?.dateTime?.toDate()?.toISOString()).toBe('2023-01-01T01:00:00.000Z'); + expect(result?.dateTime?.toISOString()).toBe('2023-01-01T01:00:00'); }); it('should handle missing SubSec tags and use available date tags', () => { @@ -1644,7 +1646,7 @@ describe(MetadataService.name, () => { const result = firstDateTime(tags); // Should use CreationDate when available expect(result?.tag).toBe('CreationDate'); - expect(result?.dateTime?.toDate()?.toISOString()).toBe('2023-07-07T07:00:00.000Z'); + expect(result?.dateTime?.toISOString()).toBe('2023-07-07T07:00:00'); }); it('should handle invalid date formats gracefully', () => { @@ -1658,7 +1660,7 @@ describe(MetadataService.name, () => { const result = firstDateTime(tags); // Should skip invalid dates and use the first valid one expect(result?.tag).toBe('GPSDateTime'); - expect(result?.dateTime?.toDate()?.toISOString()).toBe('2023-10-10T10:00:00.000Z'); + expect(result?.dateTime?.toISOString()).toBe('2023-10-10T10:00:00'); }); it('should prefer CreationDate over CreateDate', () => { diff --git a/server/src/services/metadata.service.ts b/server/src/services/metadata.service.ts index 7d3de76550..746f62a944 100644 --- a/server/src/services/metadata.service.ts +++ b/server/src/services/metadata.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { ContainerDirectoryItem, ExifDateTime, Tags } from 'exiftool-vendored'; import { Insertable } from 'kysely'; import _ from 'lodash'; -import { Duration } from 'luxon'; +import { DateTime, Duration } from 'luxon'; import { Stats } from 'node:fs'; import { constants } from 'node:fs/promises'; import { join, parse } from 'node:path'; @@ -236,8 +236,8 @@ export class MetadataService extends BaseService { latitude: number | null = null, longitude: number | null = null; if (this.hasGeo(exifTags)) { - latitude = exifTags.GPSLatitude; - longitude = exifTags.GPSLongitude; + latitude = Number(exifTags.GPSLatitude); + longitude = Number(exifTags.GPSLongitude); if (reverseGeocoding.enabled) { geo = await this.mapRepository.reverseGeocode({ latitude, longitude }); } @@ -447,7 +447,10 @@ export class MetadataService extends BaseService { * For RAW images in the CR2 or RAF format, the "ImageSize" value seems to be correct, * but ImageWidth and ImageHeight are not correct (they contain the dimensions of the preview image). */ - let [width, height] = exifTags.ImageSize?.split('x').map((dim) => Number.parseInt(dim) || undefined) || []; + let [width, height] = + exifTags.ImageSize?.toString() + ?.split('x') + ?.map((dim) => Number.parseInt(dim) || undefined) ?? []; if (!width || !height) { [width, height] = [exifTags.ImageWidth, exifTags.ImageHeight]; } @@ -863,40 +866,47 @@ export class MetadataService extends BaseService { this.logger.debug(`No timezone information found for asset ${asset.id}: ${asset.originalPath}`); } - let dateTimeOriginal = dateTime?.toDate(); - let localDateTime = dateTime?.toDateTime().setZone('UTC', { keepLocalTime: true }).toJSDate(); + let dateTimeOriginal = dateTime?.toDateTime(); + + // do not let JavaScript use local timezone + if (dateTimeOriginal && !dateTime?.hasZone) { + dateTimeOriginal = dateTimeOriginal.setZone('UTC', { keepLocalTime: true }); + } + + // align with whatever timeZone we chose + dateTimeOriginal = dateTimeOriginal?.setZone(timeZone ?? 'UTC'); + + // store as "local time" + let localDateTime = dateTimeOriginal?.setZone('UTC', { keepLocalTime: true }); + if (!localDateTime || !dateTimeOriginal) { // FileCreateDate is not available on linux, likely because exiftool hasn't integrated the statx syscall yet // birthtime is not available in Docker on macOS, so it appears as 0 - const earliestDate = new Date( + const earliestDate = DateTime.fromMillis( Math.min( asset.fileCreatedAt.getTime(), stats.birthtimeMs ? Math.min(stats.mtimeMs, stats.birthtimeMs) : stats.mtime.getTime(), ), ); this.logger.debug( - `No exif date time found, falling back on ${earliestDate.toISOString()}, earliest of file creation and modification for asset ${asset.id}: ${asset.originalPath}`, + `No exif date time found, falling back on ${earliestDate.toISO()}, earliest of file creation and modification for asset ${asset.id}: ${asset.originalPath}`, ); dateTimeOriginal = localDateTime = earliestDate; } - this.logger.verbose( - `Found local date time ${localDateTime.toISOString()} for asset ${asset.id}: ${asset.originalPath}`, - ); + this.logger.verbose(`Found local date time ${localDateTime.toISO()} for asset ${asset.id}: ${asset.originalPath}`); return { - dateTimeOriginal, timeZone, - localDateTime, + localDateTime: localDateTime.toJSDate(), + dateTimeOriginal: dateTimeOriginal.toJSDate(), }; } - private hasGeo(tags: ImmichTags): tags is ImmichTags & { GPSLatitude: number; GPSLongitude: number } { - return ( - tags.GPSLatitude !== undefined && - tags.GPSLongitude !== undefined && - (tags.GPSLatitude !== 0 || tags.GPSLatitude !== 0) - ); + private hasGeo(tags: ImmichTags) { + const lat = Number(tags.GPSLatitude); + const lng = Number(tags.GPSLongitude); + return !Number.isNaN(lat) && !Number.isNaN(lng) && (lat !== 0 || lng !== 0); } private getAutoStackId(tags: ImmichTags | null): string | null { diff --git a/server/src/services/notification-admin.service.spec.ts b/server/src/services/notification-admin.service.spec.ts index 4a747d41a3..c200897719 100644 --- a/server/src/services/notification-admin.service.spec.ts +++ b/server/src/services/notification-admin.service.spec.ts @@ -14,6 +14,7 @@ const smtpTransport = Object.freeze({ ignoreCert: false, host: 'localhost', port: 587, + secure: false, username: 'test', password: 'test', }, diff --git a/server/src/services/notification.service.spec.ts b/server/src/services/notification.service.spec.ts index 11c385b1e2..daa3f221ae 100644 --- a/server/src/services/notification.service.spec.ts +++ b/server/src/services/notification.service.spec.ts @@ -7,6 +7,7 @@ import { NotificationService } from 'src/services/notification.service'; import { INotifyAlbumUpdateJob } from 'src/types'; import { albumStub } from 'test/fixtures/album.stub'; import { assetStub } from 'test/fixtures/asset.stub'; +import { notificationStub } from 'test/fixtures/notification.stub'; import { userStub } from 'test/fixtures/user.stub'; import { newTestService, ServiceMocks } from 'test/utils'; @@ -39,6 +40,7 @@ const configs = { ignoreCert: false, host: 'localhost', port: 587, + secure: false, username: 'test', password: 'test', }, @@ -63,8 +65,8 @@ describe(NotificationService.name, () => { it('should emit client and server events', () => { const update = { oldConfig: defaults, newConfig: defaults }; expect(sut.onConfigUpdate(update)).toBeUndefined(); - expect(mocks.event.clientBroadcast).toHaveBeenCalledWith('on_config_update'); - expect(mocks.event.serverSend).toHaveBeenCalledWith('ConfigUpdate', update); + expect(mocks.websocket.clientBroadcast).toHaveBeenCalledWith('on_config_update'); + expect(mocks.websocket.serverSend).toHaveBeenCalledWith('ConfigUpdate', update); }); }); @@ -123,7 +125,7 @@ describe(NotificationService.name, () => { describe('onAssetHide', () => { it('should send connected clients an event', () => { sut.onAssetHide({ assetId: 'asset-id', userId: 'user-id' }); - expect(mocks.event.clientSend).toHaveBeenCalledWith('on_asset_hidden', 'user-id', 'asset-id'); + expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_asset_hidden', 'user-id', 'asset-id'); }); }); @@ -176,67 +178,67 @@ describe(NotificationService.name, () => { it('should send a on_session_delete client event', () => { vi.useFakeTimers(); sut.onSessionDelete({ sessionId: 'id' }); - expect(mocks.event.clientSend).not.toHaveBeenCalled(); + expect(mocks.websocket.clientSend).not.toHaveBeenCalled(); vi.advanceTimersByTime(500); - expect(mocks.event.clientSend).toHaveBeenCalledWith('on_session_delete', 'id', 'id'); + expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_session_delete', 'id', 'id'); }); }); describe('onAssetTrash', () => { - it('should send connected clients an event', () => { + it('should send connected clients an websocket', () => { sut.onAssetTrash({ assetId: 'asset-id', userId: 'user-id' }); - expect(mocks.event.clientSend).toHaveBeenCalledWith('on_asset_trash', 'user-id', ['asset-id']); + expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_asset_trash', 'user-id', ['asset-id']); }); }); describe('onAssetDelete', () => { it('should send connected clients an event', () => { sut.onAssetDelete({ assetId: 'asset-id', userId: 'user-id' }); - expect(mocks.event.clientSend).toHaveBeenCalledWith('on_asset_delete', 'user-id', 'asset-id'); + expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_asset_delete', 'user-id', 'asset-id'); }); }); describe('onAssetsTrash', () => { it('should send connected clients an event', () => { sut.onAssetsTrash({ assetIds: ['asset-id'], userId: 'user-id' }); - expect(mocks.event.clientSend).toHaveBeenCalledWith('on_asset_trash', 'user-id', ['asset-id']); + expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_asset_trash', 'user-id', ['asset-id']); }); }); describe('onAssetsRestore', () => { it('should send connected clients an event', () => { sut.onAssetsRestore({ assetIds: ['asset-id'], userId: 'user-id' }); - expect(mocks.event.clientSend).toHaveBeenCalledWith('on_asset_restore', 'user-id', ['asset-id']); + expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_asset_restore', 'user-id', ['asset-id']); }); }); describe('onStackCreate', () => { it('should send connected clients an event', () => { sut.onStackCreate({ stackId: 'stack-id', userId: 'user-id' }); - expect(mocks.event.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id'); + expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id'); }); }); describe('onStackUpdate', () => { it('should send connected clients an event', () => { sut.onStackUpdate({ stackId: 'stack-id', userId: 'user-id' }); - expect(mocks.event.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id'); + expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id'); }); }); describe('onStackDelete', () => { it('should send connected clients an event', () => { sut.onStackDelete({ stackId: 'stack-id', userId: 'user-id' }); - expect(mocks.event.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id'); + expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id'); }); }); describe('onStacksDelete', () => { it('should send connected clients an event', () => { sut.onStacksDelete({ stackIds: ['stack-id'], userId: 'user-id' }); - expect(mocks.event.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id'); + expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id'); }); }); @@ -282,6 +284,7 @@ describe(NotificationService.name, () => { }, ], }); + mocks.notification.create.mockResolvedValue(notificationStub.albumEvent); await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.Skipped); }); @@ -297,6 +300,7 @@ describe(NotificationService.name, () => { }, ], }); + mocks.notification.create.mockResolvedValue(notificationStub.albumEvent); await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.Skipped); }); @@ -313,6 +317,7 @@ describe(NotificationService.name, () => { ], }); mocks.systemMetadata.get.mockResolvedValue({ server: {} }); + mocks.notification.create.mockResolvedValue(notificationStub.albumEvent); mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' }); await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.Success); @@ -334,6 +339,7 @@ describe(NotificationService.name, () => { ], }); mocks.systemMetadata.get.mockResolvedValue({ server: {} }); + mocks.notification.create.mockResolvedValue(notificationStub.albumEvent); mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' }); mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([]); @@ -363,6 +369,7 @@ describe(NotificationService.name, () => { ], }); mocks.systemMetadata.get.mockResolvedValue({ server: {} }); + mocks.notification.create.mockResolvedValue(notificationStub.albumEvent); mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' }); mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([ { id: '1', type: AssetFileType.Thumbnail, path: 'path-to-thumb.jpg' }, @@ -394,6 +401,7 @@ describe(NotificationService.name, () => { ], }); mocks.systemMetadata.get.mockResolvedValue({ server: {} }); + mocks.notification.create.mockResolvedValue(notificationStub.albumEvent); mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' }); mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([assetStub.image.files[2]]); @@ -431,6 +439,7 @@ describe(NotificationService.name, () => { albumUsers: [{ user: { id: userStub.user1.id } } as AlbumUser], }); mocks.user.get.mockResolvedValueOnce(userStub.user1); + mocks.notification.create.mockResolvedValue(notificationStub.albumEvent); mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' }); mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([]); @@ -453,6 +462,7 @@ describe(NotificationService.name, () => { }, ], }); + mocks.notification.create.mockResolvedValue(notificationStub.albumEvent); mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' }); mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([]); @@ -475,6 +485,7 @@ describe(NotificationService.name, () => { }, ], }); + mocks.notification.create.mockResolvedValue(notificationStub.albumEvent); mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' }); mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([]); @@ -489,6 +500,7 @@ describe(NotificationService.name, () => { albumUsers: [{ user: { id: userStub.user1.id } } as AlbumUser], }); mocks.user.get.mockResolvedValue(userStub.user1); + mocks.notification.create.mockResolvedValue(notificationStub.albumEvent); mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' }); mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([]); diff --git a/server/src/services/notification.service.ts b/server/src/services/notification.service.ts index 91a043d405..8276f141a0 100644 --- a/server/src/services/notification.service.ts +++ b/server/src/services/notification.service.ts @@ -1,5 +1,6 @@ import { BadRequestException, Injectable } from '@nestjs/common'; import { OnEvent, OnJob } from 'src/decorators'; +import { MapAlbumDto } from 'src/dtos/album.dto'; import { mapAsset } from 'src/dtos/asset-response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { @@ -77,8 +78,8 @@ export class NotificationService extends BaseService { await this.notificationRepository.cleanup(); } - @OnEvent({ name: 'JobFailed' }) - async onJobFailed({ job, error }: ArgOf<'JobFailed'>) { + @OnEvent({ name: 'JobError' }) + async onJobError({ job, error }: ArgOf<'JobError'>) { const admin = await this.userRepository.getAdmin(); if (!admin) { return; @@ -97,7 +98,7 @@ export class NotificationService extends BaseService { description: `Job ${[job.name]} failed with error: ${errorMessage}`, }); - this.eventRepository.clientSend('on_notification', admin.id, mapNotification(item)); + this.websocketRepository.clientSend('on_notification', admin.id, mapNotification(item)); break; } @@ -109,8 +110,8 @@ export class NotificationService extends BaseService { @OnEvent({ name: 'ConfigUpdate' }) onConfigUpdate({ oldConfig, newConfig }: ArgOf<'ConfigUpdate'>) { - this.eventRepository.clientBroadcast('on_config_update'); - this.eventRepository.serverSend('ConfigUpdate', { oldConfig, newConfig }); + this.websocketRepository.clientBroadcast('on_config_update'); + this.websocketRepository.serverSend('ConfigUpdate', { oldConfig, newConfig }); } @OnEvent({ name: 'ConfigValidate', priority: -100 }) @@ -130,7 +131,7 @@ export class NotificationService extends BaseService { @OnEvent({ name: 'AssetHide' }) onAssetHide({ assetId, userId }: ArgOf<'AssetHide'>) { - this.eventRepository.clientSend('on_asset_hidden', userId, assetId); + this.websocketRepository.clientSend('on_asset_hidden', userId, assetId); } @OnEvent({ name: 'AssetShow' }) @@ -140,17 +141,17 @@ export class NotificationService extends BaseService { @OnEvent({ name: 'AssetTrash' }) onAssetTrash({ assetId, userId }: ArgOf<'AssetTrash'>) { - this.eventRepository.clientSend('on_asset_trash', userId, [assetId]); + this.websocketRepository.clientSend('on_asset_trash', userId, [assetId]); } @OnEvent({ name: 'AssetDelete' }) onAssetDelete({ assetId, userId }: ArgOf<'AssetDelete'>) { - this.eventRepository.clientSend('on_asset_delete', userId, assetId); + this.websocketRepository.clientSend('on_asset_delete', userId, assetId); } @OnEvent({ name: 'AssetTrashAll' }) onAssetsTrash({ assetIds, userId }: ArgOf<'AssetTrashAll'>) { - this.eventRepository.clientSend('on_asset_trash', userId, assetIds); + this.websocketRepository.clientSend('on_asset_trash', userId, assetIds); } @OnEvent({ name: 'AssetMetadataExtracted' }) @@ -161,33 +162,37 @@ export class NotificationService extends BaseService { const [asset] = await this.assetRepository.getByIdsWithAllRelationsButStacks([assetId]); if (asset) { - this.eventRepository.clientSend('on_asset_update', userId, mapAsset(asset)); + this.websocketRepository.clientSend( + 'on_asset_update', + userId, + mapAsset(asset, { auth: { user: { id: userId } } as AuthDto }), + ); } } @OnEvent({ name: 'AssetRestoreAll' }) onAssetsRestore({ assetIds, userId }: ArgOf<'AssetRestoreAll'>) { - this.eventRepository.clientSend('on_asset_restore', userId, assetIds); + this.websocketRepository.clientSend('on_asset_restore', userId, assetIds); } @OnEvent({ name: 'StackCreate' }) onStackCreate({ userId }: ArgOf<'StackCreate'>) { - this.eventRepository.clientSend('on_asset_stack_update', userId); + this.websocketRepository.clientSend('on_asset_stack_update', userId); } @OnEvent({ name: 'StackUpdate' }) onStackUpdate({ userId }: ArgOf<'StackUpdate'>) { - this.eventRepository.clientSend('on_asset_stack_update', userId); + this.websocketRepository.clientSend('on_asset_stack_update', userId); } @OnEvent({ name: 'StackDelete' }) onStackDelete({ userId }: ArgOf<'StackDelete'>) { - this.eventRepository.clientSend('on_asset_stack_update', userId); + this.websocketRepository.clientSend('on_asset_stack_update', userId); } @OnEvent({ name: 'StackDeleteAll' }) onStacksDelete({ userId }: ArgOf<'StackDeleteAll'>) { - this.eventRepository.clientSend('on_asset_stack_update', userId); + this.websocketRepository.clientSend('on_asset_stack_update', userId); } @OnEvent({ name: 'UserSignup' }) @@ -197,6 +202,11 @@ export class NotificationService extends BaseService { } } + @OnEvent({ name: 'UserDelete' }) + onUserDelete({ id }: ArgOf<'UserDelete'>) { + this.websocketRepository.clientBroadcast('on_user_delete', id); + } + @OnEvent({ name: 'AlbumUpdate' }) async onAlbumUpdate({ id, recipientId }: ArgOf<'AlbumUpdate'>) { await this.jobRepository.removeJob(JobName.NotifyAlbumUpdate, `${id}/${recipientId}`); @@ -214,7 +224,7 @@ export class NotificationService extends BaseService { @OnEvent({ name: 'SessionDelete' }) onSessionDelete({ sessionId }: ArgOf<'SessionDelete'>) { // after the response is sent - setTimeout(() => this.eventRepository.clientSend('on_session_delete', sessionId, sessionId), 500); + setTimeout(() => this.websocketRepository.clientSend('on_session_delete', sessionId, sessionId), 500); } async sendTestEmail(id: string, dto: SystemConfigSmtpDto, tempTemplate?: string) { @@ -295,6 +305,8 @@ export class NotificationService extends BaseService { return JobStatus.Skipped; } + await this.sendAlbumLocalNotification(album, recipientId, NotificationType.AlbumInvite, album.owner.name); + const { emailNotifications } = getPreferences(recipient.metadata); if (!emailNotifications.enabled || !emailNotifications.albumInvite) { @@ -344,6 +356,8 @@ export class NotificationService extends BaseService { return JobStatus.Skipped; } + await this.sendAlbumLocalNotification(album, recipientId, NotificationType.AlbumUpdate); + const attachment = await this.getAlbumThumbnailAttachment(album); const { server, templates } = await this.getConfig({ withCache: false }); @@ -431,4 +445,25 @@ export class NotificationService extends BaseService { cid: 'album-thumbnail', }; } + + private async sendAlbumLocalNotification( + album: MapAlbumDto, + userId: string, + type: NotificationType.AlbumInvite | NotificationType.AlbumUpdate, + senderName?: string, + ) { + const isInvite = type === NotificationType.AlbumInvite; + const item = await this.notificationRepository.create({ + userId, + type, + level: isInvite ? NotificationLevel.Success : NotificationLevel.Info, + title: isInvite ? 'Shared Album Invitation' : 'Shared Album Update', + description: isInvite + ? `${senderName} shared an album (${album.albumName}) with you` + : `New media has been added to the album (${album.albumName})`, + data: JSON.stringify({ albumId: album.id }), + }); + + this.websocketRepository.clientSend('on_notification', userId, mapNotification(item)); + } } diff --git a/server/src/services/ocr.service.spec.ts b/server/src/services/ocr.service.spec.ts new file mode 100644 index 0000000000..6eedba1a5f --- /dev/null +++ b/server/src/services/ocr.service.spec.ts @@ -0,0 +1,177 @@ +import { AssetVisibility, ImmichWorker, JobName, JobStatus } from 'src/enum'; +import { OcrService } from 'src/services/ocr.service'; +import { assetStub } from 'test/fixtures/asset.stub'; +import { systemConfigStub } from 'test/fixtures/system-config.stub'; +import { makeStream, newTestService, ServiceMocks } from 'test/utils'; + +describe(OcrService.name, () => { + let sut: OcrService; + let mocks: ServiceMocks; + + beforeEach(() => { + ({ sut, mocks } = newTestService(OcrService)); + + mocks.config.getWorker.mockReturnValue(ImmichWorker.Microservices); + }); + + it('should work', () => { + expect(sut).toBeDefined(); + }); + + describe('handleQueueOcr', () => { + it('should do nothing if machine learning is disabled', async () => { + mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.machineLearningDisabled); + + await sut.handleQueueOcr({ force: false }); + + expect(mocks.database.setDimensionSize).not.toHaveBeenCalled(); + }); + + it('should queue the assets without ocr', async () => { + mocks.assetJob.streamForOcrJob.mockReturnValue(makeStream([assetStub.image])); + + await sut.handleQueueOcr({ force: false }); + + expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.Ocr, data: { id: assetStub.image.id } }]); + expect(mocks.assetJob.streamForOcrJob).toHaveBeenCalledWith(false); + }); + + it('should queue all the assets', async () => { + mocks.assetJob.streamForOcrJob.mockReturnValue(makeStream([assetStub.image])); + + await sut.handleQueueOcr({ force: true }); + + expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.Ocr, data: { id: assetStub.image.id } }]); + expect(mocks.assetJob.streamForOcrJob).toHaveBeenCalledWith(true); + }); + }); + + describe('handleOcr', () => { + it('should do nothing if machine learning is disabled', async () => { + mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.machineLearningDisabled); + + expect(await sut.handleOcr({ id: '123' })).toEqual(JobStatus.Skipped); + + expect(mocks.asset.getByIds).not.toHaveBeenCalled(); + expect(mocks.machineLearning.encodeImage).not.toHaveBeenCalled(); + }); + + it('should skip assets without a resize path', async () => { + mocks.assetJob.getForOcr.mockResolvedValue({ visibility: AssetVisibility.Timeline, previewFile: null }); + + expect(await sut.handleOcr({ id: assetStub.noResizePath.id })).toEqual(JobStatus.Failed); + + expect(mocks.ocr.upsert).not.toHaveBeenCalled(); + expect(mocks.machineLearning.ocr).not.toHaveBeenCalled(); + }); + + it('should save the returned objects', async () => { + mocks.machineLearning.ocr.mockResolvedValue({ + box: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160], + boxScore: [0.9, 0.8], + text: ['One Two Three', 'Four Five'], + textScore: [0.95, 0.85], + }); + mocks.assetJob.getForOcr.mockResolvedValue({ + visibility: AssetVisibility.Timeline, + previewFile: assetStub.image.files[1].path, + }); + + expect(await sut.handleOcr({ id: assetStub.image.id })).toEqual(JobStatus.Success); + + expect(mocks.machineLearning.ocr).toHaveBeenCalledWith( + '/uploads/user-id/thumbs/path.jpg', + expect.objectContaining({ + modelName: 'PP-OCRv5_mobile', + minDetectionScore: 0.5, + minRecognitionScore: 0.8, + maxResolution: 736, + }), + ); + expect(mocks.ocr.upsert).toHaveBeenCalledWith(assetStub.image.id, [ + { + assetId: assetStub.image.id, + boxScore: 0.9, + text: 'One Two Three', + textScore: 0.95, + x1: 10, + y1: 20, + x2: 30, + y2: 40, + x3: 50, + y3: 60, + x4: 70, + y4: 80, + }, + { + assetId: assetStub.image.id, + boxScore: 0.8, + text: 'Four Five', + textScore: 0.85, + x1: 90, + y1: 100, + x2: 110, + y2: 120, + x3: 130, + y3: 140, + x4: 150, + y4: 160, + }, + ]); + }); + + it('should apply config settings', async () => { + mocks.systemMetadata.get.mockResolvedValue({ + machineLearning: { + enabled: true, + ocr: { + modelName: 'PP-OCRv5_server', + enabled: true, + minDetectionScore: 0.8, + minRecognitionScore: 0.9, + maxResolution: 1500, + }, + }, + }); + mocks.machineLearning.ocr.mockResolvedValue({ box: [], boxScore: [], text: [], textScore: [] }); + mocks.assetJob.getForOcr.mockResolvedValue({ + visibility: AssetVisibility.Timeline, + previewFile: assetStub.image.files[1].path, + }); + + expect(await sut.handleOcr({ id: assetStub.image.id })).toEqual(JobStatus.Success); + + expect(mocks.machineLearning.ocr).toHaveBeenCalledWith( + '/uploads/user-id/thumbs/path.jpg', + expect.objectContaining({ + modelName: 'PP-OCRv5_server', + minDetectionScore: 0.8, + minRecognitionScore: 0.9, + maxResolution: 1500, + }), + ); + expect(mocks.ocr.upsert).toHaveBeenCalledWith(assetStub.image.id, []); + }); + + it('should skip invisible assets', async () => { + mocks.assetJob.getForOcr.mockResolvedValue({ + visibility: AssetVisibility.Hidden, + previewFile: assetStub.image.files[1].path, + }); + + expect(await sut.handleOcr({ id: assetStub.livePhotoMotionAsset.id })).toEqual(JobStatus.Skipped); + + expect(mocks.machineLearning.ocr).not.toHaveBeenCalled(); + expect(mocks.ocr.upsert).not.toHaveBeenCalled(); + }); + + it('should fail if asset could not be found', async () => { + mocks.assetJob.getForOcr.mockResolvedValue(void 0); + + expect(await sut.handleOcr({ id: assetStub.image.id })).toEqual(JobStatus.Failed); + + expect(mocks.machineLearning.ocr).not.toHaveBeenCalled(); + expect(mocks.ocr.upsert).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/server/src/services/ocr.service.ts b/server/src/services/ocr.service.ts new file mode 100644 index 0000000000..cba57e5bc7 --- /dev/null +++ b/server/src/services/ocr.service.ts @@ -0,0 +1,86 @@ +import { Injectable } from '@nestjs/common'; +import { JOBS_ASSET_PAGINATION_SIZE } from 'src/constants'; +import { OnJob } from 'src/decorators'; +import { AssetVisibility, JobName, JobStatus, QueueName } from 'src/enum'; +import { OCR } from 'src/repositories/machine-learning.repository'; +import { BaseService } from 'src/services/base.service'; +import { JobItem, JobOf } from 'src/types'; +import { isOcrEnabled } from 'src/utils/misc'; + +@Injectable() +export class OcrService extends BaseService { + @OnJob({ name: JobName.OcrQueueAll, queue: QueueName.Ocr }) + async handleQueueOcr({ force }: JobOf): Promise { + const { machineLearning } = await this.getConfig({ withCache: false }); + if (!isOcrEnabled(machineLearning)) { + return JobStatus.Skipped; + } + + if (force) { + await this.ocrRepository.deleteAll(); + } + + let jobs: JobItem[] = []; + const assets = this.assetJobRepository.streamForOcrJob(force); + + for await (const asset of assets) { + jobs.push({ name: JobName.Ocr, data: { id: asset.id } }); + + if (jobs.length >= JOBS_ASSET_PAGINATION_SIZE) { + await this.jobRepository.queueAll(jobs); + jobs = []; + } + } + + await this.jobRepository.queueAll(jobs); + return JobStatus.Success; + } + + @OnJob({ name: JobName.Ocr, queue: QueueName.Ocr }) + async handleOcr({ id }: JobOf): Promise { + const { machineLearning } = await this.getConfig({ withCache: true }); + if (!isOcrEnabled(machineLearning)) { + return JobStatus.Skipped; + } + + const asset = await this.assetJobRepository.getForOcr(id); + if (!asset || !asset.previewFile) { + return JobStatus.Failed; + } + + if (asset.visibility === AssetVisibility.Hidden) { + return JobStatus.Skipped; + } + + const ocrResults = await this.machineLearningRepository.ocr(asset.previewFile, machineLearning.ocr); + + await this.ocrRepository.upsert(id, this.parseOcrResults(id, ocrResults)); + + await this.assetRepository.upsertJobStatus({ assetId: id, ocrAt: new Date() }); + + this.logger.debug(`Processed ${ocrResults.text.length} OCR result(s) for ${id}`); + return JobStatus.Success; + } + + private parseOcrResults(id: string, { box, boxScore, text, textScore }: OCR) { + const ocrDataList = []; + for (let i = 0; i < text.length; i++) { + const boxOffset = i * 8; + ocrDataList.push({ + assetId: id, + x1: box[boxOffset], + y1: box[boxOffset + 1], + x2: box[boxOffset + 2], + y2: box[boxOffset + 3], + x3: box[boxOffset + 4], + y3: box[boxOffset + 5], + x4: box[boxOffset + 6], + y4: box[boxOffset + 7], + boxScore: boxScore[i], + textScore: textScore[i], + text: text[i], + }); + } + return ocrDataList; + } +} diff --git a/server/src/services/plugin-host.functions.ts b/server/src/services/plugin-host.functions.ts new file mode 100644 index 0000000000..50b1052b54 --- /dev/null +++ b/server/src/services/plugin-host.functions.ts @@ -0,0 +1,120 @@ +import { CurrentPlugin } from '@extism/extism'; +import { UnauthorizedException } from '@nestjs/common'; +import { Updateable } from 'kysely'; +import { Permission } from 'src/enum'; +import { AccessRepository } from 'src/repositories/access.repository'; +import { AlbumRepository } from 'src/repositories/album.repository'; +import { AssetRepository } from 'src/repositories/asset.repository'; +import { CryptoRepository } from 'src/repositories/crypto.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; +import { AssetTable } from 'src/schema/tables/asset.table'; +import { requireAccess } from 'src/utils/access'; + +/** + * Plugin host functions that are exposed to WASM plugins via Extism. + * These functions allow plugins to interact with the Immich system. + */ +export class PluginHostFunctions { + constructor( + private assetRepository: AssetRepository, + private albumRepository: AlbumRepository, + private accessRepository: AccessRepository, + private cryptoRepository: CryptoRepository, + private logger: LoggingRepository, + private pluginJwtSecret: string, + ) {} + + /** + * Creates Extism host function bindings for the plugin. + * These are the functions that WASM plugins can call. + */ + getHostFunctions() { + return { + 'extism:host/user': { + updateAsset: (cp: CurrentPlugin, offs: bigint) => this.handleUpdateAsset(cp, offs), + addAssetToAlbum: (cp: CurrentPlugin, offs: bigint) => this.handleAddAssetToAlbum(cp, offs), + }, + }; + } + + /** + * Host function wrapper for updateAsset. + * Reads the input from the plugin, parses it, and calls the actual update function. + */ + private async handleUpdateAsset(cp: CurrentPlugin, offs: bigint) { + const input = JSON.parse(cp.read(offs)!.text()); + await this.updateAsset(input); + } + + /** + * Host function wrapper for addAssetToAlbum. + * Reads the input from the plugin, parses it, and calls the actual add function. + */ + private async handleAddAssetToAlbum(cp: CurrentPlugin, offs: bigint) { + const input = JSON.parse(cp.read(offs)!.text()); + await this.addAssetToAlbum(input); + } + + /** + * Validates the JWT token and returns the auth context. + */ + private validateToken(authToken: string): { userId: string } { + try { + const auth = this.cryptoRepository.verifyJwt<{ userId: string }>(authToken, this.pluginJwtSecret); + if (!auth.userId) { + throw new UnauthorizedException('Invalid token: missing userId'); + } + return auth; + } catch (error) { + this.logger.error('Token validation failed:', error); + throw new UnauthorizedException('Invalid token'); + } + } + + /** + * Updates an asset with the given properties. + */ + async updateAsset(input: { authToken: string } & Updateable & { id: string }) { + const { authToken, id, ...assetData } = input; + + // Validate token + const auth = this.validateToken(authToken); + + // Check access to the asset + await requireAccess(this.accessRepository, { + auth: { user: { id: auth.userId } } as any, + permission: Permission.AssetUpdate, + ids: [id], + }); + + this.logger.log(`Updating asset ${id} -- ${JSON.stringify(assetData)}`); + await this.assetRepository.update({ id, ...assetData }); + } + + /** + * Adds an asset to an album. + */ + async addAssetToAlbum(input: { authToken: string; assetId: string; albumId: string }) { + const { authToken, assetId, albumId } = input; + + // Validate token + const auth = this.validateToken(authToken); + + // Check access to both the asset and the album + await requireAccess(this.accessRepository, { + auth: { user: { id: auth.userId } } as any, + permission: Permission.AssetRead, + ids: [assetId], + }); + + await requireAccess(this.accessRepository, { + auth: { user: { id: auth.userId } } as any, + permission: Permission.AlbumUpdate, + ids: [albumId], + }); + + this.logger.log(`Adding asset ${assetId} to album ${albumId}`); + await this.albumRepository.addAssetIds(albumId, [assetId]); + return 0; + } +} diff --git a/server/src/services/plugin.service.ts b/server/src/services/plugin.service.ts new file mode 100644 index 0000000000..28d1ac56ca --- /dev/null +++ b/server/src/services/plugin.service.ts @@ -0,0 +1,317 @@ +import { Plugin as ExtismPlugin, newPlugin } from '@extism/extism'; +import { BadRequestException, Injectable } from '@nestjs/common'; +import { plainToInstance } from 'class-transformer'; +import { validateOrReject } from 'class-validator'; +import { join } from 'node:path'; +import { Asset, WorkflowAction, WorkflowFilter } from 'src/database'; +import { OnEvent, OnJob } from 'src/decorators'; +import { PluginManifestDto } from 'src/dtos/plugin-manifest.dto'; +import { mapPlugin, PluginResponseDto } from 'src/dtos/plugin.dto'; +import { JobName, JobStatus, PluginTriggerType, QueueName } from 'src/enum'; +import { ArgOf } from 'src/repositories/event.repository'; +import { BaseService } from 'src/services/base.service'; +import { PluginHostFunctions } from 'src/services/plugin-host.functions'; +import { IWorkflowJob, JobItem, JobOf, WorkflowData } from 'src/types'; + +interface WorkflowContext { + authToken: string; + asset: Asset; +} + +interface PluginInput { + authToken: string; + config: T; + data: { + asset: Asset; + }; +} + +@Injectable() +export class PluginService extends BaseService { + private pluginJwtSecret!: string; + private loadedPlugins: Map = new Map(); + private hostFunctions!: PluginHostFunctions; + + @OnEvent({ name: 'AppBootstrap' }) + async onBootstrap() { + this.pluginJwtSecret = this.cryptoRepository.randomBytesAsText(32); + + await this.loadPluginsFromManifests(); + + this.hostFunctions = new PluginHostFunctions( + this.assetRepository, + this.albumRepository, + this.accessRepository, + this.cryptoRepository, + this.logger, + this.pluginJwtSecret, + ); + + await this.loadPlugins(); + } + + // + // CRUD operations for plugins + // + async getAll(): Promise { + const plugins = await this.pluginRepository.getAllPlugins(); + return plugins.map((plugin) => mapPlugin(plugin)); + } + + async get(id: string): Promise { + const plugin = await this.pluginRepository.getPlugin(id); + if (!plugin) { + throw new BadRequestException('Plugin not found'); + } + return mapPlugin(plugin); + } + + /////////////////////////////////////////// + // Plugin Loader + ////////////////////////////////////////// + async loadPluginsFromManifests(): Promise { + // Load core plugin + const { resourcePaths, plugins } = this.configRepository.getEnv(); + const coreManifestPath = `${resourcePaths.corePlugin}/manifest.json`; + + const coreManifest = await this.readAndValidateManifest(coreManifestPath); + await this.loadPluginToDatabase(coreManifest, resourcePaths.corePlugin); + + this.logger.log(`Successfully processed core plugin: ${coreManifest.name} (version ${coreManifest.version})`); + + // Load external plugins + if (plugins.enabled && plugins.installFolder) { + await this.loadExternalPlugins(plugins.installFolder); + } + } + + private async loadExternalPlugins(installFolder: string): Promise { + try { + const entries = await this.pluginRepository.readDirectory(installFolder); + + for (const entry of entries) { + if (!entry.isDirectory()) { + continue; + } + + const pluginFolder = join(installFolder, entry.name); + const manifestPath = join(pluginFolder, 'manifest.json'); + try { + const manifest = await this.readAndValidateManifest(manifestPath); + await this.loadPluginToDatabase(manifest, pluginFolder); + + this.logger.log(`Successfully processed external plugin: ${manifest.name} (version ${manifest.version})`); + } catch (error) { + this.logger.warn(`Failed to load external plugin from ${manifestPath}:`, error); + } + } + } catch (error) { + this.logger.error(`Failed to scan external plugins folder ${installFolder}:`, error); + } + } + + private async loadPluginToDatabase(manifest: PluginManifestDto, basePath: string): Promise { + const currentPlugin = await this.pluginRepository.getPluginByName(manifest.name); + if (currentPlugin != null && currentPlugin.version === manifest.version) { + this.logger.log(`Plugin ${manifest.name} is up to date (version ${manifest.version}). Skipping`); + return; + } + + const { plugin, filters, actions } = await this.pluginRepository.loadPlugin(manifest, basePath); + + this.logger.log(`Upserted plugin: ${plugin.name} (ID: ${plugin.id}, version: ${plugin.version})`); + + for (const filter of filters) { + this.logger.log(`Upserted plugin filter: ${filter.methodName} (ID: ${filter.id})`); + } + + for (const action of actions) { + this.logger.log(`Upserted plugin action: ${action.methodName} (ID: ${action.id})`); + } + } + + private async readAndValidateManifest(manifestPath: string): Promise { + const content = await this.storageRepository.readTextFile(manifestPath); + const manifestData = JSON.parse(content); + const manifest = plainToInstance(PluginManifestDto, manifestData); + + await validateOrReject(manifest, { + whitelist: true, + forbidNonWhitelisted: true, + }); + + return manifest; + } + + /////////////////////////////////////////// + // Plugin Execution + /////////////////////////////////////////// + private async loadPlugins() { + const plugins = await this.pluginRepository.getAllPlugins(); + for (const plugin of plugins) { + try { + this.logger.debug(`Loading plugin: ${plugin.name} from ${plugin.wasmPath}`); + + const extismPlugin = await newPlugin(plugin.wasmPath, { + useWasi: true, + functions: this.hostFunctions.getHostFunctions(), + }); + + this.loadedPlugins.set(plugin.id, extismPlugin); + this.logger.log(`Successfully loaded plugin: ${plugin.name}`); + } catch (error) { + this.logger.error(`Failed to load plugin ${plugin.name}:`, error); + } + } + } + + @OnEvent({ name: 'AssetCreate' }) + async handleAssetCreate({ asset }: ArgOf<'AssetCreate'>) { + await this.handleTrigger(PluginTriggerType.AssetCreate, { + ownerId: asset.ownerId, + event: { userId: asset.ownerId, asset }, + }); + } + + private async handleTrigger( + triggerType: T, + params: { ownerId: string; event: WorkflowData[T] }, + ): Promise { + const workflows = await this.workflowRepository.getWorkflowByOwnerAndTrigger(params.ownerId, triggerType); + if (workflows.length === 0) { + return; + } + + const jobs: JobItem[] = workflows.map((workflow) => ({ + name: JobName.WorkflowRun, + data: { + id: workflow.id, + type: triggerType, + event: params.event, + } as IWorkflowJob, + })); + + await this.jobRepository.queueAll(jobs); + this.logger.debug(`Queued ${jobs.length} workflow execution jobs for trigger ${triggerType}`); + } + + @OnJob({ name: JobName.WorkflowRun, queue: QueueName.Workflow }) + async handleWorkflowRun({ id: workflowId, type, event }: JobOf): Promise { + try { + const workflow = await this.workflowRepository.getWorkflow(workflowId); + if (!workflow) { + this.logger.error(`Workflow ${workflowId} not found`); + return JobStatus.Failed; + } + + const workflowFilters = await this.workflowRepository.getFilters(workflowId); + const workflowActions = await this.workflowRepository.getActions(workflowId); + + switch (type) { + case PluginTriggerType.AssetCreate: { + const data = event as WorkflowData[PluginTriggerType.AssetCreate]; + const asset = data.asset; + + const authToken = this.cryptoRepository.signJwt({ userId: data.userId }, this.pluginJwtSecret); + + const context = { + authToken, + asset, + }; + + const filtersPassed = await this.executeFilters(workflowFilters, context); + if (!filtersPassed) { + return JobStatus.Skipped; + } + + await this.executeActions(workflowActions, context); + this.logger.debug(`Workflow ${workflowId} executed successfully`); + return JobStatus.Success; + } + + case PluginTriggerType.PersonRecognized: { + this.logger.error('unimplemented'); + return JobStatus.Skipped; + } + + default: { + this.logger.error(`Unknown workflow trigger type: ${type}`); + return JobStatus.Failed; + } + } + } catch (error) { + this.logger.error(`Error executing workflow ${workflowId}:`, error); + return JobStatus.Failed; + } + } + + private async executeFilters(workflowFilters: WorkflowFilter[], context: WorkflowContext): Promise { + for (const workflowFilter of workflowFilters) { + const filter = await this.pluginRepository.getFilter(workflowFilter.filterId); + if (!filter) { + this.logger.error(`Filter ${workflowFilter.filterId} not found`); + return false; + } + + const pluginInstance = this.loadedPlugins.get(filter.pluginId); + if (!pluginInstance) { + this.logger.error(`Plugin ${filter.pluginId} not loaded`); + return false; + } + + const filterInput: PluginInput = { + authToken: context.authToken, + config: workflowFilter.filterConfig, + data: { + asset: context.asset, + }, + }; + + this.logger.debug(`Calling filter ${filter.methodName} with input: ${JSON.stringify(filterInput)}`); + + const filterResult = await pluginInstance.call( + filter.methodName, + new TextEncoder().encode(JSON.stringify(filterInput)), + ); + + if (!filterResult) { + this.logger.error(`Filter ${filter.methodName} returned null`); + return false; + } + + const result = JSON.parse(filterResult.text()); + if (result.passed === false) { + this.logger.debug(`Filter ${filter.methodName} returned false, stopping workflow execution`); + return false; + } + } + + return true; + } + + private async executeActions(workflowActions: WorkflowAction[], context: WorkflowContext): Promise { + for (const workflowAction of workflowActions) { + const action = await this.pluginRepository.getAction(workflowAction.actionId); + if (!action) { + throw new Error(`Action ${workflowAction.actionId} not found`); + } + + const pluginInstance = this.loadedPlugins.get(action.pluginId); + if (!pluginInstance) { + throw new Error(`Plugin ${action.pluginId} not loaded`); + } + + const actionInput: PluginInput = { + authToken: context.authToken, + config: workflowAction.actionConfig, + data: { + asset: context.asset, + }, + }; + + this.logger.debug(`Calling action ${action.methodName} with input: ${JSON.stringify(actionInput)}`); + + await pluginInstance.call(action.methodName, JSON.stringify(actionInput)); + } + } +} diff --git a/server/src/services/queue.service.spec.ts b/server/src/services/queue.service.spec.ts new file mode 100644 index 0000000000..5dce9476e2 --- /dev/null +++ b/server/src/services/queue.service.spec.ts @@ -0,0 +1,224 @@ +import { BadRequestException } from '@nestjs/common'; +import { defaults, SystemConfig } from 'src/config'; +import { ImmichWorker, JobName, QueueCommand, QueueName } from 'src/enum'; +import { QueueService } from 'src/services/queue.service'; +import { newTestService, ServiceMocks } from 'test/utils'; + +describe(QueueService.name, () => { + let sut: QueueService; + let mocks: ServiceMocks; + + beforeEach(() => { + ({ sut, mocks } = newTestService(QueueService)); + + mocks.config.getWorker.mockReturnValue(ImmichWorker.Microservices); + }); + + it('should work', () => { + expect(sut).toBeDefined(); + }); + + describe('onConfigUpdate', () => { + it('should update concurrency', () => { + sut.onConfigUpdate({ newConfig: defaults, oldConfig: {} as SystemConfig }); + + expect(mocks.job.setConcurrency).toHaveBeenCalledTimes(17); + expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(5, QueueName.FacialRecognition, 1); + expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(7, QueueName.DuplicateDetection, 1); + expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(8, QueueName.BackgroundTask, 5); + expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(9, QueueName.StorageTemplateMigration, 1); + }); + }); + + describe('handleNightlyJobs', () => { + it('should run the scheduled jobs', async () => { + await sut.handleNightlyJobs(); + + expect(mocks.job.queueAll).toHaveBeenCalledWith([ + { name: JobName.AssetDeleteCheck }, + { name: JobName.UserDeleteCheck }, + { name: JobName.PersonCleanup }, + { name: JobName.MemoryCleanup }, + { name: JobName.SessionCleanup }, + { name: JobName.AuditTableCleanup }, + { name: JobName.AuditLogCleanup }, + { name: JobName.MemoryGenerate }, + { name: JobName.UserSyncUsage }, + { name: JobName.AssetGenerateThumbnailsQueueAll, data: { force: false } }, + { name: JobName.FacialRecognitionQueueAll, data: { force: false, nightly: true } }, + ]); + }); + }); + + describe('getAllJobStatus', () => { + it('should get all job statuses', async () => { + mocks.job.getJobCounts.mockResolvedValue({ + active: 1, + completed: 1, + failed: 1, + delayed: 1, + waiting: 1, + paused: 1, + }); + mocks.job.getQueueStatus.mockResolvedValue({ + isActive: true, + isPaused: true, + }); + + const expectedJobStatus = { + jobCounts: { + active: 1, + completed: 1, + delayed: 1, + failed: 1, + waiting: 1, + paused: 1, + }, + queueStatus: { + isActive: true, + isPaused: true, + }, + }; + + await expect(sut.getAll()).resolves.toEqual({ + [QueueName.BackgroundTask]: expectedJobStatus, + [QueueName.DuplicateDetection]: expectedJobStatus, + [QueueName.SmartSearch]: expectedJobStatus, + [QueueName.MetadataExtraction]: expectedJobStatus, + [QueueName.Search]: expectedJobStatus, + [QueueName.StorageTemplateMigration]: expectedJobStatus, + [QueueName.Migration]: expectedJobStatus, + [QueueName.ThumbnailGeneration]: expectedJobStatus, + [QueueName.VideoConversion]: expectedJobStatus, + [QueueName.FaceDetection]: expectedJobStatus, + [QueueName.FacialRecognition]: expectedJobStatus, + [QueueName.Sidecar]: expectedJobStatus, + [QueueName.Library]: expectedJobStatus, + [QueueName.Notification]: expectedJobStatus, + [QueueName.BackupDatabase]: expectedJobStatus, + [QueueName.Ocr]: expectedJobStatus, + [QueueName.Workflow]: expectedJobStatus, + }); + }); + }); + + describe('handleCommand', () => { + it('should handle a pause command', async () => { + await sut.runCommand(QueueName.MetadataExtraction, { command: QueueCommand.Pause, force: false }); + + expect(mocks.job.pause).toHaveBeenCalledWith(QueueName.MetadataExtraction); + }); + + it('should handle a resume command', async () => { + await sut.runCommand(QueueName.MetadataExtraction, { command: QueueCommand.Resume, force: false }); + + expect(mocks.job.resume).toHaveBeenCalledWith(QueueName.MetadataExtraction); + }); + + it('should handle an empty command', async () => { + await sut.runCommand(QueueName.MetadataExtraction, { command: QueueCommand.Empty, force: false }); + + expect(mocks.job.empty).toHaveBeenCalledWith(QueueName.MetadataExtraction); + }); + + it('should not start a job that is already running', async () => { + mocks.job.getQueueStatus.mockResolvedValue({ isActive: true, isPaused: false }); + + await expect( + sut.runCommand(QueueName.VideoConversion, { command: QueueCommand.Start, force: false }), + ).rejects.toBeInstanceOf(BadRequestException); + + expect(mocks.job.queue).not.toHaveBeenCalled(); + expect(mocks.job.queueAll).not.toHaveBeenCalled(); + }); + + it('should handle a start video conversion command', async () => { + mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); + + await sut.runCommand(QueueName.VideoConversion, { command: QueueCommand.Start, force: false }); + + expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.AssetEncodeVideoQueueAll, data: { force: false } }); + }); + + it('should handle a start storage template migration command', async () => { + mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); + + await sut.runCommand(QueueName.StorageTemplateMigration, { command: QueueCommand.Start, force: false }); + + expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.StorageTemplateMigration }); + }); + + it('should handle a start smart search command', async () => { + mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); + + await sut.runCommand(QueueName.SmartSearch, { command: QueueCommand.Start, force: false }); + + expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.SmartSearchQueueAll, data: { force: false } }); + }); + + it('should handle a start metadata extraction command', async () => { + mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); + + await sut.runCommand(QueueName.MetadataExtraction, { command: QueueCommand.Start, force: false }); + + expect(mocks.job.queue).toHaveBeenCalledWith({ + name: JobName.AssetExtractMetadataQueueAll, + data: { force: false }, + }); + }); + + it('should handle a start sidecar command', async () => { + mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); + + await sut.runCommand(QueueName.Sidecar, { command: QueueCommand.Start, force: false }); + + expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.SidecarQueueAll, data: { force: false } }); + }); + + it('should handle a start thumbnail generation command', async () => { + mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); + + await sut.runCommand(QueueName.ThumbnailGeneration, { command: QueueCommand.Start, force: false }); + + expect(mocks.job.queue).toHaveBeenCalledWith({ + name: JobName.AssetGenerateThumbnailsQueueAll, + data: { force: false }, + }); + }); + + it('should handle a start face detection command', async () => { + mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); + + await sut.runCommand(QueueName.FaceDetection, { command: QueueCommand.Start, force: false }); + + expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.AssetDetectFacesQueueAll, data: { force: false } }); + }); + + it('should handle a start facial recognition command', async () => { + mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); + + await sut.runCommand(QueueName.FacialRecognition, { command: QueueCommand.Start, force: false }); + + expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.FacialRecognitionQueueAll, data: { force: false } }); + }); + + it('should handle a start backup database command', async () => { + mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); + + await sut.runCommand(QueueName.BackupDatabase, { command: QueueCommand.Start, force: false }); + + expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.DatabaseBackup, data: { force: false } }); + }); + + it('should throw a bad request when an invalid queue is used', async () => { + mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false }); + + await expect( + sut.runCommand(QueueName.BackgroundTask, { command: QueueCommand.Start, force: false }), + ).rejects.toBeInstanceOf(BadRequestException); + + expect(mocks.job.queue).not.toHaveBeenCalled(); + expect(mocks.job.queueAll).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/server/src/services/queue.service.ts b/server/src/services/queue.service.ts new file mode 100644 index 0000000000..bea665e8fd --- /dev/null +++ b/server/src/services/queue.service.ts @@ -0,0 +1,250 @@ +import { BadRequestException, Injectable } from '@nestjs/common'; +import { ClassConstructor } from 'class-transformer'; +import { SystemConfig } from 'src/config'; +import { OnEvent } from 'src/decorators'; +import { QueueCommandDto, QueueResponseDto, QueuesResponseDto } from 'src/dtos/queue.dto'; +import { + BootstrapEventPriority, + CronJob, + DatabaseLock, + ImmichWorker, + JobName, + QueueCleanType, + QueueCommand, + QueueName, +} from 'src/enum'; +import { ArgOf } from 'src/repositories/event.repository'; +import { BaseService } from 'src/services/base.service'; +import { ConcurrentQueueName, JobItem } from 'src/types'; +import { handlePromiseError } from 'src/utils/misc'; + +const asNightlyTasksCron = (config: SystemConfig) => { + const [hours, minutes] = config.nightlyTasks.startTime.split(':').map(Number); + return `${minutes} ${hours} * * *`; +}; + +@Injectable() +export class QueueService extends BaseService { + private services: ClassConstructor[] = []; + private nightlyJobsLock = false; + + @OnEvent({ name: 'ConfigInit' }) + async onConfigInit({ newConfig: config }: ArgOf<'ConfigInit'>) { + if (this.worker === ImmichWorker.Microservices) { + this.updateConcurrency(config); + return; + } + + this.nightlyJobsLock = await this.databaseRepository.tryLock(DatabaseLock.NightlyJobs); + if (this.nightlyJobsLock) { + const cronExpression = asNightlyTasksCron(config); + this.logger.debug(`Scheduling nightly jobs for ${cronExpression}`); + this.cronRepository.create({ + name: CronJob.NightlyJobs, + expression: cronExpression, + start: true, + onTick: () => handlePromiseError(this.handleNightlyJobs(), this.logger), + }); + } + } + + @OnEvent({ name: 'ConfigUpdate', server: true }) + onConfigUpdate({ newConfig: config }: ArgOf<'ConfigUpdate'>) { + if (this.worker === ImmichWorker.Microservices) { + this.updateConcurrency(config); + return; + } + + if (this.nightlyJobsLock) { + const cronExpression = asNightlyTasksCron(config); + this.logger.debug(`Scheduling nightly jobs for ${cronExpression}`); + this.cronRepository.update({ name: CronJob.NightlyJobs, expression: cronExpression, start: true }); + } + } + + @OnEvent({ name: 'AppBootstrap', priority: BootstrapEventPriority.JobService }) + onBootstrap() { + this.jobRepository.setup(this.services); + if (this.worker === ImmichWorker.Microservices) { + this.jobRepository.startWorkers(); + } + } + + private updateConcurrency(config: SystemConfig) { + this.logger.debug(`Updating queue concurrency settings`); + for (const queueName of Object.values(QueueName)) { + let concurrency = 1; + if (this.isConcurrentQueue(queueName)) { + concurrency = config.job[queueName].concurrency; + } + this.logger.debug(`Setting ${queueName} concurrency to ${concurrency}`); + this.jobRepository.setConcurrency(queueName, concurrency); + } + } + + setServices(services: ClassConstructor[]) { + this.services = services; + } + + async runCommand(name: QueueName, dto: QueueCommandDto): Promise { + this.logger.debug(`Handling command: queue=${name},command=${dto.command},force=${dto.force}`); + + switch (dto.command) { + case QueueCommand.Start: { + await this.start(name, dto); + break; + } + + case QueueCommand.Pause: { + await this.jobRepository.pause(name); + break; + } + + case QueueCommand.Resume: { + await this.jobRepository.resume(name); + break; + } + + case QueueCommand.Empty: { + await this.jobRepository.empty(name); + break; + } + + case QueueCommand.ClearFailed: { + const failedJobs = await this.jobRepository.clear(name, QueueCleanType.Failed); + this.logger.debug(`Cleared failed jobs: ${failedJobs}`); + break; + } + } + + return this.getByName(name); + } + + async getAll(): Promise { + const response = new QueuesResponseDto(); + for (const name of Object.values(QueueName)) { + response[name] = await this.getByName(name); + } + return response; + } + + async getByName(name: QueueName): Promise { + const [jobCounts, queueStatus] = await Promise.all([ + this.jobRepository.getJobCounts(name), + this.jobRepository.getQueueStatus(name), + ]); + + return { jobCounts, queueStatus }; + } + + private async start(name: QueueName, { force }: QueueCommandDto): Promise { + const { isActive } = await this.jobRepository.getQueueStatus(name); + if (isActive) { + throw new BadRequestException(`Job is already running`); + } + + await this.eventRepository.emit('QueueStart', { name }); + + switch (name) { + case QueueName.VideoConversion: { + return this.jobRepository.queue({ name: JobName.AssetEncodeVideoQueueAll, data: { force } }); + } + + case QueueName.StorageTemplateMigration: { + return this.jobRepository.queue({ name: JobName.StorageTemplateMigration }); + } + + case QueueName.Migration: { + return this.jobRepository.queue({ name: JobName.FileMigrationQueueAll }); + } + + case QueueName.SmartSearch: { + return this.jobRepository.queue({ name: JobName.SmartSearchQueueAll, data: { force } }); + } + + case QueueName.DuplicateDetection: { + return this.jobRepository.queue({ name: JobName.AssetDetectDuplicatesQueueAll, data: { force } }); + } + + case QueueName.MetadataExtraction: { + return this.jobRepository.queue({ name: JobName.AssetExtractMetadataQueueAll, data: { force } }); + } + + case QueueName.Sidecar: { + return this.jobRepository.queue({ name: JobName.SidecarQueueAll, data: { force } }); + } + + case QueueName.ThumbnailGeneration: { + return this.jobRepository.queue({ name: JobName.AssetGenerateThumbnailsQueueAll, data: { force } }); + } + + case QueueName.FaceDetection: { + return this.jobRepository.queue({ name: JobName.AssetDetectFacesQueueAll, data: { force } }); + } + + case QueueName.FacialRecognition: { + return this.jobRepository.queue({ name: JobName.FacialRecognitionQueueAll, data: { force } }); + } + + case QueueName.Library: { + return this.jobRepository.queue({ name: JobName.LibraryScanQueueAll, data: { force } }); + } + + case QueueName.BackupDatabase: { + return this.jobRepository.queue({ name: JobName.DatabaseBackup, data: { force } }); + } + + case QueueName.Ocr: { + return this.jobRepository.queue({ name: JobName.OcrQueueAll, data: { force } }); + } + + default: { + throw new BadRequestException(`Invalid job name: ${name}`); + } + } + } + + private isConcurrentQueue(name: QueueName): name is ConcurrentQueueName { + return ![ + QueueName.FacialRecognition, + QueueName.StorageTemplateMigration, + QueueName.DuplicateDetection, + QueueName.BackupDatabase, + ].includes(name); + } + + async handleNightlyJobs() { + const config = await this.getConfig({ withCache: false }); + const jobs: JobItem[] = []; + + if (config.nightlyTasks.databaseCleanup) { + jobs.push( + { name: JobName.AssetDeleteCheck }, + { name: JobName.UserDeleteCheck }, + { name: JobName.PersonCleanup }, + { name: JobName.MemoryCleanup }, + { name: JobName.SessionCleanup }, + { name: JobName.AuditTableCleanup }, + { name: JobName.AuditLogCleanup }, + ); + } + + if (config.nightlyTasks.generateMemories) { + jobs.push({ name: JobName.MemoryGenerate }); + } + + if (config.nightlyTasks.syncQuotaUsage) { + jobs.push({ name: JobName.UserSyncUsage }); + } + + if (config.nightlyTasks.missingThumbnails) { + jobs.push({ name: JobName.AssetGenerateThumbnailsQueueAll, data: { force: false } }); + } + + if (config.nightlyTasks.clusterNewFaces) { + jobs.push({ name: JobName.FacialRecognitionQueueAll, data: { force: false, nightly: true } }); + } + + await this.jobRepository.queueAll(jobs); + } +} diff --git a/server/src/services/search.service.spec.ts b/server/src/services/search.service.spec.ts index b6e09add19..0dec02f18f 100644 --- a/server/src/services/search.service.spec.ts +++ b/server/src/services/search.service.spec.ts @@ -179,6 +179,26 @@ describe(SearchService.name, () => { ).resolves.toEqual(['Fujifilm X100VI', null]); expect(mocks.search.getCameraModels).toHaveBeenCalledWith([authStub.user1.user.id], expect.anything()); }); + + it('should return search suggestions for camera lens model', async () => { + mocks.search.getCameraLensModels.mockResolvedValue(['10-24mm']); + mocks.partner.getAll.mockResolvedValue([]); + + await expect( + sut.getSearchSuggestions(authStub.user1, { includeNull: false, type: SearchSuggestionType.CAMERA_LENS_MODEL }), + ).resolves.toEqual(['10-24mm']); + expect(mocks.search.getCameraLensModels).toHaveBeenCalledWith([authStub.user1.user.id], expect.anything()); + }); + + it('should return search suggestions for camera lens model (including null)', async () => { + mocks.search.getCameraLensModels.mockResolvedValue(['10-24mm']); + mocks.partner.getAll.mockResolvedValue([]); + + await expect( + sut.getSearchSuggestions(authStub.user1, { includeNull: true, type: SearchSuggestionType.CAMERA_LENS_MODEL }), + ).resolves.toEqual(['10-24mm', null]); + expect(mocks.search.getCameraLensModels).toHaveBeenCalledWith([authStub.user1.user.id], expect.anything()); + }); }); describe('searchSmart', () => { diff --git a/server/src/services/search.service.ts b/server/src/services/search.service.ts index fea1670e27..9a6f8321a9 100644 --- a/server/src/services/search.service.ts +++ b/server/src/services/search.service.ts @@ -177,6 +177,9 @@ export class SearchService extends BaseService { case SearchSuggestionType.CAMERA_MODEL: { return this.searchRepository.getCameraModels(userIds, dto); } + case SearchSuggestionType.CAMERA_LENS_MODEL: { + return this.searchRepository.getCameraLensModels(userIds, dto); + } default: { return Promise.resolve([]); } diff --git a/server/src/services/server.service.spec.ts b/server/src/services/server.service.spec.ts index a96a9925db..8e39f09c62 100644 --- a/server/src/services/server.service.spec.ts +++ b/server/src/services/server.service.spec.ts @@ -141,6 +141,7 @@ describe(ServerService.name, () => { reverseGeocoding: true, oauth: false, oauthAutoLaunch: false, + ocr: true, passwordLogin: true, search: true, sidecar: true, diff --git a/server/src/services/server.service.ts b/server/src/services/server.service.ts index dae484cce8..5c3669dcbb 100644 --- a/server/src/services/server.service.ts +++ b/server/src/services/server.service.ts @@ -19,7 +19,12 @@ import { UserStatsQueryResponse } from 'src/repositories/user.repository'; import { BaseService } from 'src/services/base.service'; import { asHumanReadable } from 'src/utils/bytes'; import { mimeTypes } from 'src/utils/mime-types'; -import { isDuplicateDetectionEnabled, isFacialRecognitionEnabled, isSmartSearchEnabled } from 'src/utils/misc'; +import { + isDuplicateDetectionEnabled, + isFacialRecognitionEnabled, + isOcrEnabled, + isSmartSearchEnabled, +} from 'src/utils/misc'; @Injectable() export class ServerService extends BaseService { @@ -97,6 +102,7 @@ export class ServerService extends BaseService { trash: trash.enabled, oauth: oauth.enabled, oauthAutoLaunch: oauth.autoLaunch, + ocr: isOcrEnabled(machineLearning), passwordLogin: passwordLogin.enabled, configFile: !!configFile, email: notifications.smtp.enabled, diff --git a/server/src/services/session.service.spec.ts b/server/src/services/session.service.spec.ts index 3cbad28389..7eacd148ad 100644 --- a/server/src/services/session.service.spec.ts +++ b/server/src/services/session.service.spec.ts @@ -43,17 +43,13 @@ describe('SessionService', () => { describe('logoutDevices', () => { it('should logout all devices', async () => { const currentSession = factory.session(); - const otherSession = factory.session(); const auth = factory.auth({ session: currentSession }); - mocks.session.getByUserId.mockResolvedValue([currentSession, otherSession]); - mocks.session.delete.mockResolvedValue(); + mocks.session.invalidate.mockResolvedValue(); await sut.deleteAll(auth); - expect(mocks.session.getByUserId).toHaveBeenCalledWith(auth.user.id); - expect(mocks.session.delete).toHaveBeenCalledWith(otherSession.id); - expect(mocks.session.delete).not.toHaveBeenCalledWith(currentSession.id); + expect(mocks.session.invalidate).toHaveBeenCalledWith({ userId: auth.user.id, excludeId: currentSession.id }); }); }); diff --git a/server/src/services/session.service.ts b/server/src/services/session.service.ts index a9c7e92fcb..2f477c0d6a 100644 --- a/server/src/services/session.service.ts +++ b/server/src/services/session.service.ts @@ -1,6 +1,6 @@ import { BadRequestException, Injectable } from '@nestjs/common'; import { DateTime } from 'luxon'; -import { OnJob } from 'src/decorators'; +import { OnEvent, OnJob } from 'src/decorators'; import { AuthDto } from 'src/dtos/auth.dto'; import { SessionCreateDto, @@ -10,6 +10,7 @@ import { mapSession, } from 'src/dtos/session.dto'; import { JobName, JobStatus, Permission, QueueName } from 'src/enum'; +import { ArgOf } from 'src/repositories/event.repository'; import { BaseService } from 'src/services/base.service'; @Injectable() @@ -69,18 +70,19 @@ export class SessionService extends BaseService { await this.sessionRepository.delete(id); } + async deleteAll(auth: AuthDto): Promise { + const userId = auth.user.id; + const currentSessionId = auth.session?.id; + await this.sessionRepository.invalidate({ userId, excludeId: currentSessionId }); + } + async lock(auth: AuthDto, id: string): Promise { await this.requireAccess({ auth, permission: Permission.SessionLock, ids: [id] }); await this.sessionRepository.update(id, { pinExpiresAt: null }); } - async deleteAll(auth: AuthDto): Promise { - const sessions = await this.sessionRepository.getByUserId(auth.user.id); - for (const session of sessions) { - if (session.id === auth.session?.id) { - continue; - } - await this.sessionRepository.delete(session.id); - } + @OnEvent({ name: 'AuthChangePassword' }) + async onAuthChangePassword({ userId, currentSessionId }: ArgOf<'AuthChangePassword'>): Promise { + await this.sessionRepository.invalidate({ userId, excludeId: currentSessionId }); } } diff --git a/server/src/services/shared-link.service.spec.ts b/server/src/services/shared-link.service.spec.ts index 9483cdddff..062214b975 100644 --- a/server/src/services/shared-link.service.spec.ts +++ b/server/src/services/shared-link.service.spec.ts @@ -300,6 +300,7 @@ describe(SharedLinkService.name, () => { mocks.sharedLink.get.mockResolvedValue(_.cloneDeep(sharedLinkStub.individual)); mocks.sharedLink.create.mockResolvedValue(sharedLinkStub.individual); mocks.sharedLink.update.mockResolvedValue(sharedLinkStub.individual); + mocks.sharedLinkAsset.remove.mockResolvedValue([assetStub.image.id]); await expect( sut.removeAssets(authStub.admin, 'link-1', { assetIds: [assetStub.image.id, 'asset-2'] }), @@ -308,6 +309,7 @@ describe(SharedLinkService.name, () => { { assetId: 'asset-2', success: false, error: AssetIdErrorReason.NOT_FOUND }, ]); + expect(mocks.sharedLinkAsset.remove).toHaveBeenCalledWith('link-1', [assetStub.image.id, 'asset-2']); expect(mocks.sharedLink.update).toHaveBeenCalledWith({ ...sharedLinkStub.individual, assets: [] }); }); }); diff --git a/server/src/services/shared-link.service.ts b/server/src/services/shared-link.service.ts index 096739d056..3c1a6083e9 100644 --- a/server/src/services/shared-link.service.ts +++ b/server/src/services/shared-link.service.ts @@ -175,10 +175,12 @@ export class SharedLinkService extends BaseService { throw new BadRequestException('Invalid shared link type'); } + const removedAssetIds = await this.sharedLinkAssetRepository.remove(id, dto.assetIds); + const results: AssetIdsResponseDto[] = []; for (const assetId of dto.assetIds) { - const hasAsset = sharedLink.assets.find((asset) => asset.id === assetId); - if (!hasAsset) { + const wasRemoved = removedAssetIds.find((id) => id === assetId); + if (!wasRemoved) { results.push({ assetId, success: false, error: AssetIdErrorReason.NOT_FOUND }); continue; } diff --git a/server/src/services/storage.service.ts b/server/src/services/storage.service.ts index b983c34f62..71cf0d0ce8 100644 --- a/server/src/services/storage.service.ts +++ b/server/src/services/storage.service.ts @@ -15,7 +15,7 @@ import { BaseService } from 'src/services/base.service'; import { JobOf, SystemFlags } from 'src/types'; import { ImmichStartupError } from 'src/utils/misc'; -const docsMessage = `Please see https://immich.app/docs/administration/system-integrity#folder-checks for more information.`; +const docsMessage = `Please see https://docs.immich.app/administration/system-integrity#folder-checks for more information.`; @Injectable() export class StorageService extends BaseService { @@ -115,7 +115,7 @@ export class StorageService extends BaseService { if (!path.startsWith(previous)) { throw new Error( - 'Detected an inconsistent media location. For more information, see https://immich.app/errors#inconsistent-media-location', + 'Detected an inconsistent media location. For more information, see https://docs.immich.app/errors#inconsistent-media-location', ); } diff --git a/server/src/services/system-config.service.spec.ts b/server/src/services/system-config.service.spec.ts index 5a9c7f4df3..fbdd655bbc 100644 --- a/server/src/services/system-config.service.spec.ts +++ b/server/src/services/system-config.service.spec.ts @@ -39,6 +39,8 @@ const updatedConfig = Object.freeze({ [QueueName.ThumbnailGeneration]: { concurrency: 3 }, [QueueName.VideoConversion]: { concurrency: 1 }, [QueueName.Notification]: { concurrency: 5 }, + [QueueName.Ocr]: { concurrency: 1 }, + [QueueName.Workflow]: { concurrency: 5 }, }, backup: { database: { @@ -102,6 +104,13 @@ const updatedConfig = Object.freeze({ maxDistance: 0.5, minFaces: 3, }, + ocr: { + enabled: true, + modelName: 'PP-OCRv5_mobile', + minDetectionScore: 0.5, + minRecognitionScore: 0.8, + maxResolution: 736, + }, }, map: { enabled: true, @@ -197,6 +206,7 @@ const updatedConfig = Object.freeze({ transport: { host: '', port: 587, + secure: false, username: '', password: '', ignoreCert: false, diff --git a/server/src/services/tag.service.spec.ts b/server/src/services/tag.service.spec.ts index 6699c61970..6a630de6a1 100644 --- a/server/src/services/tag.service.spec.ts +++ b/server/src/services/tag.service.spec.ts @@ -192,12 +192,12 @@ describe(TagService.name, () => { mocks.access.tag.checkOwnerAccess.mockResolvedValue(new Set(['tag-1', 'tag-2'])); mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2', 'asset-3'])); mocks.tag.upsertAssetIds.mockResolvedValue([ - { tagsId: 'tag-1', assetsId: 'asset-1' }, - { tagsId: 'tag-1', assetsId: 'asset-2' }, - { tagsId: 'tag-1', assetsId: 'asset-3' }, - { tagsId: 'tag-2', assetsId: 'asset-1' }, - { tagsId: 'tag-2', assetsId: 'asset-2' }, - { tagsId: 'tag-2', assetsId: 'asset-3' }, + { tagId: 'tag-1', assetId: 'asset-1' }, + { tagId: 'tag-1', assetId: 'asset-2' }, + { tagId: 'tag-1', assetId: 'asset-3' }, + { tagId: 'tag-2', assetId: 'asset-1' }, + { tagId: 'tag-2', assetId: 'asset-2' }, + { tagId: 'tag-2', assetId: 'asset-3' }, ]); await expect( sut.bulkTagAssets(authStub.admin, { tagIds: ['tag-1', 'tag-2'], assetIds: ['asset-1', 'asset-2', 'asset-3'] }), @@ -205,12 +205,12 @@ describe(TagService.name, () => { count: 6, }); expect(mocks.tag.upsertAssetIds).toHaveBeenCalledWith([ - { tagsId: 'tag-1', assetsId: 'asset-1' }, - { tagsId: 'tag-1', assetsId: 'asset-2' }, - { tagsId: 'tag-1', assetsId: 'asset-3' }, - { tagsId: 'tag-2', assetsId: 'asset-1' }, - { tagsId: 'tag-2', assetsId: 'asset-2' }, - { tagsId: 'tag-2', assetsId: 'asset-3' }, + { tagId: 'tag-1', assetId: 'asset-1' }, + { tagId: 'tag-1', assetId: 'asset-2' }, + { tagId: 'tag-1', assetId: 'asset-3' }, + { tagId: 'tag-2', assetId: 'asset-1' }, + { tagId: 'tag-2', assetId: 'asset-2' }, + { tagId: 'tag-2', assetId: 'asset-3' }, ]); }); }); diff --git a/server/src/services/tag.service.ts b/server/src/services/tag.service.ts index 2fae4b55d0..3ee5d29b75 100644 --- a/server/src/services/tag.service.ts +++ b/server/src/services/tag.service.ts @@ -82,14 +82,14 @@ export class TagService extends BaseService { ]); const items: Insertable[] = []; - for (const tagsId of tagIds) { - for (const assetsId of assetIds) { - items.push({ tagsId, assetsId }); + for (const tagId of tagIds) { + for (const assetId of assetIds) { + items.push({ tagId, assetId }); } } const results = await this.tagRepository.upsertAssetIds(items); - for (const assetId of new Set(results.map((item) => item.assetsId))) { + for (const assetId of new Set(results.map((item) => item.assetId))) { await this.eventRepository.emit('AssetTag', { assetId }); } diff --git a/server/src/services/telemetry.service.ts b/server/src/services/telemetry.service.ts new file mode 100644 index 0000000000..7c4fe43214 --- /dev/null +++ b/server/src/services/telemetry.service.ts @@ -0,0 +1,59 @@ +import { snakeCase } from 'lodash'; +import { OnEvent } from 'src/decorators'; +import { ImmichWorker, JobStatus } from 'src/enum'; +import { ArgOf, ArgsOf } from 'src/repositories/event.repository'; +import { BaseService } from 'src/services/base.service'; + +export class TelemetryService extends BaseService { + @OnEvent({ name: 'AppBootstrap', workers: [ImmichWorker.Api] }) + async onBootstrap(): Promise { + const userCount = await this.userRepository.getCount(); + this.telemetryRepository.api.addToGauge('immich.users.total', userCount); + } + + @OnEvent({ name: 'UserCreate' }) + onUserCreate() { + this.telemetryRepository.api.addToGauge(`immich.users.total`, 1); + } + + @OnEvent({ name: 'UserTrash' }) + onUserTrash() { + this.telemetryRepository.api.addToGauge(`immich.users.total`, -1); + } + + @OnEvent({ name: 'UserRestore' }) + onUserRestore() { + this.telemetryRepository.api.addToGauge(`immich.users.total`, 1); + } + + @OnEvent({ name: 'JobStart' }) + onJobStart(...[queueName]: ArgsOf<'JobStart'>) { + const queueMetric = `immich.queues.${snakeCase(queueName)}.active`; + this.telemetryRepository.jobs.addToGauge(queueMetric, 1); + } + + @OnEvent({ name: 'JobSuccess' }) + onJobSuccess({ job, response }: ArgOf<'JobSuccess'>) { + if (response && Object.values(JobStatus).includes(response as JobStatus)) { + const jobMetric = `immich.jobs.${snakeCase(job.name)}.${response}`; + this.telemetryRepository.jobs.addToCounter(jobMetric, 1); + } + } + + @OnEvent({ name: 'JobError' }) + onJobError({ job }: ArgOf<'JobError'>) { + const jobMetric = `immich.jobs.${snakeCase(job.name)}.${JobStatus.Failed}`; + this.telemetryRepository.jobs.addToCounter(jobMetric, 1); + } + + @OnEvent({ name: 'JobComplete' }) + onJobComplete(...[queueName]: ArgsOf<'JobComplete'>) { + const queueMetric = `immich.queues.${snakeCase(queueName)}.active`; + this.telemetryRepository.jobs.addToGauge(queueMetric, -1); + } + + @OnEvent({ name: 'QueueStart' }) + onQueueStart({ name }: ArgOf<'QueueStart'>) { + this.telemetryRepository.jobs.addToCounter(`immich.queues.${snakeCase(name)}.started`, 1); + } +} diff --git a/server/src/services/timeline.service.spec.ts b/server/src/services/timeline.service.spec.ts index 11df30a7d4..3301e61318 100644 --- a/server/src/services/timeline.service.spec.ts +++ b/server/src/services/timeline.service.spec.ts @@ -36,10 +36,14 @@ describe(TimelineService.name, () => { ); expect(mocks.access.album.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['album-id'])); - expect(mocks.asset.getTimeBucket).toHaveBeenCalledWith('bucket', { - timeBucket: 'bucket', - albumId: 'album-id', - }); + expect(mocks.asset.getTimeBucket).toHaveBeenCalledWith( + 'bucket', + { + timeBucket: 'bucket', + albumId: 'album-id', + }, + authStub.admin, + ); }); it('should return the assets for a archive time bucket if user has archive.read', async () => { @@ -60,6 +64,7 @@ describe(TimelineService.name, () => { visibility: AssetVisibility.Archive, userIds: [authStub.admin.user.id], }), + authStub.admin, ); }); @@ -76,12 +81,16 @@ describe(TimelineService.name, () => { withPartners: true, }), ).resolves.toEqual(json); - expect(mocks.asset.getTimeBucket).toHaveBeenCalledWith('bucket', { - timeBucket: 'bucket', - visibility: AssetVisibility.Timeline, - withPartners: true, - userIds: [authStub.admin.user.id], - }); + expect(mocks.asset.getTimeBucket).toHaveBeenCalledWith( + 'bucket', + { + timeBucket: 'bucket', + visibility: AssetVisibility.Timeline, + withPartners: true, + userIds: [authStub.admin.user.id], + }, + authStub.admin, + ); }); it('should check permissions to read tag', async () => { @@ -96,11 +105,15 @@ describe(TimelineService.name, () => { tagId: 'tag-123', }), ).resolves.toEqual(json); - expect(mocks.asset.getTimeBucket).toHaveBeenCalledWith('bucket', { - tagId: 'tag-123', - timeBucket: 'bucket', - userIds: [authStub.admin.user.id], - }); + expect(mocks.asset.getTimeBucket).toHaveBeenCalledWith( + 'bucket', + { + tagId: 'tag-123', + timeBucket: 'bucket', + userIds: [authStub.admin.user.id], + }, + authStub.admin, + ); }); it('should return the assets for a library time bucket if user has library.read', async () => { @@ -119,6 +132,7 @@ describe(TimelineService.name, () => { timeBucket: 'bucket', userIds: [authStub.admin.user.id], }), + authStub.admin, ); }); diff --git a/server/src/services/timeline.service.ts b/server/src/services/timeline.service.ts index d8cac3a205..b840883fa9 100644 --- a/server/src/services/timeline.service.ts +++ b/server/src/services/timeline.service.ts @@ -21,7 +21,7 @@ export class TimelineService extends BaseService { const timeBucketOptions = await this.buildTimeBucketOptions(auth, { ...dto }); // TODO: use id cursor for pagination - const bucket = await this.assetRepository.getTimeBucket(dto.timeBucket, timeBucketOptions); + const bucket = await this.assetRepository.getTimeBucket(dto.timeBucket, timeBucketOptions, auth); return bucket.assets; } diff --git a/server/src/services/user-admin.service.ts b/server/src/services/user-admin.service.ts index ce70419ff6..58b4221cc9 100644 --- a/server/src/services/user-admin.service.ts +++ b/server/src/services/user-admin.service.ts @@ -2,6 +2,7 @@ import { BadRequestException, ForbiddenException, Injectable } from '@nestjs/com import { SALT_ROUNDS } from 'src/constants'; import { AssetStatsDto, AssetStatsResponseDto, mapStats } from 'src/dtos/asset.dto'; import { AuthDto } from 'src/dtos/auth.dto'; +import { SessionResponseDto, mapSession } from 'src/dtos/session.dto'; import { UserPreferencesResponseDto, UserPreferencesUpdateDto, mapPreferences } from 'src/dtos/user-preferences.dto'; import { UserAdminCreateDto, @@ -103,6 +104,8 @@ export class UserAdminService extends BaseService { const status = force ? UserStatus.Removing : UserStatus.Deleted; const user = await this.userRepository.update(id, { status, deletedAt: new Date() }); + await this.eventRepository.emit('UserTrash', user); + if (force) { await this.jobRepository.queue({ name: JobName.UserDelete, data: { id: user.id, force } }); } @@ -114,9 +117,15 @@ export class UserAdminService extends BaseService { await this.findOrFail(id, { withDeleted: true }); await this.albumRepository.restoreAll(id); const user = await this.userRepository.restore(id); + await this.eventRepository.emit('UserRestore', user); return mapUserAdmin(user); } + async getSessions(auth: AuthDto, id: string): Promise { + const sessions = await this.sessionRepository.getByUserId(id); + return sessions.map((session) => mapSession(session)); + } + async getStatistics(auth: AuthDto, id: string, dto: AssetStatsDto): Promise { const stats = await this.assetRepository.getStatistics(id, dto); return mapStats(stats); diff --git a/server/src/services/user.service.ts b/server/src/services/user.service.ts index 6849b17ac3..9fb1f45e54 100644 --- a/server/src/services/user.service.ts +++ b/server/src/services/user.service.ts @@ -228,17 +228,17 @@ export class UserService extends BaseService { } @OnJob({ name: JobName.UserDelete, queue: QueueName.BackgroundTask }) - async handleUserDelete({ id, force }: JobOf): Promise { + async handleUserDelete({ id, force }: JobOf) { const config = await this.getConfig({ withCache: false }); const user = await this.userRepository.get(id, { withDeleted: true }); if (!user) { - return JobStatus.Failed; + return; } // just for extra protection here if (!force && !this.isReadyForDeletion(user, config.user.deleteDelay)) { this.logger.warn(`Skipped user that was not ready for deletion: id=${id}`); - return JobStatus.Skipped; + return; } this.logger.log(`Deleting user: ${user.id}`); @@ -260,7 +260,7 @@ export class UserService extends BaseService { await this.albumRepository.deleteAll(user.id); await this.userRepository.delete(user, true); - return JobStatus.Success; + await this.eventRepository.emit('UserDelete', user); } private isReadyForDeletion(user: { id: string; deletedAt?: Date | null }, deleteDelay: number): boolean { diff --git a/server/src/services/version.service.spec.ts b/server/src/services/version.service.spec.ts index 73794275ea..84c7b578dd 100644 --- a/server/src/services/version.service.spec.ts +++ b/server/src/services/version.service.spec.ts @@ -108,7 +108,7 @@ describe(VersionService.name, () => { await expect(sut.handleVersionCheck()).resolves.toEqual(JobStatus.Success); expect(mocks.systemMetadata.set).toHaveBeenCalled(); expect(mocks.logger.log).toHaveBeenCalled(); - expect(mocks.event.clientBroadcast).toHaveBeenCalled(); + expect(mocks.websocket.clientBroadcast).toHaveBeenCalled(); }); it('should not notify if the version is equal', async () => { @@ -118,14 +118,14 @@ describe(VersionService.name, () => { checkedAt: expect.any(String), releaseVersion: serverVersion.toString(), }); - expect(mocks.event.clientBroadcast).not.toHaveBeenCalled(); + expect(mocks.websocket.clientBroadcast).not.toHaveBeenCalled(); }); it('should handle a github error', async () => { mocks.serverInfo.getGitHubRelease.mockRejectedValue(new Error('GitHub is down')); await expect(sut.handleVersionCheck()).resolves.toEqual(JobStatus.Failed); expect(mocks.systemMetadata.set).not.toHaveBeenCalled(); - expect(mocks.event.clientBroadcast).not.toHaveBeenCalled(); + expect(mocks.websocket.clientBroadcast).not.toHaveBeenCalled(); expect(mocks.logger.warn).toHaveBeenCalled(); }); }); @@ -133,15 +133,15 @@ describe(VersionService.name, () => { describe('onWebsocketConnectionEvent', () => { it('should send on_server_version client event', async () => { await sut.onWebsocketConnection({ userId: '42' }); - expect(mocks.event.clientSend).toHaveBeenCalledWith('on_server_version', '42', expect.any(SemVer)); - expect(mocks.event.clientSend).toHaveBeenCalledTimes(1); + expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_server_version', '42', expect.any(SemVer)); + expect(mocks.websocket.clientSend).toHaveBeenCalledTimes(1); }); it('should also send a new release notification', async () => { mocks.systemMetadata.get.mockResolvedValue({ checkedAt: '2024-01-01', releaseVersion: 'v1.42.0' }); await sut.onWebsocketConnection({ userId: '42' }); - expect(mocks.event.clientSend).toHaveBeenCalledWith('on_server_version', '42', expect.any(SemVer)); - expect(mocks.event.clientSend).toHaveBeenCalledWith('on_new_release', '42', expect.any(Object)); + expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_server_version', '42', expect.any(SemVer)); + expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_new_release', '42', expect.any(Object)); }); }); }); diff --git a/server/src/services/version.service.ts b/server/src/services/version.service.ts index b817363eac..2d3924bc49 100644 --- a/server/src/services/version.service.ts +++ b/server/src/services/version.service.ts @@ -92,7 +92,7 @@ export class VersionService extends BaseService { if (semver.gt(releaseVersion, serverVersion)) { this.logger.log(`Found ${releaseVersion}, released at ${new Date(publishedAt).toLocaleString()}`); - this.eventRepository.clientBroadcast('on_new_release', asNotification(metadata)); + this.websocketRepository.clientBroadcast('on_new_release', asNotification(metadata)); } } catch (error: Error | any) { this.logger.warn(`Unable to run version check: ${error}\n${error?.stack}`); @@ -104,10 +104,10 @@ export class VersionService extends BaseService { @OnEvent({ name: 'WebsocketConnect' }) async onWebsocketConnection({ userId }: ArgOf<'WebsocketConnect'>) { - this.eventRepository.clientSend('on_server_version', userId, serverVersion); + this.websocketRepository.clientSend('on_server_version', userId, serverVersion); const metadata = await this.systemMetadataRepository.get(SystemMetadataKey.VersionCheckState); if (metadata) { - this.eventRepository.clientSend('on_new_release', userId, asNotification(metadata)); + this.websocketRepository.clientSend('on_new_release', userId, asNotification(metadata)); } } } diff --git a/server/src/services/workflow.service.ts b/server/src/services/workflow.service.ts new file mode 100644 index 0000000000..ae72187d7d --- /dev/null +++ b/server/src/services/workflow.service.ts @@ -0,0 +1,159 @@ +import { BadRequestException, Injectable } from '@nestjs/common'; +import { Workflow } from 'src/database'; +import { AuthDto } from 'src/dtos/auth.dto'; +import { + mapWorkflowAction, + mapWorkflowFilter, + WorkflowCreateDto, + WorkflowResponseDto, + WorkflowUpdateDto, +} from 'src/dtos/workflow.dto'; +import { Permission, PluginContext, PluginTriggerType } from 'src/enum'; +import { pluginTriggers } from 'src/plugins'; + +import { BaseService } from 'src/services/base.service'; + +@Injectable() +export class WorkflowService extends BaseService { + async create(auth: AuthDto, dto: WorkflowCreateDto): Promise { + const trigger = this.getTriggerOrFail(dto.triggerType); + + const filterInserts = await this.validateAndMapFilters(dto.filters, trigger.context); + const actionInserts = await this.validateAndMapActions(dto.actions, trigger.context); + + const workflow = await this.workflowRepository.createWorkflow( + { + ownerId: auth.user.id, + triggerType: dto.triggerType, + name: dto.name, + description: dto.description || '', + enabled: dto.enabled ?? true, + }, + filterInserts, + actionInserts, + ); + + return this.mapWorkflow(workflow); + } + + async getAll(auth: AuthDto): Promise { + const workflows = await this.workflowRepository.getWorkflowsByOwner(auth.user.id); + + return Promise.all(workflows.map((workflow) => this.mapWorkflow(workflow))); + } + + async get(auth: AuthDto, id: string): Promise { + await this.requireAccess({ auth, permission: Permission.WorkflowRead, ids: [id] }); + const workflow = await this.findOrFail(id); + return this.mapWorkflow(workflow); + } + + async update(auth: AuthDto, id: string, dto: WorkflowUpdateDto): Promise { + await this.requireAccess({ auth, permission: Permission.WorkflowUpdate, ids: [id] }); + + if (Object.values(dto).filter((prop) => prop !== undefined).length === 0) { + throw new BadRequestException('No fields to update'); + } + + const workflow = await this.findOrFail(id); + const trigger = this.getTriggerOrFail(workflow.triggerType); + + const { filters, actions, ...workflowUpdate } = dto; + const filterInserts = filters && (await this.validateAndMapFilters(filters, trigger.context)); + const actionInserts = actions && (await this.validateAndMapActions(actions, trigger.context)); + + const updatedWorkflow = await this.workflowRepository.updateWorkflow( + id, + workflowUpdate, + filterInserts, + actionInserts, + ); + + return this.mapWorkflow(updatedWorkflow); + } + + async delete(auth: AuthDto, id: string): Promise { + await this.requireAccess({ auth, permission: Permission.WorkflowDelete, ids: [id] }); + await this.workflowRepository.deleteWorkflow(id); + } + + private async validateAndMapFilters( + filters: Array<{ filterId: string; filterConfig?: any }>, + requiredContext: PluginContext, + ) { + for (const dto of filters) { + const filter = await this.pluginRepository.getFilter(dto.filterId); + if (!filter) { + throw new BadRequestException(`Invalid filter ID: ${dto.filterId}`); + } + + if (!filter.supportedContexts.includes(requiredContext)) { + throw new BadRequestException( + `Filter "${filter.title}" does not support ${requiredContext} context. Supported contexts: ${filter.supportedContexts.join(', ')}`, + ); + } + } + + return filters.map((dto, index) => ({ + filterId: dto.filterId, + filterConfig: dto.filterConfig || null, + order: index, + })); + } + + private async validateAndMapActions( + actions: Array<{ actionId: string; actionConfig?: any }>, + requiredContext: PluginContext, + ) { + for (const dto of actions) { + const action = await this.pluginRepository.getAction(dto.actionId); + if (!action) { + throw new BadRequestException(`Invalid action ID: ${dto.actionId}`); + } + if (!action.supportedContexts.includes(requiredContext)) { + throw new BadRequestException( + `Action "${action.title}" does not support ${requiredContext} context. Supported contexts: ${action.supportedContexts.join(', ')}`, + ); + } + } + + return actions.map((dto, index) => ({ + actionId: dto.actionId, + actionConfig: dto.actionConfig || null, + order: index, + })); + } + + private getTriggerOrFail(triggerType: PluginTriggerType) { + const trigger = pluginTriggers.find((t) => t.type === triggerType); + if (!trigger) { + throw new BadRequestException(`Invalid trigger type: ${triggerType}`); + } + return trigger; + } + + private async findOrFail(id: string) { + const workflow = await this.workflowRepository.getWorkflow(id); + if (!workflow) { + throw new BadRequestException('Workflow not found'); + } + return workflow; + } + + private async mapWorkflow(workflow: Workflow): Promise { + const filters = await this.workflowRepository.getFilters(workflow.id); + const actions = await this.workflowRepository.getActions(workflow.id); + + return { + id: workflow.id, + ownerId: workflow.ownerId, + triggerType: workflow.triggerType, + name: workflow.name, + description: workflow.description, + createdAt: workflow.createdAt.toISOString(), + enabled: workflow.enabled, + filters: filters.map((f) => mapWorkflowFilter(f)), + actions: actions.map((a) => mapWorkflowAction(a)), + }; + } +} diff --git a/server/src/sql-tools/types.ts b/server/src/sql-tools/types.ts index 9529067040..899ba1b963 100644 --- a/server/src/sql-tools/types.ts +++ b/server/src/sql-tools/types.ts @@ -322,7 +322,8 @@ export type ColumnType = | 'uuid' | 'vector' | 'enum' - | 'serial'; + | 'serial' + | 'real'; export type DatabaseSchema = { databaseName: string; diff --git a/server/src/types.ts b/server/src/types.ts index ce0baa1672..ad947e3774 100644 --- a/server/src/types.ts +++ b/server/src/types.ts @@ -1,5 +1,6 @@ import { SystemConfig } from 'src/config'; import { VECTOR_EXTENSIONS } from 'src/constants'; +import { Asset } from 'src/database'; import { UploadFieldName } from 'src/dtos/asset-media.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { @@ -11,6 +12,7 @@ import { ImageFormat, JobName, MemoryType, + PluginTriggerType, QueueName, StorageFolder, SyncEntityType, @@ -88,6 +90,9 @@ export interface VideoStreamInfo { isHDR: boolean; bitrate: number; pixelFormat: string; + colorPrimaries?: string; + colorSpace?: string; + colorTransfer?: string; } export interface AudioStreamInfo { @@ -260,6 +265,23 @@ export interface INotifyAlbumUpdateJob extends IEntityJob, IDelayedJob { recipientId: string; } +export interface WorkflowData { + [PluginTriggerType.AssetCreate]: { + userId: string; + asset: Asset; + }; + [PluginTriggerType.PersonRecognized]: { + personId: string; + assetId: string; + }; +} + +export interface IWorkflowJob { + id: string; + type: T; + event: WorkflowData[T]; +} + export interface JobCounts { active: number; completed: number; @@ -367,7 +389,14 @@ export type JobItem = | { name: JobName.NotifyUserSignup; data: INotifySignupJob } // Version check - | { name: JobName.VersionCheck; data: IBaseJob }; + | { name: JobName.VersionCheck; data: IBaseJob } + + // OCR + | { name: JobName.OcrQueueAll; data: IBaseJob } + | { name: JobName.Ocr; data: IEntityJob } + + // Workflow + | { name: JobName.WorkflowRun; data: IWorkflowJob }; export type VectorExtension = (typeof VECTOR_EXTENSIONS)[number]; @@ -412,14 +441,16 @@ export interface UploadFile { size: number; } +export interface UploadBody { + filename?: string; + [key: string]: unknown; +} + export type UploadRequest = { auth: AuthDto | null; fieldName: UploadFieldName; file: UploadFile; - body: { - filename?: string; - [key: string]: unknown; - }; + body: UploadBody; }; export interface UploadFiles { @@ -490,6 +521,7 @@ export interface UserPreferences { }; memories: { enabled: boolean; + duration: number; }; people: { enabled: boolean; diff --git a/server/src/types/plugin-schema.types.ts b/server/src/types/plugin-schema.types.ts new file mode 100644 index 0000000000..793bb3c1ff --- /dev/null +++ b/server/src/types/plugin-schema.types.ts @@ -0,0 +1,35 @@ +/** + * JSON Schema types for plugin configuration schemas + * Based on JSON Schema Draft 7 + */ + +export type JSONSchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array' | 'null'; + +export interface JSONSchemaProperty { + type?: JSONSchemaType | JSONSchemaType[]; + description?: string; + default?: any; + enum?: any[]; + items?: JSONSchemaProperty; + properties?: Record; + required?: string[]; + additionalProperties?: boolean | JSONSchemaProperty; +} + +export interface JSONSchema { + type: 'object'; + properties?: Record; + required?: string[]; + additionalProperties?: boolean; + description?: string; +} + +export type ConfigValue = string | number | boolean | null | ConfigValue[] | { [key: string]: ConfigValue }; + +export interface FilterConfig { + [key: string]: ConfigValue; +} + +export interface ActionConfig { + [key: string]: ConfigValue; +} diff --git a/server/src/utils/access.ts b/server/src/utils/access.ts index 8427da6f1b..f8d5f0ca08 100644 --- a/server/src/utils/access.ts +++ b/server/src/utils/access.ts @@ -153,6 +153,10 @@ const checkOtherAccess = async (access: AccessRepository, request: OtherAccessRe return await access.asset.checkOwnerAccess(auth.user.id, ids, auth.session?.hasElevatedPermission); } + case Permission.AssetCopy: { + return await access.asset.checkOwnerAccess(auth.user.id, ids, auth.session?.hasElevatedPermission); + } + case Permission.AlbumRead: { const isOwner = await access.album.checkOwnerAccess(auth.user.id, ids); const isShared = await access.album.checkSharedAlbumAccess( @@ -294,6 +298,12 @@ const checkOtherAccess = async (access: AccessRepository, request: OtherAccessRe return access.stack.checkOwnerAccess(auth.user.id, ids); } + case Permission.WorkflowRead: + case Permission.WorkflowUpdate: + case Permission.WorkflowDelete: { + return access.workflow.checkOwnerAccess(auth.user.id, ids); + } + default: { return new Set(); } diff --git a/server/src/utils/database.ts b/server/src/utils/database.ts index d9fe6b7897..0cc3788f1a 100644 --- a/server/src/utils/database.ts +++ b/server/src/utils/database.ts @@ -200,6 +200,14 @@ export function withFiles(eb: ExpressionBuilder, type?: AssetFileTy ).as('files'); } +export function withFilePath(eb: ExpressionBuilder, type: AssetFileType) { + return eb + .selectFrom('asset_file') + .select('asset_file.path') + .whereRef('asset_file.assetId', '=', 'asset.id') + .where('asset_file.type', '=', type); +} + export function withFacesAndPeople(eb: ExpressionBuilder, withDeletedFace?: boolean) { return jsonArrayFrom( eb @@ -236,12 +244,12 @@ export function inAlbums(qb: SelectQueryBuilder, albumIds: st (eb) => eb .selectFrom('album_asset') - .select('assetsId') - .where('albumsId', '=', anyUuid(albumIds!)) - .groupBy('assetsId') - .having((eb) => eb.fn.count('albumsId').distinct(), '=', albumIds.length) + .select('assetId') + .where('albumId', '=', anyUuid(albumIds!)) + .groupBy('assetId') + .having((eb) => eb.fn.count('albumId').distinct(), '=', albumIds.length) .as('has_album'), - (join) => join.onRef('has_album.assetsId', '=', 'asset.id'), + (join) => join.onRef('has_album.assetId', '=', 'asset.id'), ); } @@ -250,13 +258,13 @@ export function hasTags(qb: SelectQueryBuilder, tagIds: strin (eb) => eb .selectFrom('tag_asset') - .select('assetsId') - .innerJoin('tag_closure', 'tag_asset.tagsId', 'tag_closure.id_descendant') + .select('assetId') + .innerJoin('tag_closure', 'tag_asset.tagId', 'tag_closure.id_descendant') .where('tag_closure.id_ancestor', '=', anyUuid(tagIds)) - .groupBy('assetsId') + .groupBy('assetId') .having((eb) => eb.fn.count('tag_closure.id_ancestor').distinct(), '>=', tagIds.length) .as('has_tags'), - (join) => join.onRef('has_tags.assetsId', '=', 'asset.id'), + (join) => join.onRef('has_tags.assetId', '=', 'asset.id'), ); } @@ -277,8 +285,8 @@ export function withTags(eb: ExpressionBuilder) { eb .selectFrom('tag') .select(columns.tag) - .innerJoin('tag_asset', 'tag.id', 'tag_asset.tagsId') - .whereRef('asset.id', '=', 'tag_asset.assetsId'), + .innerJoin('tag_asset', 'tag.id', 'tag_asset.tagId') + .whereRef('asset.id', '=', 'tag_asset.assetId'), ).as('tags'); } @@ -291,8 +299,8 @@ export function withTagId(qb: SelectQueryBuilder, tagId: stri eb.exists( eb .selectFrom('tag_closure') - .innerJoin('tag_asset', 'tag_asset.tagsId', 'tag_closure.id_descendant') - .whereRef('tag_asset.assetsId', '=', 'asset.id') + .innerJoin('tag_asset', 'tag_asset.tagId', 'tag_closure.id_descendant') + .whereRef('tag_asset.assetId', '=', 'asset.id') .where('tag_closure.id_ancestor', '=', tagId), ), ); @@ -312,7 +320,7 @@ export function searchAssetBuilder(kysely: Kysely, options: AssetSearchBuild .$if(!!options.albumIds && options.albumIds.length > 0, (qb) => inAlbums(qb, options.albumIds!)) .$if(!!options.tagIds && options.tagIds.length > 0, (qb) => hasTags(qb, options.tagIds!)) .$if(options.tagIds === null, (qb) => - qb.where((eb) => eb.not(eb.exists((eb) => eb.selectFrom('tag_asset').whereRef('assetsId', '=', 'asset.id')))), + qb.where((eb) => eb.not(eb.exists((eb) => eb.selectFrom('tag_asset').whereRef('assetId', '=', 'asset.id')))), ) .$if(!!options.personIds && options.personIds.length > 0, (qb) => hasPeople(qb, options.personIds!)) .$if(!!options.createdBefore, (qb) => qb.where('asset.createdAt', '<=', options.createdBefore!)) @@ -380,6 +388,11 @@ export function searchAssetBuilder(kysely: Kysely, options: AssetSearchBuild .innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId') .where(sql`f_unaccent(asset_exif.description)`, 'ilike', sql`'%' || f_unaccent(${options.description}) || '%'`), ) + .$if(!!options.ocr, (qb) => + qb + .innerJoin('ocr_search', 'asset.id', 'ocr_search.assetId') + .where(() => sql`f_unaccent(ocr_search.text) %>> f_unaccent(${options.ocr!})`), + ) .$if(!!options.type, (qb) => qb.where('asset.type', '=', options.type!)) .$if(options.isFavorite !== undefined, (qb) => qb.where('asset.isFavorite', '=', options.isFavorite!)) .$if(options.isOffline !== undefined, (qb) => qb.where('asset.isOffline', '=', options.isOffline!)) @@ -390,7 +403,7 @@ export function searchAssetBuilder(kysely: Kysely, options: AssetSearchBuild qb.where('asset.livePhotoVideoId', options.isMotion ? 'is not' : 'is', null), ) .$if(!!options.isNotInAlbum && (!options.albumIds || options.albumIds.length === 0), (qb) => - qb.where((eb) => eb.not(eb.exists((eb) => eb.selectFrom('album_asset').whereRef('assetsId', '=', 'asset.id')))), + qb.where((eb) => eb.not(eb.exists((eb) => eb.selectFrom('album_asset').whereRef('assetId', '=', 'asset.id')))), ) .$if(!!options.withExif, withExifInner) .$if(!!(options.withFaces || options.withPeople || options.personIds), (qb) => qb.select(withFacesAndPeople)) diff --git a/server/src/utils/lifecycle.ts b/server/src/utils/lifecycle.ts deleted file mode 100644 index 16793f6922..0000000000 --- a/server/src/utils/lifecycle.ts +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env node -import { OpenAPIObject } from '@nestjs/swagger'; -import { SchemaObject } from '@nestjs/swagger/dist/interfaces/open-api-spec.interface'; -import { readFileSync } from 'node:fs'; -import { resolve } from 'node:path'; -import { SemVer } from 'semver'; -import { ADDED_IN_PREFIX, DEPRECATED_IN_PREFIX, LIFECYCLE_EXTENSION, NEXT_RELEASE } from 'src/constants'; - -const outputPath = resolve(process.cwd(), '../open-api/immich-openapi-specs.json'); -const spec = JSON.parse(readFileSync(outputPath).toString()) as OpenAPIObject; - -type Items = { - oldEndpoints: Endpoint[]; - newEndpoints: Endpoint[]; - oldProperties: Property[]; - newProperties: Property[]; -}; -type Endpoint = { url: string; method: string; endpoint: any }; -type Property = { schema: string; property: string }; - -const metadata: Record = {}; -const trackVersion = (version: string) => { - if (!metadata[version]) { - metadata[version] = { - oldEndpoints: [], - newEndpoints: [], - oldProperties: [], - newProperties: [], - }; - } - return metadata[version]; -}; - -for (const [url, methods] of Object.entries(spec.paths)) { - for (const [method, endpoint] of Object.entries(methods) as Array<[string, any]>) { - const deprecatedAt = endpoint[LIFECYCLE_EXTENSION]?.deprecatedAt; - if (deprecatedAt) { - trackVersion(deprecatedAt).oldEndpoints.push({ url, method, endpoint }); - } - - const addedAt = endpoint[LIFECYCLE_EXTENSION]?.addedAt; - if (addedAt) { - trackVersion(addedAt).newEndpoints.push({ url, method, endpoint }); - } - } -} - -for (const [schemaName, schema] of Object.entries(spec.components?.schemas || {})) { - for (const [propertyName, property] of Object.entries((schema as SchemaObject).properties || {})) { - const propertySchema = property as SchemaObject; - if (propertySchema.description?.startsWith(DEPRECATED_IN_PREFIX)) { - const deprecatedAt = propertySchema.description.replace(DEPRECATED_IN_PREFIX, '').trim(); - trackVersion(deprecatedAt).oldProperties.push({ schema: schemaName, property: propertyName }); - } - - if (propertySchema.description?.startsWith(ADDED_IN_PREFIX)) { - const addedAt = propertySchema.description.replace(ADDED_IN_PREFIX, '').trim(); - trackVersion(addedAt).newProperties.push({ schema: schemaName, property: propertyName }); - } - } -} - -const sortedVersions = Object.keys(metadata).sort((a, b) => { - if (a === NEXT_RELEASE) { - return -1; - } - - if (b === NEXT_RELEASE) { - return 1; - } - - return new SemVer(b).compare(new SemVer(a)); -}); - -for (const version of sortedVersions) { - const { oldEndpoints, newEndpoints, oldProperties, newProperties } = metadata[version]; - console.log(`\nChanges in ${version}`); - console.log('---------------------'); - for (const { url, method, endpoint } of oldEndpoints) { - console.log(`- Deprecated ${method.toUpperCase()} ${url} (${endpoint.operationId})`); - } - for (const { url, method, endpoint } of newEndpoints) { - console.log(`- Added ${method.toUpperCase()} ${url} (${endpoint.operationId})`); - } - for (const { schema, property } of oldProperties) { - console.log(`- Deprecated ${schema}.${property}`); - } - for (const { schema, property } of newProperties) { - console.log(`- Added ${schema}.${property}`); - } -} diff --git a/server/src/utils/media.ts b/server/src/utils/media.ts index 1ecd005315..f678a32b0f 100644 --- a/server/src/utils/media.ts +++ b/server/src/utils/media.ts @@ -392,9 +392,30 @@ export class ThumbnailConfig extends BaseConfig { getBaseInputOptions(videoStream: VideoStreamInfo, format?: VideoFormat): string[] { // skip_frame nointra skips all frames for some MPEG-TS files. Look at ffmpeg tickets 7950 and 7895 for more details. - return format?.formatName === 'mpegts' - ? ['-sws_flags accurate_rnd+full_chroma_int'] - : ['-skip_frame nointra', '-sws_flags accurate_rnd+full_chroma_int']; + const options = + format?.formatName === 'mpegts' + ? ['-sws_flags accurate_rnd+full_chroma_int'] + : ['-skip_frame nointra', '-sws_flags accurate_rnd+full_chroma_int']; + + const metadataOverrides = []; + if (videoStream.colorPrimaries === 'reserved') { + metadataOverrides.push('colour_primaries=1'); + } + + if (videoStream.colorSpace === 'reserved') { + metadataOverrides.push('matrix_coefficients=1'); + } + + if (videoStream.colorTransfer === 'reserved') { + metadataOverrides.push('transfer_characteristics=1'); + } + + if (metadataOverrides.length > 0) { + // workaround for https://fftrac-bg.ffmpeg.org/ticket/11020 + options.push(`-bsf:v ${videoStream.codecName}_metadata=${metadataOverrides.join(':')}`); + } + + return options; } getBaseOutputOptions() { diff --git a/server/src/utils/misc.ts b/server/src/utils/misc.ts index a32632b52d..eb548eab74 100644 --- a/server/src/utils/misc.ts +++ b/server/src/utils/misc.ts @@ -17,7 +17,7 @@ import path from 'node:path'; import picomatch from 'picomatch'; import parse from 'picomatch/lib/parse'; import { SystemConfig } from 'src/config'; -import { CLIP_MODEL_INFO, serverVersion } from 'src/constants'; +import { CLIP_MODEL_INFO, endpointTags, serverVersion } from 'src/constants'; import { extraSyncModels } from 'src/dtos/sync.dto'; import { ApiCustomExtension, ImmichCookie, ImmichHeader, MetadataKey } from 'src/enum'; import { LoggingRepository } from 'src/repositories/logging.repository'; @@ -95,6 +95,8 @@ export const unsetDeep = (object: unknown, key: string) => { const isMachineLearningEnabled = (machineLearning: SystemConfig['machineLearning']) => machineLearning.enabled; export const isSmartSearchEnabled = (machineLearning: SystemConfig['machineLearning']) => isMachineLearningEnabled(machineLearning) && machineLearning.clip.enabled; +export const isOcrEnabled = (machineLearning: SystemConfig['machineLearning']) => + isMachineLearningEnabled(machineLearning) && machineLearning.ocr.enabled; export const isFacialRecognitionEnabled = (machineLearning: SystemConfig['machineLearning']) => isMachineLearningEnabled(machineLearning) && machineLearning.facialRecognition.enabled; export const isDuplicateDetectionEnabled = (machineLearning: SystemConfig['machineLearning']) => @@ -216,25 +218,16 @@ const patchOpenAPI = (document: OpenAPIObject) => { delete operation.summary; } + if (operation.description === '') { + delete operation.description; + } + if (operation.operationId) { // console.log(`${routeToErrorMessage(operation.operationId).padEnd(40)} (${operation.operationId})`); } - const adminOnly = operation[ApiCustomExtension.AdminOnly] ?? false; - const permission = operation[ApiCustomExtension.Permission]; - if (permission) { - let description = (operation.description || '').trim(); - if (description && !description.endsWith('.')) { - description += '. '; - } - - operation.description = - description + - `This endpoint ${adminOnly ? 'is an admin-only route, and ' : ''}requires the \`${permission}\` permission.`; - - if (operation.parameters) { - operation.parameters = _.orderBy(operation.parameters, 'name'); - } + if (operation.parameters) { + operation.parameters = _.orderBy(operation.parameters, 'name'); } } } @@ -243,7 +236,7 @@ const patchOpenAPI = (document: OpenAPIObject) => { }; export const useSwagger = (app: INestApplication, { write }: { write: boolean }) => { - const config = new DocumentBuilder() + const builder = new DocumentBuilder() .setTitle('Immich') .setDescription('Immich API') .setVersion(serverVersion.toString()) @@ -261,8 +254,12 @@ export const useSwagger = (app: INestApplication, { write }: { write: boolean }) }, MetadataKey.ApiKeySecurity, ) - .addServer('/api') - .build(); + .addServer('/api'); + + for (const [tag, description] of Object.entries(endpointTags)) { + builder.addTag(tag, description); + } + const config = builder.build(); const options: SwaggerDocumentOptions = { operationIdFactory: (controllerKey: string, methodKey: string) => methodKey, diff --git a/server/src/utils/preferences.ts b/server/src/utils/preferences.ts index 121bf2826d..b25369670a 100644 --- a/server/src/utils/preferences.ts +++ b/server/src/utils/preferences.ts @@ -16,6 +16,7 @@ const getDefaultPreferences = (): UserPreferences => { }, memories: { enabled: true, + duration: 5, }, people: { enabled: true, diff --git a/server/src/utils/request.ts b/server/src/utils/request.ts index 19d3cac661..c64c980520 100644 --- a/server/src/utils/request.ts +++ b/server/src/utils/request.ts @@ -1,5 +1,22 @@ +import { IncomingHttpHeaders } from 'node:http'; +import { UAParser } from 'ua-parser-js'; + export const fromChecksum = (checksum: string): Buffer => { return Buffer.from(checksum, checksum.length === 28 ? 'base64' : 'hex'); }; export const fromMaybeArray = (param: T | T[]) => (Array.isArray(param) ? param[0] : param); + +const getAppVersionFromUA = (ua: string) => + ua.match(/^Immich_(?:Android|iOS)_(?.+)$/)?.groups?.appVersion ?? null; + +export const getUserAgentDetails = (headers: IncomingHttpHeaders) => { + const userAgent = UAParser(headers['user-agent']); + const appVersion = getAppVersionFromUA(headers['user-agent'] ?? ''); + + return { + deviceType: userAgent.browser.name || userAgent.device.type || (headers['devicemodel'] as string) || '', + deviceOS: userAgent.os.name || (headers['devicetype'] as string) || '', + appVersion, + }; +}; diff --git a/server/test/fixtures/asset.stub.ts b/server/test/fixtures/asset.stub.ts index dd1f98d6eb..0fd2189268 100644 --- a/server/test/fixtures/asset.stub.ts +++ b/server/test/fixtures/asset.stub.ts @@ -866,4 +866,43 @@ export const assetStub = { stackId: null, visibility: AssetVisibility.Timeline, }), + panoramaTif: Object.freeze({ + id: 'asset-id', + status: AssetStatus.Active, + deviceAssetId: 'device-asset-id', + fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'), + fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'), + owner: userStub.user1, + ownerId: 'user-id', + deviceId: 'device-id', + originalPath: '/original/path.tif', + checksum: Buffer.from('file hash', 'utf8'), + type: AssetType.Image, + files, + thumbhash: Buffer.from('blablabla', 'base64'), + encodedVideoPath: null, + createdAt: new Date('2023-02-23T05:06:29.716Z'), + updatedAt: new Date('2023-02-23T05:06:29.716Z'), + localDateTime: new Date('2023-02-23T05:06:29.716Z'), + isFavorite: true, + duration: null, + isExternal: false, + livePhotoVideo: null, + livePhotoVideoId: null, + sharedLinks: [], + originalFileName: 'asset-id.tif', + faces: [], + deletedAt: null, + sidecarPath: null, + exifInfo: { + fileSizeInByte: 5000, + projectionType: 'EQUIRECTANGULAR', + } as Exif, + duplicateId: null, + isOffline: false, + updateId: '42', + libraryId: null, + stackId: null, + visibility: AssetVisibility.Timeline, + }), }; diff --git a/server/test/fixtures/media.stub.ts b/server/test/fixtures/media.stub.ts index efbf21d317..727f5ae7cf 100644 --- a/server/test/fixtures/media.stub.ts +++ b/server/test/fixtures/media.stub.ts @@ -261,4 +261,15 @@ export const probeStub = { bitrate: 0, }, }), + videoStreamReserved: Object.freeze({ + ...probeStubDefault, + videoStreams: [ + { + ...probeStubDefaultVideoStream[0], + colorPrimaries: 'reserved', + colorSpace: 'reserved', + colorTransfer: 'reserved', + }, + ], + }), }; diff --git a/server/test/fixtures/notification.stub.ts b/server/test/fixtures/notification.stub.ts new file mode 100644 index 0000000000..b5a436a622 --- /dev/null +++ b/server/test/fixtures/notification.stub.ts @@ -0,0 +1,14 @@ +import { NotificationLevel, NotificationType } from 'src/enum'; + +export const notificationStub = { + albumEvent: { + id: 'notification-album-event', + type: NotificationType.AlbumInvite, + description: 'You have been invited to a shared album', + title: 'Album Invitation', + createdAt: new Date('2024-01-01'), + data: { albumId: 'album-id' }, + level: NotificationLevel.Success, + readAt: null, + }, +}; diff --git a/server/test/medium.factory.ts b/server/test/medium.factory.ts index a169d96322..efcdc59793 100644 --- a/server/test/medium.factory.ts +++ b/server/test/medium.factory.ts @@ -2,6 +2,7 @@ import { Insertable, Kysely } from 'kysely'; import { DateTime } from 'luxon'; import { createHash, randomBytes } from 'node:crypto'; +import { Stats } from 'node:fs'; import { Writable } from 'node:stream'; import { AssetFace } from 'src/database'; import { AuthDto, LoginResponseDto } from 'src/dtos/auth.dto'; @@ -27,23 +28,33 @@ import { EmailRepository } from 'src/repositories/email.repository'; import { EventRepository } from 'src/repositories/event.repository'; import { JobRepository } from 'src/repositories/job.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; +import { MachineLearningRepository } from 'src/repositories/machine-learning.repository'; +import { MapRepository } from 'src/repositories/map.repository'; import { MemoryRepository } from 'src/repositories/memory.repository'; +import { MetadataRepository } from 'src/repositories/metadata.repository'; import { NotificationRepository } from 'src/repositories/notification.repository'; +import { OcrRepository } from 'src/repositories/ocr.repository'; import { PartnerRepository } from 'src/repositories/partner.repository'; import { PersonRepository } from 'src/repositories/person.repository'; +import { PluginRepository } from 'src/repositories/plugin.repository'; import { SearchRepository } from 'src/repositories/search.repository'; import { SessionRepository } from 'src/repositories/session.repository'; +import { SharedLinkAssetRepository } from 'src/repositories/shared-link-asset.repository'; import { SharedLinkRepository } from 'src/repositories/shared-link.repository'; import { StackRepository } from 'src/repositories/stack.repository'; import { StorageRepository } from 'src/repositories/storage.repository'; import { SyncCheckpointRepository } from 'src/repositories/sync-checkpoint.repository'; import { SyncRepository } from 'src/repositories/sync.repository'; import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository'; +import { TagRepository } from 'src/repositories/tag.repository'; +import { TelemetryRepository } from 'src/repositories/telemetry.repository'; import { UserRepository } from 'src/repositories/user.repository'; import { VersionHistoryRepository } from 'src/repositories/version-history.repository'; +import { WorkflowRepository } from 'src/repositories/workflow.repository'; import { DB } from 'src/schema'; import { AlbumTable } from 'src/schema/tables/album.table'; import { AssetExifTable } from 'src/schema/tables/asset-exif.table'; +import { AssetFileTable } from 'src/schema/tables/asset-file.table'; import { AssetJobStatusTable } from 'src/schema/tables/asset-job-status.table'; import { AssetTable } from 'src/schema/tables/asset.table'; import { FaceSearchTable } from 'src/schema/tables/face-search.table'; @@ -51,9 +62,14 @@ import { MemoryTable } from 'src/schema/tables/memory.table'; import { PersonTable } from 'src/schema/tables/person.table'; import { SessionTable } from 'src/schema/tables/session.table'; import { StackTable } from 'src/schema/tables/stack.table'; +import { TagAssetTable } from 'src/schema/tables/tag-asset.table'; +import { TagTable } from 'src/schema/tables/tag.table'; import { UserTable } from 'src/schema/tables/user.table'; import { BASE_SERVICE_DEPENDENCIES, BaseService } from 'src/services/base.service'; +import { MetadataService } from 'src/services/metadata.service'; import { SyncService } from 'src/services/sync.service'; +import { mockEnvData } from 'test/repositories/config.repository.mock'; +import { newTelemetryRepositoryMock } from 'test/repositories/telemetry.repository.mock'; import { factory, newDate, newEmbedding, newUuid } from 'test/small.factory'; import { automock, wait } from 'test/utils'; import { Mocked } from 'vitest'; @@ -163,6 +179,11 @@ export class MediumTestContext { return { asset, result }; } + async newAssetFile(dto: Insertable) { + const result = await this.get(AssetRepository).upsertFile(dto); + return { result }; + } + async newAssetFace(dto: Partial> & { assetId: string }) { const assetFace = mediumFactory.assetFaceInsert(dto); const result = await this.get(PersonRepository).createAssetFace(assetFace); @@ -198,7 +219,7 @@ export class MediumTestContext { async newAlbumUser(dto: { albumId: string; userId: string; role?: AlbumUserRole }) { const { albumId, userId, role = AlbumUserRole.Editor } = dto; - const result = await this.get(AlbumUserRepository).create({ albumsId: albumId, usersId: userId, role }); + const result = await this.get(AlbumUserRepository).create({ albumId, userId, role }); return { albumUser: { albumId, userId, role }, result }; } @@ -238,6 +259,18 @@ export class MediumTestContext { user, }; } + + async newTagAsset(tagBulkAssets: { tagIds: string[]; assetIds: string[] }) { + const tagsAssets: Insertable[] = []; + for (const tagId of tagBulkAssets.tagIds) { + for (const assetId of tagBulkAssets.assetIds) { + tagsAssets.push({ tagId, assetId }); + } + } + + const result = await this.get(TagRepository).upsertAssetIds(tagsAssets); + return { tagsAssets, result }; + } } export class SyncTestContext extends MediumTestContext { @@ -279,6 +312,63 @@ export class SyncTestContext extends MediumTestContext { } } +const mockDate = new Date('2024-06-01T12:00:00.000Z'); +const mockStats = { + mtime: mockDate, + atime: mockDate, + ctime: mockDate, + birthtime: mockDate, + atimeMs: 0, + mtimeMs: 0, + ctimeMs: 0, + birthtimeMs: 0, +}; + +export class ExifTestContext extends MediumTestContext { + constructor(database: Kysely) { + super(MetadataService, { + database, + real: [AssetRepository, AssetJobRepository, MetadataRepository, SystemMetadataRepository, TagRepository], + mock: [ConfigRepository, EventRepository, LoggingRepository, MapRepository, StorageRepository], + }); + + this.getMock(ConfigRepository).getEnv.mockReturnValue(mockEnvData({})); + this.getMock(EventRepository).emit.mockResolvedValue(); + this.getMock(MapRepository).reverseGeocode.mockResolvedValue({ country: null, state: null, city: null }); + this.getMock(StorageRepository).stat.mockResolvedValue(mockStats as Stats); + } + + getMockStats() { + return mockStats; + } + + getGps(assetId: string) { + return this.database + .selectFrom('asset_exif') + .select(['latitude', 'longitude']) + .where('assetId', '=', assetId) + .executeTakeFirstOrThrow(); + } + + getTags(assetId: string) { + return this.database + .selectFrom('tag') + .innerJoin('tag_asset', 'tag.id', 'tag_asset.tagId') + .where('tag_asset.assetId', '=', assetId) + .selectAll() + .execute(); + } + + getDates(assetId: string) { + return this.database + .selectFrom('asset') + .innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId') + .where('id', '=', assetId) + .select(['asset.fileCreatedAt', 'asset.localDateTime', 'asset_exif.dateTimeOriginal', 'asset_exif.timeZone']) + .executeTakeFirstOrThrow(); + } +} + const newRealRepository = (key: ClassConstructor, db: Kysely): T => { switch (key) { case AccessRepository: @@ -289,17 +379,21 @@ const newRealRepository = (key: ClassConstructor, db: Kysely): T => { case AssetJobRepository: case MemoryRepository: case NotificationRepository: + case OcrRepository: case PartnerRepository: case PersonRepository: + case PluginRepository: case SearchRepository: case SessionRepository: case SharedLinkRepository: + case SharedLinkAssetRepository: case StackRepository: case SyncRepository: case SyncCheckpointRepository: case SystemMetadataRepository: case UserRepository: - case VersionHistoryRepository: { + case VersionHistoryRepository: + case WorkflowRepository: { return new key(db); } @@ -316,6 +410,18 @@ const newRealRepository = (key: ClassConstructor, db: Kysely): T => { return new key(LoggingRepository.create()); } + case MetadataRepository: { + return new key(LoggingRepository.create()); + } + + case StorageRepository: { + return new key(LoggingRepository.create()); + } + + case TagRepository: { + return new key(db, LoggingRepository.create()); + } + case LoggingRepository as unknown as ClassConstructor: { return new key() as unknown as T; } @@ -336,17 +442,29 @@ const newMockRepository = (key: ClassConstructor) => { case CryptoRepository: case MemoryRepository: case NotificationRepository: + case OcrRepository: case PartnerRepository: case PersonRepository: + case PluginRepository: case SessionRepository: case SyncRepository: case SyncCheckpointRepository: case SystemMetadataRepository: case UserRepository: - case VersionHistoryRepository: { + case VersionHistoryRepository: + case TagRepository: + case WorkflowRepository: { return automock(key); } + case MapRepository: { + return automock(MapRepository, { args: [undefined, undefined, { setContext: () => {} }] }); + } + + case TelemetryRepository: { + return newTelemetryRepositoryMock(); + } + case DatabaseRepository: { return automock(DatabaseRepository, { args: [undefined, { setContext: () => {} }, { getEnv: () => ({ database: { vectorExtension: '' } }) }], @@ -379,6 +497,10 @@ const newMockRepository = (key: ClassConstructor) => { return automock(LoggingRepository, { args: [undefined, configMock], strict: false }); } + case MachineLearningRepository: { + return automock(MachineLearningRepository, { args: [{ setContext: () => {} }] }); + } + case StorageRepository: { return automock(StorageRepository, { args: [{ setContext: () => {} }] }); } @@ -561,6 +683,23 @@ const memoryInsert = (memory: Partial> = {}) => { return { ...defaults, ...memory, id }; }; +const tagInsert = (tag: Partial>) => { + const id = tag.id || newUuid(); + + const defaults: Insertable = { + id, + userId: '', + value: '', + createdAt: newDate(), + updatedAt: newDate(), + color: '', + parentId: null, + updateId: newUuid(), + }; + + return { ...defaults, ...tag, id }; +}; + class CustomWritable extends Writable { private data = ''; @@ -583,7 +722,7 @@ const syncStream = () => { }; const loginDetails = () => { - return { isSecure: false, clientIp: '', deviceType: '', deviceOS: '' }; + return { isSecure: false, clientIp: '', deviceType: '', deviceOS: '', appVersion: null }; }; const loginResponse = (): LoginResponseDto => { @@ -613,4 +752,5 @@ export const mediumFactory = { memoryInsert, loginDetails, loginResponse, + tagInsert, }; diff --git a/server/test/medium/specs/exif/exif-date-time.spec.ts b/server/test/medium/specs/exif/exif-date-time.spec.ts new file mode 100644 index 0000000000..e46f17855e --- /dev/null +++ b/server/test/medium/specs/exif/exif-date-time.spec.ts @@ -0,0 +1,65 @@ +import { Kysely } from 'kysely'; +import { DateTime } from 'luxon'; +import { resolve } from 'node:path'; +import { DB } from 'src/schema'; +import { ExifTestContext } from 'test/medium.factory'; +import { getKyselyDB } from 'test/utils'; + +let database: Kysely; + +const setup = async (testAssetPath: string) => { + const ctx = new ExifTestContext(database); + + const { user } = await ctx.newUser(); + const originalPath = resolve(`../e2e/test-assets/${testAssetPath}`); + const { asset } = await ctx.newAsset({ ownerId: user.id, originalPath }); + + return { ctx, sut: ctx.sut, asset }; +}; + +beforeAll(async () => { + database = await getKyselyDB(); +}); + +describe('exif date time', () => { + it('should prioritize DateTimeOriginal', async () => { + const { ctx, sut, asset } = await setup('metadata/dates/date-priority-test.jpg'); + + await sut.handleMetadataExtraction({ id: asset.id }); + + await expect(ctx.getDates(asset.id)).resolves.toEqual({ + timeZone: null, + dateTimeOriginal: DateTime.fromISO('2023-02-02T02:00:00.000Z').toJSDate(), + localDateTime: DateTime.fromISO('2023-02-02T02:00:00.000Z').toJSDate(), + fileCreatedAt: DateTime.fromISO('2023-02-02T02:00:00.000Z').toJSDate(), + }); + }); + + it('should extract GPSDateTime with GPS coordinates ', async () => { + const { ctx, sut, asset } = await setup('metadata/dates/gps-datetime.jpg'); + + await sut.handleMetadataExtraction({ id: asset.id }); + + await expect(ctx.getDates(asset.id)).resolves.toEqual({ + timeZone: 'America/Los_Angeles', + dateTimeOriginal: DateTime.fromISO('2023-11-15T12:30:00.000Z').toJSDate(), + localDateTime: DateTime.fromISO('2023-11-15T04:30:00.000Z').toJSDate(), + fileCreatedAt: DateTime.fromISO('2023-11-15T12:30:00.000Z').toJSDate(), + }); + }); + + it('should ignore the TimeCreated tag', async () => { + const { ctx, sut, asset } = await setup('metadata/dates/time-created.jpg'); + + await sut.handleMetadataExtraction({ id: asset.id }); + + const stats = ctx.getMockStats(); + + await expect(ctx.getDates(asset.id)).resolves.toEqual({ + timeZone: null, + dateTimeOriginal: stats.mtime, + localDateTime: stats.mtime, + fileCreatedAt: stats.mtime, + }); + }); +}); diff --git a/server/test/medium/specs/exif/exif-gps.spec.ts b/server/test/medium/specs/exif/exif-gps.spec.ts new file mode 100644 index 0000000000..651321b599 --- /dev/null +++ b/server/test/medium/specs/exif/exif-gps.spec.ts @@ -0,0 +1,31 @@ +import { Kysely } from 'kysely'; +import { resolve } from 'node:path'; +import { DB } from 'src/schema'; +import { ExifTestContext } from 'test/medium.factory'; +import { getKyselyDB } from 'test/utils'; + +let database: Kysely; + +const setup = async (testAssetPath: string) => { + const ctx = new ExifTestContext(database); + + const { user } = await ctx.newUser(); + const originalPath = resolve(`../e2e/test-assets/${testAssetPath}`); + const { asset } = await ctx.newAsset({ ownerId: user.id, originalPath }); + + return { ctx, sut: ctx.sut, asset }; +}; + +beforeAll(async () => { + database = await getKyselyDB(); +}); + +describe('exif gps', () => { + it('should handle empty strings', async () => { + const { ctx, sut, asset } = await setup('metadata/gps-position/empty_gps.jpg'); + + await sut.handleMetadataExtraction({ id: asset.id }); + + await expect(ctx.getGps(asset.id)).resolves.toEqual({ latitude: null, longitude: null }); + }); +}); diff --git a/server/test/medium/specs/exif/exif-tags.spec.ts b/server/test/medium/specs/exif/exif-tags.spec.ts new file mode 100644 index 0000000000..33a81d24b6 --- /dev/null +++ b/server/test/medium/specs/exif/exif-tags.spec.ts @@ -0,0 +1,34 @@ +import { Kysely } from 'kysely'; +import { resolve } from 'node:path'; +import { DB } from 'src/schema'; +import { ExifTestContext } from 'test/medium.factory'; +import { getKyselyDB } from 'test/utils'; + +let database: Kysely; + +const setup = async (testAssetPath: string) => { + const ctx = new ExifTestContext(database); + + const { user } = await ctx.newUser(); + const originalPath = resolve(`../e2e/test-assets/${testAssetPath}`); + const { asset } = await ctx.newAsset({ ownerId: user.id, originalPath }); + + return { ctx, sut: ctx.sut, asset }; +}; + +beforeAll(async () => { + database = await getKyselyDB(); +}); + +describe('exif tags', () => { + it('should detect and regular tags', async () => { + const { ctx, sut, asset } = await setup('metadata/tags/picasa.jpg'); + + await sut.handleMetadataExtraction({ id: asset.id }); + + await expect(ctx.getTags(asset.id)).resolves.toEqual([ + expect.objectContaining({ assetId: asset.id, value: 'Frost', parentId: null }), + expect.objectContaining({ assetId: asset.id, value: 'Yard', parentId: null }), + ]); + }); +}); diff --git a/server/test/medium/specs/services/asset.service.spec.ts b/server/test/medium/specs/services/asset.service.spec.ts index 11343bd6e1..b3fc481708 100644 --- a/server/test/medium/specs/services/asset.service.spec.ts +++ b/server/test/medium/specs/services/asset.service.spec.ts @@ -1,6 +1,14 @@ import { Kysely } from 'kysely'; +import { JobName, SharedLinkType } from 'src/enum'; +import { AccessRepository } from 'src/repositories/access.repository'; +import { AlbumRepository } from 'src/repositories/album.repository'; import { AssetRepository } from 'src/repositories/asset.repository'; +import { JobRepository } from 'src/repositories/job.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; +import { SharedLinkAssetRepository } from 'src/repositories/shared-link-asset.repository'; +import { SharedLinkRepository } from 'src/repositories/shared-link.repository'; +import { StackRepository } from 'src/repositories/stack.repository'; +import { StorageRepository } from 'src/repositories/storage.repository'; import { DB } from 'src/schema'; import { AssetService } from 'src/services/asset.service'; import { newMediumService } from 'test/medium.factory'; @@ -12,8 +20,8 @@ let defaultDatabase: Kysely; const setup = (db?: Kysely) => { return newMediumService(AssetService, { database: db || defaultDatabase, - real: [AssetRepository], - mock: [LoggingRepository], + real: [AssetRepository, AlbumRepository, AccessRepository, SharedLinkAssetRepository, StackRepository], + mock: [LoggingRepository, JobRepository, StorageRepository], }); }; @@ -32,4 +40,166 @@ describe(AssetService.name, () => { await expect(sut.getStatistics(auth, {})).resolves.toEqual({ images: 1, total: 1, videos: 0 }); }); }); + + describe('copy', () => { + it('should copy albums', async () => { + const { sut, ctx } = setup(); + const albumRepo = ctx.get(AlbumRepository); + + const { user } = await ctx.newUser(); + const { asset: oldAsset } = await ctx.newAsset({ ownerId: user.id }); + const { asset: newAsset } = await ctx.newAsset({ ownerId: user.id }); + + const { album } = await ctx.newAlbum({ ownerId: user.id }); + await ctx.newAlbumAsset({ albumId: album.id, assetId: oldAsset.id }); + + const auth = factory.auth({ user: { id: user.id } }); + await sut.copy(auth, { sourceId: oldAsset.id, targetId: newAsset.id }); + + await expect(albumRepo.getAssetIds(album.id, [oldAsset.id, newAsset.id])).resolves.toEqual( + new Set([oldAsset.id, newAsset.id]), + ); + }); + + it('should copy shared links', async () => { + const { sut, ctx } = setup(); + const sharedLinkRepo = ctx.get(SharedLinkRepository); + + const { user } = await ctx.newUser(); + const { asset: oldAsset } = await ctx.newAsset({ ownerId: user.id }); + const { asset: newAsset } = await ctx.newAsset({ ownerId: user.id }); + + await ctx.newExif({ assetId: oldAsset.id, description: 'foo' }); + await ctx.newExif({ assetId: newAsset.id, description: 'bar' }); + + const { id: sharedLinkId } = await sharedLinkRepo.create({ + allowUpload: false, + key: Buffer.from('123'), + type: SharedLinkType.Individual, + userId: user.id, + assetIds: [oldAsset.id], + }); + + const auth = factory.auth({ user: { id: user.id } }); + + await sut.copy(auth, { sourceId: oldAsset.id, targetId: newAsset.id }); + await expect(sharedLinkRepo.get(user.id, sharedLinkId)).resolves.toEqual( + expect.objectContaining({ + assets: [expect.objectContaining({ id: oldAsset.id }), expect.objectContaining({ id: newAsset.id })], + }), + ); + }); + + it('should merge stacks', async () => { + const { sut, ctx } = setup(); + const stackRepo = ctx.get(StackRepository); + + const { user } = await ctx.newUser(); + const { asset: oldAsset } = await ctx.newAsset({ ownerId: user.id }); + const { asset: asset1 } = await ctx.newAsset({ ownerId: user.id }); + + const { asset: newAsset } = await ctx.newAsset({ ownerId: user.id }); + const { asset: asset2 } = await ctx.newAsset({ ownerId: user.id }); + + await ctx.newExif({ assetId: oldAsset.id, description: 'foo' }); + await ctx.newExif({ assetId: asset1.id, description: 'bar' }); + await ctx.newExif({ assetId: newAsset.id, description: 'bar' }); + await ctx.newExif({ assetId: asset2.id, description: 'foo' }); + + await ctx.newStack({ ownerId: user.id }, [oldAsset.id, asset1.id]); + + const { + stack: { id: newStackId }, + } = await ctx.newStack({ ownerId: user.id }, [newAsset.id, asset2.id]); + + const auth = factory.auth({ user: { id: user.id } }); + await sut.copy(auth, { sourceId: oldAsset.id, targetId: newAsset.id }); + + await expect(stackRepo.getById(oldAsset.id)).resolves.toEqual(undefined); + + const newStack = await stackRepo.getById(newStackId); + expect(newStack).toEqual( + expect.objectContaining({ + primaryAssetId: newAsset.id, + assets: expect.arrayContaining([expect.objectContaining({ id: asset2.id })]), + }), + ); + expect(newStack!.assets.length).toEqual(4); + }); + + it('should copy stack', async () => { + const { sut, ctx } = setup(); + const stackRepo = ctx.get(StackRepository); + + const { user } = await ctx.newUser(); + const { asset: oldAsset } = await ctx.newAsset({ ownerId: user.id }); + const { asset: asset1 } = await ctx.newAsset({ ownerId: user.id }); + + const { asset: newAsset } = await ctx.newAsset({ ownerId: user.id }); + + await ctx.newExif({ assetId: oldAsset.id, description: 'foo' }); + await ctx.newExif({ assetId: asset1.id, description: 'bar' }); + await ctx.newExif({ assetId: newAsset.id, description: 'bar' }); + + const { + stack: { id: stackId }, + } = await ctx.newStack({ ownerId: user.id }, [oldAsset.id, asset1.id]); + + const auth = factory.auth({ user: { id: user.id } }); + await sut.copy(auth, { sourceId: oldAsset.id, targetId: newAsset.id }); + + const stack = await stackRepo.getById(stackId); + expect(stack).toEqual( + expect.objectContaining({ + primaryAssetId: oldAsset.id, + assets: expect.arrayContaining([expect.objectContaining({ id: newAsset.id })]), + }), + ); + expect(stack!.assets.length).toEqual(3); + }); + + it('should copy favorite status', async () => { + const { sut, ctx } = setup(); + const assetRepo = ctx.get(AssetRepository); + + const { user } = await ctx.newUser(); + const { asset: oldAsset } = await ctx.newAsset({ ownerId: user.id, isFavorite: true }); + const { asset: newAsset } = await ctx.newAsset({ ownerId: user.id }); + + await ctx.newExif({ assetId: oldAsset.id, description: 'foo' }); + await ctx.newExif({ assetId: newAsset.id, description: 'bar' }); + + const auth = factory.auth({ user: { id: user.id } }); + await sut.copy(auth, { sourceId: oldAsset.id, targetId: newAsset.id }); + + await expect(assetRepo.getById(newAsset.id)).resolves.toEqual(expect.objectContaining({ isFavorite: true })); + }); + + it('should copy sidecar file', async () => { + const { sut, ctx } = setup(); + const storageRepo = ctx.getMock(StorageRepository); + const jobRepo = ctx.getMock(JobRepository); + + storageRepo.copyFile.mockResolvedValue(); + jobRepo.queue.mockResolvedValue(); + + const { user } = await ctx.newUser(); + const { asset: oldAsset } = await ctx.newAsset({ ownerId: user.id, sidecarPath: '/path/to/my/sidecar.xmp' }); + const { asset: newAsset } = await ctx.newAsset({ ownerId: user.id }); + + await ctx.newExif({ assetId: oldAsset.id, description: 'foo' }); + await ctx.newExif({ assetId: newAsset.id, description: 'bar' }); + + const auth = factory.auth({ user: { id: user.id } }); + + await sut.copy(auth, { sourceId: oldAsset.id, targetId: newAsset.id }); + + expect(storageRepo.copyFile).toHaveBeenCalledWith('/path/to/my/sidecar.xmp', `${newAsset.originalPath}.xmp`); + + expect(jobRepo.queue).toHaveBeenCalledWith({ + name: JobName.AssetExtractMetadata, + data: { id: newAsset.id }, + }); + }); + }); }); diff --git a/server/test/medium/specs/services/auth.service.spec.ts b/server/test/medium/specs/services/auth.service.spec.ts index 14ea1451f2..1fc306f790 100644 --- a/server/test/medium/specs/services/auth.service.spec.ts +++ b/server/test/medium/specs/services/auth.service.spec.ts @@ -11,6 +11,7 @@ import { LoggingRepository } from 'src/repositories/logging.repository'; import { SessionRepository } from 'src/repositories/session.repository'; import { StorageRepository } from 'src/repositories/storage.repository'; import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository'; +import { TelemetryRepository } from 'src/repositories/telemetry.repository'; import { UserRepository } from 'src/repositories/user.repository'; import { DB } from 'src/schema'; import { AuthService } from 'src/services/auth.service'; @@ -32,7 +33,7 @@ const setup = (db?: Kysely) => { SystemMetadataRepository, UserRepository, ], - mock: [LoggingRepository, StorageRepository, EventRepository], + mock: [LoggingRepository, StorageRepository, EventRepository, TelemetryRepository], }); }; @@ -43,7 +44,8 @@ beforeAll(async () => { describe(AuthService.name, () => { describe('adminSignUp', () => { it(`should sign up the admin`, async () => { - const { sut } = setup(); + const { sut, ctx } = setup(); + ctx.getMock(EventRepository).emit.mockResolvedValue(); const dto = { name: 'Admin', email: 'admin@immich.cloud', password: 'password' }; await expect(sut.adminSignUp(dto)).resolves.toEqual( @@ -129,6 +131,7 @@ describe(AuthService.name, () => { describe('changePassword', () => { it('should change the password and login with it', async () => { const { sut, ctx } = setup(); + ctx.getMock(EventRepository).emit.mockResolvedValue(); const dto = { password: 'password', newPassword: 'new-password' }; const passwordHashed = await hash(dto.password, 10); const { user } = await ctx.newUser({ password: passwordHashed }); diff --git a/server/test/medium/specs/services/memory.service.spec.ts b/server/test/medium/specs/services/memory.service.spec.ts index 12df2f130e..b3a3da6010 100644 --- a/server/test/medium/specs/services/memory.service.spec.ts +++ b/server/test/medium/specs/services/memory.service.spec.ts @@ -153,6 +153,46 @@ describe(MemoryService.name, () => { ); }); + it('should create a memory from an asset - in advance', async () => { + const { sut, ctx } = setup(); + const assetRepo = ctx.get(AssetRepository); + const memoryRepo = ctx.get(MemoryRepository); + const now = DateTime.fromObject({ year: 2035, month: 2, day: 26 }, { zone: 'utc' }) as DateTime; + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id, localDateTime: now.minus({ years: 1 }).toISO() }); + await Promise.all([ + ctx.newExif({ assetId: asset.id, make: 'Canon' }), + ctx.newJobStatus({ assetId: asset.id }), + assetRepo.upsertFiles([ + { assetId: asset.id, type: AssetFileType.Preview, path: '/path/to/preview.jpg' }, + { assetId: asset.id, type: AssetFileType.Thumbnail, path: '/path/to/thumbnail.jpg' }, + ]), + ]); + + vi.setSystemTime(now.toJSDate()); + await sut.onMemoriesCreate(); + + const memories = await memoryRepo.search(user.id, {}); + expect(memories.length).toBe(1); + expect(memories[0]).toEqual( + expect.objectContaining({ + id: expect.any(String), + createdAt: expect.any(Date), + memoryAt: expect.any(Date), + updatedAt: expect.any(Date), + deletedAt: null, + ownerId: user.id, + assets: expect.arrayContaining([expect.objectContaining({ id: asset.id })]), + isSaved: false, + showAt: now.startOf('day').toJSDate(), + hideAt: now.endOf('day').toJSDate(), + seenAt: null, + type: 'on_this_day', + data: { year: 2034 }, + }), + ); + }); + it('should not generate a memory twice for the same day', async () => { const { sut, ctx } = setup(); const assetRepo = ctx.get(AssetRepository); diff --git a/server/test/medium/specs/services/metadata.service.spec.ts b/server/test/medium/specs/services/metadata.service.spec.ts index 13b9867373..5d44079be5 100644 --- a/server/test/medium/specs/services/metadata.service.spec.ts +++ b/server/test/medium/specs/services/metadata.service.spec.ts @@ -65,42 +65,6 @@ describe(MetadataService.name, () => { timeZone: null, }, }, - { - description: 'should handle no time zone information and server behind UTC', - serverTimeZone: 'America/Los_Angeles', - exifData: { - DateTimeOriginal: '2022:01:01 00:00:00', - }, - expected: { - localDateTime: '2022-01-01T00:00:00.000Z', - dateTimeOriginal: '2022-01-01T08:00:00.000Z', - timeZone: null, - }, - }, - { - description: 'should handle no time zone information and server ahead of UTC', - serverTimeZone: 'Europe/Brussels', - exifData: { - DateTimeOriginal: '2022:01:01 00:00:00', - }, - expected: { - localDateTime: '2022-01-01T00:00:00.000Z', - dateTimeOriginal: '2021-12-31T23:00:00.000Z', - timeZone: null, - }, - }, - { - description: 'should handle no time zone information and server ahead of UTC in the summer', - serverTimeZone: 'Europe/Brussels', - exifData: { - DateTimeOriginal: '2022:06:01 00:00:00', - }, - expected: { - localDateTime: '2022-06-01T00:00:00.000Z', - dateTimeOriginal: '2022-05-31T22:00:00.000Z', - timeZone: null, - }, - }, { description: 'should handle a +13:00 time zone', exifData: { diff --git a/server/test/medium/specs/services/ocr.service.spec.ts b/server/test/medium/specs/services/ocr.service.spec.ts new file mode 100644 index 0000000000..45c34dd09e --- /dev/null +++ b/server/test/medium/specs/services/ocr.service.spec.ts @@ -0,0 +1,243 @@ +import { Kysely } from 'kysely'; +import { AssetFileType, JobStatus } from 'src/enum'; +import { AssetJobRepository } from 'src/repositories/asset-job.repository'; +import { AssetRepository } from 'src/repositories/asset.repository'; +import { ConfigRepository } from 'src/repositories/config.repository'; +import { JobRepository } from 'src/repositories/job.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; +import { MachineLearningRepository } from 'src/repositories/machine-learning.repository'; +import { OcrRepository } from 'src/repositories/ocr.repository'; +import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository'; +import { DB } from 'src/schema'; +import { OcrService } from 'src/services/ocr.service'; +import { newMediumService } from 'test/medium.factory'; +import { getKyselyDB } from 'test/utils'; + +let defaultDatabase: Kysely; + +const setup = (db?: Kysely) => { + return newMediumService(OcrService, { + database: db || defaultDatabase, + real: [AssetRepository, AssetJobRepository, ConfigRepository, OcrRepository, SystemMetadataRepository], + mock: [JobRepository, LoggingRepository, MachineLearningRepository], + }); +}; + +beforeAll(async () => { + defaultDatabase = await getKyselyDB(); +}); + +describe(OcrService.name, () => { + it('should work', () => { + const { sut } = setup(); + expect(sut).toBeDefined(); + }); + + it('should parse asset', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + await ctx.newAssetFile({ assetId: asset.id, type: AssetFileType.Preview, path: 'preview.jpg' }); + + const machineLearningMock = ctx.getMock(MachineLearningRepository); + machineLearningMock.ocr.mockResolvedValue({ + box: [10, 10, 50, 10, 50, 50, 10, 50], + boxScore: [0.99], + text: ['Test OCR'], + textScore: [0.95], + }); + + await expect(sut.handleOcr({ id: asset.id })).resolves.toBe(JobStatus.Success); + + const ocrRepository = ctx.get(OcrRepository); + await expect(ocrRepository.getByAssetId(asset.id)).resolves.toEqual([ + { + assetId: asset.id, + boxScore: 0.99, + id: expect.any(String), + text: 'Test OCR', + textScore: 0.95, + x1: 10, + y1: 10, + x2: 50, + y2: 10, + x3: 50, + y3: 50, + x4: 10, + y4: 50, + }, + ]); + await expect( + ctx.database.selectFrom('ocr_search').selectAll().where('assetId', '=', asset.id).executeTakeFirst(), + ).resolves.toEqual({ + assetId: asset.id, + text: 'Test OCR', + }); + await expect( + ctx.database + .selectFrom('asset_job_status') + .select('asset_job_status.ocrAt') + .where('assetId', '=', asset.id) + .executeTakeFirst(), + ).resolves.toEqual({ ocrAt: expect.any(Date) }); + }); + + it('should handle multiple boxes', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + await ctx.newAssetFile({ assetId: asset.id, type: AssetFileType.Preview, path: 'preview.jpg' }); + + const machineLearningMock = ctx.getMock(MachineLearningRepository); + machineLearningMock.ocr.mockResolvedValue({ + box: Array.from({ length: 8 * 5 }, (_, i) => i), + boxScore: [0.7, 0.67, 0.65, 0.62, 0.6], + text: ['One', 'Two', 'Three', 'Four', 'Five'], + textScore: [0.9, 0.89, 0.88, 0.87, 0.86], + }); + + await expect(sut.handleOcr({ id: asset.id })).resolves.toBe(JobStatus.Success); + + const ocrRepository = ctx.get(OcrRepository); + await expect(ocrRepository.getByAssetId(asset.id)).resolves.toEqual([ + { + assetId: asset.id, + boxScore: 0.7, + id: expect.any(String), + text: 'One', + textScore: 0.9, + x1: 0, + y1: 1, + x2: 2, + y2: 3, + x3: 4, + y3: 5, + x4: 6, + y4: 7, + }, + { + assetId: asset.id, + boxScore: 0.67, + id: expect.any(String), + text: 'Two', + textScore: 0.89, + x1: 8, + y1: 9, + x2: 10, + y2: 11, + x3: 12, + y3: 13, + x4: 14, + y4: 15, + }, + { + assetId: asset.id, + boxScore: 0.65, + id: expect.any(String), + text: 'Three', + textScore: 0.88, + x1: 16, + y1: 17, + x2: 18, + y2: 19, + x3: 20, + y3: 21, + x4: 22, + y4: 23, + }, + { + assetId: asset.id, + boxScore: 0.62, + id: expect.any(String), + text: 'Four', + textScore: 0.87, + x1: 24, + y1: 25, + x2: 26, + y2: 27, + x3: 28, + y3: 29, + x4: 30, + y4: 31, + }, + { + assetId: asset.id, + boxScore: 0.6, + id: expect.any(String), + text: 'Five', + textScore: 0.86, + x1: 32, + y1: 33, + x2: 34, + y2: 35, + x3: 36, + y3: 37, + x4: 38, + y4: 39, + }, + ]); + await expect( + ctx.database.selectFrom('ocr_search').selectAll().where('assetId', '=', asset.id).executeTakeFirst(), + ).resolves.toEqual({ + assetId: asset.id, + text: 'One Two Three Four Five', + }); + await expect( + ctx.database + .selectFrom('asset_job_status') + .select('asset_job_status.ocrAt') + .where('assetId', '=', asset.id) + .executeTakeFirst(), + ).resolves.toEqual({ ocrAt: expect.any(Date) }); + }); + + it('should handle no boxes', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + await ctx.newAssetFile({ assetId: asset.id, type: AssetFileType.Preview, path: 'preview.jpg' }); + + const machineLearningMock = ctx.getMock(MachineLearningRepository); + machineLearningMock.ocr.mockResolvedValue({ box: [], boxScore: [], text: [], textScore: [] }); + + await expect(sut.handleOcr({ id: asset.id })).resolves.toBe(JobStatus.Success); + + const ocrRepository = ctx.get(OcrRepository); + await expect(ocrRepository.getByAssetId(asset.id)).resolves.toEqual([]); + await expect( + ctx.database.selectFrom('ocr_search').selectAll().where('assetId', '=', asset.id).executeTakeFirst(), + ).resolves.toBeUndefined(); + await expect( + ctx.database + .selectFrom('asset_job_status') + .select('asset_job_status.ocrAt') + .where('assetId', '=', asset.id) + .executeTakeFirst(), + ).resolves.toEqual({ ocrAt: expect.any(Date) }); + }); + + it('should update existing results', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + await ctx.newAssetFile({ assetId: asset.id, type: AssetFileType.Preview, path: 'preview.jpg' }); + + const machineLearningMock = ctx.getMock(MachineLearningRepository); + machineLearningMock.ocr.mockResolvedValue({ + box: [10, 10, 50, 10, 50, 50, 10, 50], + boxScore: [0.99], + text: ['Test OCR'], + textScore: [0.95], + }); + await expect(sut.handleOcr({ id: asset.id })).resolves.toBe(JobStatus.Success); + + machineLearningMock.ocr.mockResolvedValue({ box: [], boxScore: [], text: [], textScore: [] }); + await expect(sut.handleOcr({ id: asset.id })).resolves.toBe(JobStatus.Success); + + const ocrRepository = ctx.get(OcrRepository); + await expect(ocrRepository.getByAssetId(asset.id)).resolves.toEqual([]); + await expect( + ctx.database.selectFrom('ocr_search').selectAll().where('assetId', '=', asset.id).executeTakeFirst(), + ).resolves.toBeUndefined(); + }); +}); diff --git a/server/test/medium/specs/services/plugin.service.spec.ts b/server/test/medium/specs/services/plugin.service.spec.ts new file mode 100644 index 0000000000..b70e8e8d54 --- /dev/null +++ b/server/test/medium/specs/services/plugin.service.spec.ts @@ -0,0 +1,308 @@ +import { Kysely } from 'kysely'; +import { PluginContext } from 'src/enum'; +import { AccessRepository } from 'src/repositories/access.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; +import { PluginRepository } from 'src/repositories/plugin.repository'; +import { DB } from 'src/schema'; +import { PluginService } from 'src/services/plugin.service'; +import { newMediumService } from 'test/medium.factory'; +import { getKyselyDB } from 'test/utils'; + +let defaultDatabase: Kysely; +let pluginRepo: PluginRepository; + +const setup = (db?: Kysely) => { + return newMediumService(PluginService, { + database: db || defaultDatabase, + real: [PluginRepository, AccessRepository], + mock: [LoggingRepository], + }); +}; + +beforeAll(async () => { + defaultDatabase = await getKyselyDB(); + pluginRepo = new PluginRepository(defaultDatabase); +}); + +afterEach(async () => { + await defaultDatabase.deleteFrom('plugin').execute(); +}); + +describe(PluginService.name, () => { + describe('getAll', () => { + it('should return empty array when no plugins exist', async () => { + const { sut } = setup(); + + const plugins = await sut.getAll(); + + expect(plugins).toEqual([]); + }); + + it('should return plugin without filters and actions', async () => { + const { sut } = setup(); + + const result = await pluginRepo.loadPlugin( + { + name: 'test-plugin', + title: 'Test Plugin', + description: 'A test plugin', + author: 'Test Author', + version: '1.0.0', + wasm: { path: '/path/to/test.wasm' }, + }, + '/test/base/path', + ); + + const plugins = await sut.getAll(); + + expect(plugins).toHaveLength(1); + expect(plugins[0]).toMatchObject({ + id: result.plugin.id, + name: 'test-plugin', + description: 'A test plugin', + author: 'Test Author', + version: '1.0.0', + filters: [], + actions: [], + }); + }); + + it('should return plugin with filters and actions', async () => { + const { sut } = setup(); + + const result = await pluginRepo.loadPlugin( + { + name: 'full-plugin', + title: 'Full Plugin', + description: 'A plugin with filters and actions', + author: 'Test Author', + version: '1.0.0', + wasm: { path: '/path/to/full.wasm' }, + filters: [ + { + methodName: 'test-filter', + title: 'Test Filter', + description: 'A test filter', + supportedContexts: [PluginContext.Asset], + schema: { type: 'object', properties: {} }, + }, + ], + actions: [ + { + methodName: 'test-action', + title: 'Test Action', + description: 'A test action', + supportedContexts: [PluginContext.Asset], + schema: { type: 'object', properties: {} }, + }, + ], + }, + '/test/base/path', + ); + + const plugins = await sut.getAll(); + + expect(plugins).toHaveLength(1); + expect(plugins[0]).toMatchObject({ + id: result.plugin.id, + name: 'full-plugin', + filters: [ + { + id: result.filters[0].id, + pluginId: result.plugin.id, + methodName: 'test-filter', + title: 'Test Filter', + description: 'A test filter', + supportedContexts: [PluginContext.Asset], + schema: { type: 'object', properties: {} }, + }, + ], + actions: [ + { + id: result.actions[0].id, + pluginId: result.plugin.id, + methodName: 'test-action', + title: 'Test Action', + description: 'A test action', + supportedContexts: [PluginContext.Asset], + schema: { type: 'object', properties: {} }, + }, + ], + }); + }); + + it('should return multiple plugins with their respective filters and actions', async () => { + const { sut } = setup(); + + await pluginRepo.loadPlugin( + { + name: 'plugin-1', + title: 'Plugin 1', + description: 'First plugin', + author: 'Author 1', + version: '1.0.0', + wasm: { path: '/path/to/plugin1.wasm' }, + filters: [ + { + methodName: 'filter-1', + title: 'Filter 1', + description: 'Filter for plugin 1', + supportedContexts: [PluginContext.Asset], + schema: undefined, + }, + ], + }, + '/test/base/path', + ); + + await pluginRepo.loadPlugin( + { + name: 'plugin-2', + title: 'Plugin 2', + description: 'Second plugin', + author: 'Author 2', + version: '2.0.0', + wasm: { path: '/path/to/plugin2.wasm' }, + actions: [ + { + methodName: 'action-2', + title: 'Action 2', + description: 'Action for plugin 2', + supportedContexts: [PluginContext.Album], + schema: undefined, + }, + ], + }, + '/test/base/path', + ); + + const plugins = await sut.getAll(); + + expect(plugins).toHaveLength(2); + expect(plugins[0].name).toBe('plugin-1'); + expect(plugins[0].filters).toHaveLength(1); + expect(plugins[0].actions).toHaveLength(0); + + expect(plugins[1].name).toBe('plugin-2'); + expect(plugins[1].filters).toHaveLength(0); + expect(plugins[1].actions).toHaveLength(1); + }); + + it('should handle plugin with multiple filters and actions', async () => { + const { sut } = setup(); + + await pluginRepo.loadPlugin( + { + name: 'multi-plugin', + title: 'Multi Plugin', + description: 'Plugin with multiple items', + author: 'Test Author', + version: '1.0.0', + wasm: { path: '/path/to/multi.wasm' }, + filters: [ + { + methodName: 'filter-a', + title: 'Filter A', + description: 'First filter', + supportedContexts: [PluginContext.Asset], + schema: undefined, + }, + { + methodName: 'filter-b', + title: 'Filter B', + description: 'Second filter', + supportedContexts: [PluginContext.Album], + schema: undefined, + }, + ], + actions: [ + { + methodName: 'action-x', + title: 'Action X', + description: 'First action', + supportedContexts: [PluginContext.Asset], + schema: undefined, + }, + { + methodName: 'action-y', + title: 'Action Y', + description: 'Second action', + supportedContexts: [PluginContext.Person], + schema: undefined, + }, + ], + }, + '/test/base/path', + ); + + const plugins = await sut.getAll(); + + expect(plugins).toHaveLength(1); + expect(plugins[0].filters).toHaveLength(2); + expect(plugins[0].actions).toHaveLength(2); + }); + }); + + describe('get', () => { + it('should throw error when plugin does not exist', async () => { + const { sut } = setup(); + + await expect(sut.get('00000000-0000-0000-0000-000000000000')).rejects.toThrow('Plugin not found'); + }); + + it('should return single plugin with filters and actions', async () => { + const { sut } = setup(); + + const result = await pluginRepo.loadPlugin( + { + name: 'single-plugin', + title: 'Single Plugin', + description: 'A single plugin', + author: 'Test Author', + version: '1.0.0', + wasm: { path: '/path/to/single.wasm' }, + filters: [ + { + methodName: 'single-filter', + title: 'Single Filter', + description: 'A single filter', + supportedContexts: [PluginContext.Asset], + schema: undefined, + }, + ], + actions: [ + { + methodName: 'single-action', + title: 'Single Action', + description: 'A single action', + supportedContexts: [PluginContext.Asset], + schema: undefined, + }, + ], + }, + '/test/base/path', + ); + + const pluginResult = await sut.get(result.plugin.id); + + expect(pluginResult).toMatchObject({ + id: result.plugin.id, + name: 'single-plugin', + filters: [ + { + id: result.filters[0].id, + methodName: 'single-filter', + title: 'Single Filter', + }, + ], + actions: [ + { + id: result.actions[0].id, + methodName: 'single-action', + title: 'Single Action', + }, + ], + }); + }); + }); +}); diff --git a/server/test/medium/specs/services/shared-link.service.spec.ts b/server/test/medium/specs/services/shared-link.service.spec.ts index 88e7e86df5..acc51374d1 100644 --- a/server/test/medium/specs/services/shared-link.service.spec.ts +++ b/server/test/medium/specs/services/shared-link.service.spec.ts @@ -4,6 +4,7 @@ import { SharedLinkType } from 'src/enum'; import { AccessRepository } from 'src/repositories/access.repository'; import { DatabaseRepository } from 'src/repositories/database.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; +import { SharedLinkAssetRepository } from 'src/repositories/shared-link-asset.repository'; import { SharedLinkRepository } from 'src/repositories/shared-link.repository'; import { StorageRepository } from 'src/repositories/storage.repository'; import { DB } from 'src/schema'; @@ -17,7 +18,7 @@ let defaultDatabase: Kysely; const setup = (db?: Kysely) => { return newMediumService(SharedLinkService, { database: db || defaultDatabase, - real: [AccessRepository, DatabaseRepository, SharedLinkRepository], + real: [AccessRepository, DatabaseRepository, SharedLinkRepository, SharedLinkAssetRepository], mock: [LoggingRepository, StorageRepository], }); }; @@ -62,4 +63,65 @@ describe(SharedLinkService.name, () => { }); }); }); + + it('should share individually assets', async () => { + const { sut, ctx } = setup(); + + const { user } = await ctx.newUser(); + + const assets = await Promise.all([ + ctx.newAsset({ ownerId: user.id }), + ctx.newAsset({ ownerId: user.id }), + ctx.newAsset({ ownerId: user.id }), + ]); + + for (const { asset } of assets) { + await ctx.newExif({ assetId: asset.id, make: 'Canon' }); + } + + const sharedLinkRepo = ctx.get(SharedLinkRepository); + + const sharedLink = await sharedLinkRepo.create({ + key: randomBytes(16), + id: factory.uuid(), + userId: user.id, + allowUpload: false, + type: SharedLinkType.Individual, + assetIds: assets.map(({ asset }) => asset.id), + }); + + await expect(sut.getMine({ user, sharedLink }, {})).resolves.toMatchObject({ + assets: assets.map(({ asset }) => expect.objectContaining({ id: asset.id })), + }); + }); + + it('should remove individually shared asset', async () => { + const { sut, ctx } = setup(); + + const { user } = await ctx.newUser(); + const auth = factory.auth({ user }); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + await ctx.newExif({ assetId: asset.id, make: 'Canon' }); + + const sharedLinkRepo = ctx.get(SharedLinkRepository); + + const sharedLink = await sharedLinkRepo.create({ + key: randomBytes(16), + id: factory.uuid(), + userId: user.id, + allowUpload: false, + type: SharedLinkType.Individual, + assetIds: [asset.id], + }); + + await expect(sut.getMine({ user, sharedLink }, {})).resolves.toMatchObject({ + assets: [expect.objectContaining({ id: asset.id })], + }); + + await sut.removeAssets(auth, sharedLink.id, { + assetIds: [asset.id], + }); + + await expect(sut.getMine({ user, sharedLink }, {})).resolves.toHaveProperty('assets', []); + }); }); diff --git a/server/test/medium/specs/services/tag.service.spec.ts b/server/test/medium/specs/services/tag.service.spec.ts new file mode 100644 index 0000000000..2ec498e56d --- /dev/null +++ b/server/test/medium/specs/services/tag.service.spec.ts @@ -0,0 +1,116 @@ +import { Kysely } from 'kysely'; +import { JobStatus } from 'src/enum'; +import { AccessRepository } from 'src/repositories/access.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; +import { TagRepository } from 'src/repositories/tag.repository'; +import { DB } from 'src/schema'; +import { TagService } from 'src/services/tag.service'; +import { upsertTags } from 'src/utils/tag'; +import { newMediumService } from 'test/medium.factory'; +import { getKyselyDB } from 'test/utils'; + +let defaultDatabase: Kysely; + +const setup = (db?: Kysely) => { + return newMediumService(TagService, { + database: db || defaultDatabase, + real: [TagRepository, AccessRepository], + mock: [LoggingRepository], + }); +}; + +beforeAll(async () => { + defaultDatabase = await getKyselyDB(); +}); + +describe(TagService.name, () => { + describe('deleteEmptyTags', () => { + it('single tag exists, not connected to any assets, and is deleted', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const tagRepo = ctx.get(TagRepository); + const [tag] = await upsertTags(tagRepo, { userId: user.id, tags: ['tag-1'] }); + + await expect(tagRepo.getByValue(user.id, 'tag-1')).resolves.toEqual(expect.objectContaining({ id: tag.id })); + await expect(sut.handleTagCleanup()).resolves.toBe(JobStatus.Success); + await expect(tagRepo.getByValue(user.id, 'tag-1')).resolves.toBeUndefined(); + }); + + it('single tag exists, connected to one asset, and is not deleted', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + const tagRepo = ctx.get(TagRepository); + const [tag] = await upsertTags(tagRepo, { userId: user.id, tags: ['tag-1'] }); + + await ctx.newTagAsset({ tagIds: [tag.id], assetIds: [asset.id] }); + + await expect(tagRepo.getByValue(user.id, 'tag-1')).resolves.toEqual(expect.objectContaining({ id: tag.id })); + await expect(sut.handleTagCleanup()).resolves.toBe(JobStatus.Success); + await expect(tagRepo.getByValue(user.id, 'tag-1')).resolves.toEqual(expect.objectContaining({ id: tag.id })); + }); + + it('hierarchical tag exists, and the parent is connected to an asset, and the child is deleted', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + const tagRepo = ctx.get(TagRepository); + const [parentTag, childTag] = await upsertTags(tagRepo, { userId: user.id, tags: ['parent', 'parent/child'] }); + + await ctx.newTagAsset({ tagIds: [parentTag.id], assetIds: [asset.id] }); + + await expect(tagRepo.getByValue(user.id, 'parent')).resolves.toEqual( + expect.objectContaining({ id: parentTag.id }), + ); + await expect(tagRepo.getByValue(user.id, 'parent/child')).resolves.toEqual( + expect.objectContaining({ id: childTag.id }), + ); + await expect(sut.handleTagCleanup()).resolves.toBe(JobStatus.Success); + await expect(tagRepo.getByValue(user.id, 'parent')).resolves.toEqual( + expect.objectContaining({ id: parentTag.id }), + ); + await expect(tagRepo.getByValue(user.id, 'parent/child')).resolves.toBeUndefined(); + }); + + it('hierarchical tag exists, and only the child is connected to an asset, and nothing is deleted', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id }); + const tagRepo = ctx.get(TagRepository); + const [parentTag, childTag] = await upsertTags(tagRepo, { userId: user.id, tags: ['parent', 'parent/child'] }); + + await ctx.newTagAsset({ tagIds: [childTag.id], assetIds: [asset.id] }); + + await expect(tagRepo.getByValue(user.id, 'parent')).resolves.toEqual( + expect.objectContaining({ id: parentTag.id }), + ); + await expect(tagRepo.getByValue(user.id, 'parent/child')).resolves.toEqual( + expect.objectContaining({ id: childTag.id }), + ); + await expect(sut.handleTagCleanup()).resolves.toBe(JobStatus.Success); + await expect(tagRepo.getByValue(user.id, 'parent')).resolves.toEqual( + expect.objectContaining({ id: parentTag.id }), + ); + await expect(tagRepo.getByValue(user.id, 'parent/child')).resolves.toEqual( + expect.objectContaining({ id: childTag.id }), + ); + }); + + it('hierarchical tag exists, and neither parent nor child is connected to an asset, and both are deleted', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const tagRepo = ctx.get(TagRepository); + const [parentTag, childTag] = await upsertTags(tagRepo, { userId: user.id, tags: ['parent', 'parent/child'] }); + + await expect(tagRepo.getByValue(user.id, 'parent')).resolves.toEqual( + expect.objectContaining({ id: parentTag.id }), + ); + await expect(tagRepo.getByValue(user.id, 'parent/child')).resolves.toEqual( + expect.objectContaining({ id: childTag.id }), + ); + await expect(sut.handleTagCleanup()).resolves.toBe(JobStatus.Success); + await expect(tagRepo.getByValue(user.id, 'parent/child')).resolves.toBeUndefined(); + await expect(tagRepo.getByValue(user.id, 'parent')).resolves.toBeUndefined(); + }); + }); +}); diff --git a/server/test/medium/specs/services/timeline.service.spec.ts b/server/test/medium/specs/services/timeline.service.spec.ts index fa4a75e869..eaca4dcc14 100644 --- a/server/test/medium/specs/services/timeline.service.spec.ts +++ b/server/test/medium/specs/services/timeline.service.spec.ts @@ -4,6 +4,7 @@ import { AssetVisibility } from 'src/enum'; import { AccessRepository } from 'src/repositories/access.repository'; import { AssetRepository } from 'src/repositories/asset.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; +import { PartnerRepository } from 'src/repositories/partner.repository'; import { DB } from 'src/schema'; import { TimelineService } from 'src/services/timeline.service'; import { newMediumService } from 'test/medium.factory'; @@ -15,7 +16,7 @@ let defaultDatabase: Kysely; const setup = (db?: Kysely) => { return newMediumService(TimelineService, { database: db || defaultDatabase, - real: [AssetRepository, AccessRepository], + real: [AssetRepository, AccessRepository, PartnerRepository], mock: [LoggingRepository], }); }; @@ -155,5 +156,54 @@ describe(TimelineService.name, () => { const response = JSON.parse(rawResponse); expect(response).toEqual(expect.objectContaining({ isTrashed: [true] })); }); + + it('should return false for favorite status unless asset owner', async () => { + const { sut, ctx } = setup(); + const [{ asset: asset1 }, { asset: asset2 }] = await Promise.all([ + ctx.newUser().then(async ({ user }) => { + const result = await ctx.newAsset({ + ownerId: user.id, + fileCreatedAt: new Date('1970-02-12'), + localDateTime: new Date('1970-02-12'), + isFavorite: true, + }); + await ctx.newExif({ assetId: result.asset.id, make: 'Canon' }); + return result; + }), + ctx.newUser().then(async ({ user }) => { + const result = await ctx.newAsset({ + ownerId: user.id, + fileCreatedAt: new Date('1970-02-13'), + localDateTime: new Date('1970-02-13'), + isFavorite: true, + }); + await ctx.newExif({ assetId: result.asset.id, make: 'Canon' }); + return result; + }), + ]); + + await Promise.all([ + ctx.newPartner({ sharedById: asset1.ownerId, sharedWithId: asset2.ownerId }), + ctx.newPartner({ sharedById: asset2.ownerId, sharedWithId: asset1.ownerId }), + ]); + + const auth1 = factory.auth({ user: { id: asset1.ownerId } }); + const rawResponse1 = await sut.getTimeBucket(auth1, { + timeBucket: '1970-02-01', + withPartners: true, + visibility: AssetVisibility.Timeline, + }); + const response1 = JSON.parse(rawResponse1); + expect(response1).toEqual(expect.objectContaining({ id: [asset2.id, asset1.id], isFavorite: [false, true] })); + + const auth2 = factory.auth({ user: { id: asset2.ownerId } }); + const rawResponse2 = await sut.getTimeBucket(auth2, { + timeBucket: '1970-02-01', + withPartners: true, + visibility: AssetVisibility.Timeline, + }); + const response2 = JSON.parse(rawResponse2); + expect(response2).toEqual(expect.objectContaining({ id: [asset2.id, asset1.id], isFavorite: [true, false] })); + }); }); }); diff --git a/server/test/medium/specs/services/user.service.spec.ts b/server/test/medium/specs/services/user.service.spec.ts index 7643be292e..24a06404b1 100644 --- a/server/test/medium/specs/services/user.service.spec.ts +++ b/server/test/medium/specs/services/user.service.spec.ts @@ -3,6 +3,7 @@ import { DateTime } from 'luxon'; import { ImmichEnvironment, JobName, JobStatus } from 'src/enum'; import { ConfigRepository } from 'src/repositories/config.repository'; import { CryptoRepository } from 'src/repositories/crypto.repository'; +import { EventRepository } from 'src/repositories/event.repository'; import { JobRepository } from 'src/repositories/job.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository'; @@ -21,7 +22,7 @@ const setup = (db?: Kysely) => { return newMediumService(UserService, { database: db || defaultDatabase, real: [CryptoRepository, ConfigRepository, SystemMetadataRepository, UserRepository], - mock: [LoggingRepository, JobRepository], + mock: [LoggingRepository, JobRepository, EventRepository], }); }; @@ -34,7 +35,8 @@ beforeAll(async () => { describe(UserService.name, () => { describe('create', () => { it('should create a user', async () => { - const { sut } = setup(); + const { sut, ctx } = setup(); + ctx.getMock(EventRepository).emit.mockResolvedValue(); const user = mediumFactory.userInsert(); await expect(sut.createUser({ name: user.name, email: user.email })).resolves.toEqual( expect.objectContaining({ name: user.name, email: user.email }), @@ -42,14 +44,16 @@ describe(UserService.name, () => { }); it('should reject user with duplicate email', async () => { - const { sut } = setup(); + const { sut, ctx } = setup(); + ctx.getMock(EventRepository).emit.mockResolvedValue(); const user = mediumFactory.userInsert(); await expect(sut.createUser({ email: user.email })).resolves.toMatchObject({ email: user.email }); await expect(sut.createUser({ email: user.email })).rejects.toThrow('User exists'); }); it('should not return password', async () => { - const { sut } = setup(); + const { sut, ctx } = setup(); + ctx.getMock(EventRepository).emit.mockResolvedValue(); const dto = mediumFactory.userInsert({ password: 'password' }); const user = await sut.createUser({ email: dto.email, password: 'password' }); expect((user as any).password).toBeUndefined(); diff --git a/server/test/medium/specs/services/workflow.service.spec.ts b/server/test/medium/specs/services/workflow.service.spec.ts new file mode 100644 index 0000000000..af12019ef6 --- /dev/null +++ b/server/test/medium/specs/services/workflow.service.spec.ts @@ -0,0 +1,697 @@ +import { Kysely } from 'kysely'; +import { PluginContext, PluginTriggerType } from 'src/enum'; +import { AccessRepository } from 'src/repositories/access.repository'; +import { LoggingRepository } from 'src/repositories/logging.repository'; +import { PluginRepository } from 'src/repositories/plugin.repository'; +import { WorkflowRepository } from 'src/repositories/workflow.repository'; +import { DB } from 'src/schema'; +import { WorkflowService } from 'src/services/workflow.service'; +import { newMediumService } from 'test/medium.factory'; +import { getKyselyDB } from 'test/utils'; + +let defaultDatabase: Kysely; + +const setup = (db?: Kysely) => { + return newMediumService(WorkflowService, { + database: db || defaultDatabase, + real: [WorkflowRepository, PluginRepository, AccessRepository], + mock: [LoggingRepository], + }); +}; + +beforeAll(async () => { + defaultDatabase = await getKyselyDB(); +}); + +describe(WorkflowService.name, () => { + let testPluginId: string; + let testFilterId: string; + let testActionId: string; + + beforeAll(async () => { + // Create a test plugin with filters and actions once for all tests + const pluginRepo = new PluginRepository(defaultDatabase); + const result = await pluginRepo.loadPlugin( + { + name: 'test-core-plugin', + title: 'Test Core Plugin', + description: 'A test core plugin for workflow tests', + author: 'Test Author', + version: '1.0.0', + wasm: { + path: '/test/path.wasm', + }, + filters: [ + { + methodName: 'test-filter', + title: 'Test Filter', + description: 'A test filter', + supportedContexts: [PluginContext.Asset], + schema: undefined, + }, + ], + actions: [ + { + methodName: 'test-action', + title: 'Test Action', + description: 'A test action', + supportedContexts: [PluginContext.Asset], + schema: undefined, + }, + ], + }, + '/plugins/test-core-plugin', + ); + + testPluginId = result.plugin.id; + testFilterId = result.filters[0].id; + testActionId = result.actions[0].id; + }); + + afterAll(async () => { + await defaultDatabase.deleteFrom('plugin').where('id', '=', testPluginId).execute(); + }); + + describe('create', () => { + it('should create a workflow without filters or actions', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + + const auth = { user: { id: user.id } } as any; + + const workflow = await sut.create(auth, { + triggerType: PluginTriggerType.AssetCreate, + name: 'test-workflow', + description: 'A test workflow', + enabled: true, + filters: [], + actions: [], + }); + + expect(workflow).toMatchObject({ + id: expect.any(String), + ownerId: user.id, + triggerType: PluginTriggerType.AssetCreate, + name: 'test-workflow', + description: 'A test workflow', + enabled: true, + filters: [], + actions: [], + }); + }); + + it('should create a workflow with filters and actions', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const auth = { user: { id: user.id } } as any; + + const workflow = await sut.create(auth, { + triggerType: PluginTriggerType.AssetCreate, + name: 'test-workflow-with-relations', + description: 'A test workflow with filters and actions', + enabled: true, + filters: [ + { + filterId: testFilterId, + filterConfig: { key: 'value' }, + }, + ], + actions: [ + { + actionId: testActionId, + actionConfig: { action: 'test' }, + }, + ], + }); + + expect(workflow).toMatchObject({ + id: expect.any(String), + ownerId: user.id, + triggerType: PluginTriggerType.AssetCreate, + name: 'test-workflow-with-relations', + enabled: true, + }); + + expect(workflow.filters).toHaveLength(1); + expect(workflow.filters[0]).toMatchObject({ + id: expect.any(String), + workflowId: workflow.id, + filterId: testFilterId, + filterConfig: { key: 'value' }, + order: 0, + }); + + expect(workflow.actions).toHaveLength(1); + expect(workflow.actions[0]).toMatchObject({ + id: expect.any(String), + workflowId: workflow.id, + actionId: testActionId, + actionConfig: { action: 'test' }, + order: 0, + }); + }); + + it('should throw error when creating workflow with invalid filter', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const auth = { user: { id: user.id } } as any; + + await expect( + sut.create(auth, { + triggerType: PluginTriggerType.AssetCreate, + name: 'invalid-workflow', + description: 'A workflow with invalid filter', + enabled: true, + filters: [ + { + filterId: '66da82df-e424-4bf4-b6f3-5d8e71620dae', + filterConfig: { key: 'value' }, + }, + ], + actions: [], + }), + ).rejects.toThrow('Invalid filter ID'); + }); + + it('should throw error when creating workflow with invalid action', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const auth = { user: { id: user.id } } as any; + + await expect( + sut.create(auth, { + triggerType: PluginTriggerType.AssetCreate, + name: 'invalid-workflow', + description: 'A workflow with invalid action', + enabled: true, + filters: [], + actions: [ + { + actionId: '66da82df-e424-4bf4-b6f3-5d8e71620dae', + actionConfig: { action: 'test' }, + }, + ], + }), + ).rejects.toThrow('Invalid action ID'); + }); + + it('should throw error when filter does not support trigger context', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const auth = { user: { id: user.id } } as any; + + // Create a plugin with a filter that only supports Album context + const pluginRepo = new PluginRepository(defaultDatabase); + const result = await pluginRepo.loadPlugin( + { + name: 'album-only-plugin', + title: 'Album Only Plugin', + description: 'Plugin with album-only filter', + author: 'Test Author', + version: '1.0.0', + wasm: { path: '/test/album-plugin.wasm' }, + filters: [ + { + methodName: 'album-filter', + title: 'Album Filter', + description: 'A filter that only works with albums', + supportedContexts: [PluginContext.Album], + schema: undefined, + }, + ], + }, + '/plugins/test-core-plugin', + ); + + await expect( + sut.create(auth, { + triggerType: PluginTriggerType.AssetCreate, + name: 'invalid-context-workflow', + description: 'A workflow with context mismatch', + enabled: true, + filters: [{ filterId: result.filters[0].id }], + actions: [], + }), + ).rejects.toThrow('does not support asset context'); + }); + + it('should throw error when action does not support trigger context', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const auth = { user: { id: user.id } } as any; + + // Create a plugin with an action that only supports Person context + const pluginRepo = new PluginRepository(defaultDatabase); + const result = await pluginRepo.loadPlugin( + { + name: 'person-only-plugin', + title: 'Person Only Plugin', + description: 'Plugin with person-only action', + author: 'Test Author', + version: '1.0.0', + wasm: { path: '/test/person-plugin.wasm' }, + actions: [ + { + methodName: 'person-action', + title: 'Person Action', + description: 'An action that only works with persons', + supportedContexts: [PluginContext.Person], + schema: undefined, + }, + ], + }, + '/plugins/test-core-plugin', + ); + + await expect( + sut.create(auth, { + triggerType: PluginTriggerType.AssetCreate, + name: 'invalid-context-workflow', + description: 'A workflow with context mismatch', + enabled: true, + filters: [], + actions: [{ actionId: result.actions[0].id }], + }), + ).rejects.toThrow('does not support asset context'); + }); + + it('should create workflow with multiple filters and actions in correct order', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const auth = { user: { id: user.id } } as any; + + const workflow = await sut.create(auth, { + triggerType: PluginTriggerType.AssetCreate, + name: 'multi-step-workflow', + description: 'A workflow with multiple filters and actions', + enabled: true, + filters: [ + { filterId: testFilterId, filterConfig: { step: 1 } }, + { filterId: testFilterId, filterConfig: { step: 2 } }, + ], + actions: [ + { actionId: testActionId, actionConfig: { step: 1 } }, + { actionId: testActionId, actionConfig: { step: 2 } }, + { actionId: testActionId, actionConfig: { step: 3 } }, + ], + }); + + expect(workflow.filters).toHaveLength(2); + expect(workflow.filters[0].order).toBe(0); + expect(workflow.filters[0].filterConfig).toEqual({ step: 1 }); + expect(workflow.filters[1].order).toBe(1); + expect(workflow.filters[1].filterConfig).toEqual({ step: 2 }); + + expect(workflow.actions).toHaveLength(3); + expect(workflow.actions[0].order).toBe(0); + expect(workflow.actions[1].order).toBe(1); + expect(workflow.actions[2].order).toBe(2); + }); + }); + + describe('getAll', () => { + it('should return all workflows for a user', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const auth = { user: { id: user.id } } as any; + + const workflow1 = await sut.create(auth, { + triggerType: PluginTriggerType.AssetCreate, + name: 'workflow-1', + description: 'First workflow', + enabled: true, + filters: [], + actions: [], + }); + + const workflow2 = await sut.create(auth, { + triggerType: PluginTriggerType.AssetCreate, + name: 'workflow-2', + description: 'Second workflow', + enabled: false, + filters: [], + actions: [], + }); + + const workflows = await sut.getAll(auth); + + expect(workflows).toHaveLength(2); + expect(workflows).toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: workflow1.id, name: 'workflow-1' }), + expect.objectContaining({ id: workflow2.id, name: 'workflow-2' }), + ]), + ); + }); + + it('should return empty array when user has no workflows', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const auth = { user: { id: user.id } } as any; + + const workflows = await sut.getAll(auth); + + expect(workflows).toEqual([]); + }); + + it('should not return workflows from other users', async () => { + const { sut, ctx } = setup(); + const { user: user1 } = await ctx.newUser(); + const { user: user2 } = await ctx.newUser(); + const auth1 = { user: { id: user1.id } } as any; + const auth2 = { user: { id: user2.id } } as any; + + await sut.create(auth1, { + triggerType: PluginTriggerType.AssetCreate, + name: 'user1-workflow', + description: 'User 1 workflow', + enabled: true, + filters: [], + actions: [], + }); + + const user2Workflows = await sut.getAll(auth2); + + expect(user2Workflows).toEqual([]); + }); + }); + + describe('get', () => { + it('should return a specific workflow by id', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const auth = { user: { id: user.id } } as any; + + const created = await sut.create(auth, { + triggerType: PluginTriggerType.AssetCreate, + name: 'test-workflow', + description: 'A test workflow', + enabled: true, + filters: [{ filterId: testFilterId, filterConfig: { key: 'value' } }], + actions: [{ actionId: testActionId, actionConfig: { action: 'test' } }], + }); + + const workflow = await sut.get(auth, created.id); + + expect(workflow).toMatchObject({ + id: created.id, + name: 'test-workflow', + description: 'A test workflow', + enabled: true, + }); + expect(workflow.filters).toHaveLength(1); + expect(workflow.actions).toHaveLength(1); + }); + + it('should throw error when workflow does not exist', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const auth = { user: { id: user.id } } as any; + + await expect(sut.get(auth, '66da82df-e424-4bf4-b6f3-5d8e71620dae')).rejects.toThrow(); + }); + + it('should throw error when user does not have access to workflow', async () => { + const { sut, ctx } = setup(); + const { user: user1 } = await ctx.newUser(); + const { user: user2 } = await ctx.newUser(); + const auth1 = { user: { id: user1.id } } as any; + const auth2 = { user: { id: user2.id } } as any; + + const workflow = await sut.create(auth1, { + triggerType: PluginTriggerType.AssetCreate, + name: 'private-workflow', + description: 'Private workflow', + enabled: true, + filters: [], + actions: [], + }); + + await expect(sut.get(auth2, workflow.id)).rejects.toThrow(); + }); + }); + + describe('update', () => { + it('should update workflow basic fields', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const auth = { user: { id: user.id } } as any; + + const created = await sut.create(auth, { + triggerType: PluginTriggerType.AssetCreate, + name: 'original-workflow', + description: 'Original description', + enabled: true, + filters: [], + actions: [], + }); + + const updated = await sut.update(auth, created.id, { + name: 'updated-workflow', + description: 'Updated description', + enabled: false, + }); + + expect(updated).toMatchObject({ + id: created.id, + name: 'updated-workflow', + description: 'Updated description', + enabled: false, + }); + }); + + it('should update workflow filters', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const auth = { user: { id: user.id } } as any; + + const created = await sut.create(auth, { + triggerType: PluginTriggerType.AssetCreate, + name: 'test-workflow', + description: 'Test', + enabled: true, + filters: [{ filterId: testFilterId, filterConfig: { old: 'config' } }], + actions: [], + }); + + const updated = await sut.update(auth, created.id, { + filters: [ + { filterId: testFilterId, filterConfig: { new: 'config' } }, + { filterId: testFilterId, filterConfig: { second: 'filter' } }, + ], + }); + + expect(updated.filters).toHaveLength(2); + expect(updated.filters[0].filterConfig).toEqual({ new: 'config' }); + expect(updated.filters[1].filterConfig).toEqual({ second: 'filter' }); + }); + + it('should update workflow actions', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const auth = { user: { id: user.id } } as any; + + const created = await sut.create(auth, { + triggerType: PluginTriggerType.AssetCreate, + name: 'test-workflow', + description: 'Test', + enabled: true, + filters: [], + actions: [{ actionId: testActionId, actionConfig: { old: 'config' } }], + }); + + const updated = await sut.update(auth, created.id, { + actions: [ + { actionId: testActionId, actionConfig: { new: 'config' } }, + { actionId: testActionId, actionConfig: { second: 'action' } }, + ], + }); + + expect(updated.actions).toHaveLength(2); + expect(updated.actions[0].actionConfig).toEqual({ new: 'config' }); + expect(updated.actions[1].actionConfig).toEqual({ second: 'action' }); + }); + + it('should clear filters when updated with empty array', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const auth = { user: { id: user.id } } as any; + + const created = await sut.create(auth, { + triggerType: PluginTriggerType.AssetCreate, + name: 'test-workflow', + description: 'Test', + enabled: true, + filters: [{ filterId: testFilterId, filterConfig: { key: 'value' } }], + actions: [], + }); + + const updated = await sut.update(auth, created.id, { + filters: [], + }); + + expect(updated.filters).toHaveLength(0); + }); + + it('should throw error when no fields to update', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const auth = { user: { id: user.id } } as any; + + const created = await sut.create(auth, { + triggerType: PluginTriggerType.AssetCreate, + name: 'test-workflow', + description: 'Test', + enabled: true, + filters: [], + actions: [], + }); + + await expect(sut.update(auth, created.id, {})).rejects.toThrow('No fields to update'); + }); + + it('should throw error when updating non-existent workflow', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const auth = { user: { id: user.id } } as any; + + await expect( + sut.update(auth, 'non-existent-id', { + name: 'updated-name', + }), + ).rejects.toThrow(); + }); + + it('should throw error when user does not have access to update workflow', async () => { + const { sut, ctx } = setup(); + const { user: user1 } = await ctx.newUser(); + const { user: user2 } = await ctx.newUser(); + const auth1 = { user: { id: user1.id } } as any; + const auth2 = { user: { id: user2.id } } as any; + + const workflow = await sut.create(auth1, { + triggerType: PluginTriggerType.AssetCreate, + name: 'private-workflow', + description: 'Private', + enabled: true, + filters: [], + actions: [], + }); + + await expect( + sut.update(auth2, workflow.id, { + name: 'hacked-workflow', + }), + ).rejects.toThrow(); + }); + + it('should throw error when updating with invalid filter', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const auth = { user: { id: user.id } } as any; + + const created = await sut.create(auth, { + triggerType: PluginTriggerType.AssetCreate, + name: 'test-workflow', + description: 'Test', + enabled: true, + filters: [], + actions: [], + }); + + await expect( + sut.update(auth, created.id, { + filters: [{ filterId: 'invalid-filter-id', filterConfig: {} }], + }), + ).rejects.toThrow(); + }); + + it('should throw error when updating with invalid action', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const auth = { user: { id: user.id } } as any; + + const created = await sut.create(auth, { + triggerType: PluginTriggerType.AssetCreate, + name: 'test-workflow', + description: 'Test', + enabled: true, + filters: [], + actions: [], + }); + + await expect( + sut.update(auth, created.id, { + actions: [{ actionId: 'invalid-action-id', actionConfig: {} }], + }), + ).rejects.toThrow(); + }); + }); + + describe('delete', () => { + it('should delete a workflow', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const auth = { user: { id: user.id } } as any; + + const workflow = await sut.create(auth, { + triggerType: PluginTriggerType.AssetCreate, + name: 'test-workflow', + description: 'Test', + enabled: true, + filters: [], + actions: [], + }); + + await sut.delete(auth, workflow.id); + + await expect(sut.get(auth, workflow.id)).rejects.toThrow('Not found or no workflow.read access'); + }); + + it('should delete workflow with filters and actions', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const auth = { user: { id: user.id } } as any; + + const workflow = await sut.create(auth, { + triggerType: PluginTriggerType.AssetCreate, + name: 'test-workflow', + description: 'Test', + enabled: true, + filters: [{ filterId: testFilterId, filterConfig: {} }], + actions: [{ actionId: testActionId, actionConfig: {} }], + }); + + await sut.delete(auth, workflow.id); + + await expect(sut.get(auth, workflow.id)).rejects.toThrow('Not found or no workflow.read access'); + }); + + it('should throw error when deleting non-existent workflow', async () => { + const { sut, ctx } = setup(); + const { user } = await ctx.newUser(); + const auth = { user: { id: user.id } } as any; + + await expect(sut.delete(auth, 'non-existent-id')).rejects.toThrow(); + }); + + it('should throw error when user does not have access to delete workflow', async () => { + const { sut, ctx } = setup(); + const { user: user1 } = await ctx.newUser(); + const { user: user2 } = await ctx.newUser(); + const auth1 = { user: { id: user1.id } } as any; + const auth2 = { user: { id: user2.id } } as any; + + const workflow = await sut.create(auth1, { + triggerType: PluginTriggerType.AssetCreate, + name: 'private-workflow', + description: 'Private', + enabled: true, + filters: [], + actions: [], + }); + + await expect(sut.delete(auth2, workflow.id)).rejects.toThrow(); + }); + }); +}); diff --git a/server/test/medium/specs/sync/sync-album-user.spec.ts b/server/test/medium/specs/sync/sync-album-user.spec.ts index d779ffd9f3..4970995d28 100644 --- a/server/test/medium/specs/sync/sync-album-user.spec.ts +++ b/server/test/medium/specs/sync/sync-album-user.spec.ts @@ -74,7 +74,7 @@ describe(SyncRequestType.AlbumUsersV1, () => { await ctx.syncAckAll(auth, response); await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumUsersV1]); - await albumUserRepo.update({ albumsId: album.id, usersId: user1.id }, { role: AlbumUserRole.Viewer }); + await albumUserRepo.update({ albumId: album.id, userId: user1.id }, { role: AlbumUserRole.Viewer }); const newResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]); expect(newResponse).toEqual([ { @@ -104,7 +104,7 @@ describe(SyncRequestType.AlbumUsersV1, () => { await ctx.syncAckAll(auth, response); await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumUsersV1]); - await albumUserRepo.delete({ albumsId: album.id, usersId: user1.id }); + await albumUserRepo.delete({ albumId: album.id, userId: user1.id }); const newResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]); expect(newResponse).toEqual([ { @@ -171,7 +171,7 @@ describe(SyncRequestType.AlbumUsersV1, () => { await ctx.syncAckAll(auth, response); await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumUsersV1]); - await albumUserRepo.update({ albumsId: album.id, usersId: user.id }, { role: AlbumUserRole.Viewer }); + await albumUserRepo.update({ albumId: album.id, userId: user.id }, { role: AlbumUserRole.Viewer }); const newResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]); expect(newResponse).toEqual([ { @@ -208,7 +208,7 @@ describe(SyncRequestType.AlbumUsersV1, () => { await ctx.syncAckAll(auth, response); await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumUsersV1]); - await albumUserRepo.delete({ albumsId: album.id, usersId: user.id }); + await albumUserRepo.delete({ albumId: album.id, userId: user.id }); const newResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]); expect(newResponse).toEqual([ diff --git a/server/test/medium/specs/sync/sync-album.spec.ts b/server/test/medium/specs/sync/sync-album.spec.ts index 591d7e1f3c..02536e3a8d 100644 --- a/server/test/medium/specs/sync/sync-album.spec.ts +++ b/server/test/medium/specs/sync/sync-album.spec.ts @@ -217,7 +217,7 @@ describe(SyncRequestType.AlbumsV1, () => { await ctx.syncAckAll(auth, response); await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumsV1]); - await albumUserRepo.delete({ albumsId: album.id, usersId: auth.user.id }); + await albumUserRepo.delete({ albumId: album.id, userId: auth.user.id }); const newResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumsV1]); expect(newResponse).toEqual([ { diff --git a/server/test/repositories/access.repository.mock.ts b/server/test/repositories/access.repository.mock.ts index 50db983cba..208b09c120 100644 --- a/server/test/repositories/access.repository.mock.ts +++ b/server/test/repositories/access.repository.mock.ts @@ -65,5 +65,9 @@ export const newAccessRepositoryMock = (): IAccessRepositoryMock => { tag: { checkOwnerAccess: vitest.fn().mockResolvedValue(new Set()), }, + + workflow: { + checkOwnerAccess: vitest.fn().mockResolvedValue(new Set()), + }, }; }; diff --git a/server/test/repositories/config.repository.mock.ts b/server/test/repositories/config.repository.mock.ts index e31e1a3348..656027fab5 100644 --- a/server/test/repositories/config.repository.mock.ts +++ b/server/test/repositories/config.repository.mock.ts @@ -72,6 +72,7 @@ const envData: EnvData = { root: '/build/www', indexHtml: '/build/www/index.html', }, + corePlugin: '/build/corePlugin', }, storage: { @@ -86,6 +87,11 @@ const envData: EnvData = { workers: [ImmichWorker.Api, ImmichWorker.Microservices], + plugins: { + enabled: true, + installFolder: '/app/data/plugins', + }, + noColor: false, }; diff --git a/server/test/repositories/crypto.repository.mock.ts b/server/test/repositories/crypto.repository.mock.ts index 1167923c0c..773891206e 100644 --- a/server/test/repositories/crypto.repository.mock.ts +++ b/server/test/repositories/crypto.repository.mock.ts @@ -13,5 +13,7 @@ export const newCryptoRepositoryMock = (): Mocked Buffer.from(`${input.toString()} (hashed)`)), hashFile: vitest.fn().mockImplementation((input) => `${input} (file-hashed)`), randomBytesAsText: vitest.fn().mockReturnValue(Buffer.from('random-bytes').toString('base64')), + signJwt: vitest.fn().mockReturnValue('mock-jwt-token'), + verifyJwt: vitest.fn().mockImplementation((token) => ({ verified: true, token })), }; }; diff --git a/server/test/repositories/database.repository.mock.ts b/server/test/repositories/database.repository.mock.ts index 3664730be2..0ff869ca28 100644 --- a/server/test/repositories/database.repository.mock.ts +++ b/server/test/repositories/database.repository.mock.ts @@ -19,6 +19,7 @@ export const newDatabaseRepositoryMock = (): Mocked() => Promise) => function_()), tryLock: vitest.fn(), isBusy: vitest.fn(), diff --git a/server/test/repositories/storage.repository.mock.ts b/server/test/repositories/storage.repository.mock.ts index 9752a39441..31451da82f 100644 --- a/server/test/repositories/storage.repository.mock.ts +++ b/server/test/repositories/storage.repository.mock.ts @@ -49,6 +49,7 @@ export const newStorageRepositoryMock = (): Mocked = {}) => ({ userId: newUuid(), pinExpiresAt: newDate(), isPendingSyncReset: false, + appVersion: session.appVersion ?? null, ...session, }); @@ -308,10 +309,44 @@ const assetSidecarWriteFactory = (asset: Partial = {}) => ({ ...asset, }); +const assetOcrFactory = ( + ocr: { + id?: string; + assetId?: string; + x1?: number; + y1?: number; + x2?: number; + y2?: number; + x3?: number; + y3?: number; + x4?: number; + y4?: number; + boxScore?: number; + textScore?: number; + text?: string; + } = {}, +) => ({ + id: newUuid(), + assetId: newUuid(), + x1: 0.1, + y1: 0.2, + x2: 0.3, + y2: 0.2, + x3: 0.3, + y3: 0.4, + x4: 0.1, + y4: 0.4, + boxScore: 0.95, + textScore: 0.92, + text: 'Sample Text', + ...ocr, +}); + export const factory = { activity: activityFactory, apiKey: apiKeyFactory, asset: assetFactory, + assetOcr: assetOcrFactory, auth: authFactory, authApiKey: authApiKeyFactory, authUser: authUserFactory, diff --git a/server/test/utils.ts b/server/test/utils.ts index c23341d64c..d584cf5398 100644 --- a/server/test/utils.ts +++ b/server/test/utils.ts @@ -41,12 +41,15 @@ import { MetadataRepository } from 'src/repositories/metadata.repository'; import { MoveRepository } from 'src/repositories/move.repository'; import { NotificationRepository } from 'src/repositories/notification.repository'; import { OAuthRepository } from 'src/repositories/oauth.repository'; +import { OcrRepository } from 'src/repositories/ocr.repository'; import { PartnerRepository } from 'src/repositories/partner.repository'; import { PersonRepository } from 'src/repositories/person.repository'; +import { PluginRepository } from 'src/repositories/plugin.repository'; import { ProcessRepository } from 'src/repositories/process.repository'; import { SearchRepository } from 'src/repositories/search.repository'; import { ServerInfoRepository } from 'src/repositories/server-info.repository'; import { SessionRepository } from 'src/repositories/session.repository'; +import { SharedLinkAssetRepository } from 'src/repositories/shared-link-asset.repository'; import { SharedLinkRepository } from 'src/repositories/shared-link.repository'; import { StackRepository } from 'src/repositories/stack.repository'; import { StorageRepository } from 'src/repositories/storage.repository'; @@ -59,6 +62,8 @@ import { TrashRepository } from 'src/repositories/trash.repository'; import { UserRepository } from 'src/repositories/user.repository'; import { VersionHistoryRepository } from 'src/repositories/version-history.repository'; import { ViewRepository } from 'src/repositories/view-repository'; +import { WebsocketRepository } from 'src/repositories/websocket.repository'; +import { WorkflowRepository } from 'src/repositories/workflow.repository'; import { DB } from 'src/schema'; import { AuthService } from 'src/services/auth.service'; import { BaseService } from 'src/services/base.service'; @@ -228,14 +233,17 @@ export type ServiceOverrides = { metadata: MetadataRepository; move: MoveRepository; notification: NotificationRepository; + ocr: OcrRepository; oauth: OAuthRepository; partner: PartnerRepository; person: PersonRepository; + plugin: PluginRepository; process: ProcessRepository; search: SearchRepository; serverInfo: ServerInfoRepository; session: SessionRepository; sharedLink: SharedLinkRepository; + sharedLinkAsset: SharedLinkAssetRepository; stack: StackRepository; storage: StorageRepository; sync: SyncRepository; @@ -247,6 +255,8 @@ export type ServiceOverrides = { user: UserRepository; versionHistory: VersionHistoryRepository; view: ViewRepository; + websocket: WebsocketRepository; + workflow: WorkflowRepository; }; type As = T extends RepositoryInterface ? U : never; @@ -298,15 +308,18 @@ export const newTestService = ( metadata: newMetadataRepositoryMock(), move: automock(MoveRepository, { strict: false }), notification: automock(NotificationRepository), + ocr: automock(OcrRepository, { strict: false }), oauth: automock(OAuthRepository, { args: [loggerMock] }), partner: automock(PartnerRepository, { strict: false }), person: automock(PersonRepository, { strict: false }), + plugin: automock(PluginRepository, { strict: true }), process: automock(ProcessRepository), search: automock(SearchRepository, { strict: false }), // eslint-disable-next-line no-sparse-arrays serverInfo: automock(ServerInfoRepository, { args: [, loggerMock], strict: false }), session: automock(SessionRepository), sharedLink: automock(SharedLinkRepository), + sharedLinkAsset: automock(SharedLinkAssetRepository), stack: automock(StackRepository), storage: newStorageRepositoryMock(), sync: automock(SyncRepository), @@ -320,6 +333,9 @@ export const newTestService = ( user: automock(UserRepository, { strict: false }), versionHistory: automock(VersionHistoryRepository), view: automock(ViewRepository), + // eslint-disable-next-line no-sparse-arrays + websocket: automock(WebsocketRepository, { args: [, loggerMock], strict: false }), + workflow: automock(WorkflowRepository, { strict: true }), }; const sut = new Service( @@ -350,13 +366,16 @@ export const newTestService = ( overrides.move || (mocks.move as As), overrides.notification || (mocks.notification as As), overrides.oauth || (mocks.oauth as As), + overrides.ocr || (mocks.ocr as As), overrides.partner || (mocks.partner as As), overrides.person || (mocks.person as As), + overrides.plugin || (mocks.plugin as As), overrides.process || (mocks.process as As), overrides.search || (mocks.search as As), overrides.serverInfo || (mocks.serverInfo as As), overrides.session || (mocks.session as As), overrides.sharedLink || (mocks.sharedLink as As), + overrides.sharedLinkAsset || (mocks.sharedLinkAsset as As), overrides.stack || (mocks.stack as As), overrides.storage || (mocks.storage as As), overrides.sync || (mocks.sync as As), @@ -368,6 +387,8 @@ export const newTestService = ( overrides.user || (mocks.user as As), overrides.versionHistory || (mocks.versionHistory as As), overrides.view || (mocks.view as As), + overrides.websocket || (mocks.websocket as As), + overrides.workflow || (mocks.workflow as As), ); return { diff --git a/web/.nvmrc b/web/.nvmrc index e2228113dd..0a492611a0 100644 --- a/web/.nvmrc +++ b/web/.nvmrc @@ -1 +1 @@ -22.19.0 +24.11.0 diff --git a/web/eslint.config.js b/web/eslint.config.js index d2ef7631e7..f8e6cdd9c6 100644 --- a/web/eslint.config.js +++ b/web/eslint.config.js @@ -121,6 +121,8 @@ export default typescriptEslint.config( 'unicorn/filename-case': 'off', 'unicorn/prefer-top-level-await': 'off', 'unicorn/import-style': 'off', + 'unicorn/no-array-sort': 'off', + 'unicorn/no-for-loop': 'off', 'svelte/button-has-type': 'error', '@typescript-eslint/await-thenable': 'error', '@typescript-eslint/no-floating-promises': 'error', diff --git a/web/mise.toml b/web/mise.toml new file mode 100644 index 0000000000..5aca2d737d --- /dev/null +++ b/web/mise.toml @@ -0,0 +1,62 @@ +[tasks.install] +run = "pnpm install --filter immich-web --frozen-lockfile" + +[tasks."svelte-kit-sync"] +env._.path = "./node_modules/.bin" +run = "svelte-kit sync" + +[tasks.build] +env._.path = "./node_modules/.bin" +run = "vite build" + +[tasks."build-stats"] +env.BUILD_STATS = "true" +env._.path = "./node_modules/.bin" +run = "vite build" + +[tasks.preview] +env._.path = "./node_modules/.bin" +run = "vite preview" + +[tasks.start] +env._.path = "./node_modules/.bin" +run = "vite dev --host 0.0.0.0 --port 3000" + +[tasks.test] +depends = ["svelte-kit-sync"] +env._.path = "./node_modules/.bin" +run = "vitest" + +[tasks.format] +env._.path = "./node_modules/.bin" +run = "prettier --check ." + +[tasks."format-fix"] +env._.path = "./node_modules/.bin" +run = "prettier --write ." + +[tasks.lint] +env._.path = "./node_modules/.bin" +run = "eslint . --max-warnings 0 --concurrency 4" + +[tasks."lint-fix"] +run = { task = "lint --fix" } + +[tasks.check] +depends = ["svelte-kit-sync"] +env._.path = "./node_modules/.bin" +run = "tsc --noEmit" + +[tasks."check-svelte"] +depends = ["svelte-kit-sync"] +env._.path = "./node_modules/.bin" +run = "svelte-check --no-tsconfig --fail-on-warnings" + +[tasks.checklist] +run = [ + { task = ":install" }, + { task = ":format" }, + { task = ":check" }, + { task = ":test --run" }, + { task = ":lint" }, +] diff --git a/web/package.json b/web/package.json index 2cf47d1b7d..2a93230c24 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "immich-web", - "version": "1.143.0", + "version": "2.2.3", "license": "GNU Affero General Public License version 3", "type": "module", "scripts": { @@ -14,12 +14,11 @@ "check:watch": "npm run check:svelte -- --watch", "check:code": "npm run format && npm run lint:p && npm run check:svelte && npm run check:typescript", "check:all": "npm run check:code && npm run test:cov", - "lint": "eslint . --max-warnings 0", - "lint:p": "eslint-p . --max-warnings 0 --concurrency=4", + "lint": "eslint . --max-warnings 0 --concurrency 4", "lint:fix": "npm run lint -- --fix", "format": "prettier --check .", "format:fix": "prettier --write . && npm run format:i18n", - "format:i18n": "pnpx sort-json ../i18n/*.json", + "format:i18n": "pnpm dlx sort-json ../i18n/*.json", "test": "vitest --run", "test:cov": "vitest --coverage", "test:watch": "vitest dev", @@ -27,8 +26,9 @@ }, "dependencies": { "@formatjs/icu-messageformat-parser": "^2.9.8", + "@immich/justified-layout-wasm": "^0.4.3", "@immich/sdk": "file:../open-api/typescript-sdk", - "@immich/ui": "^0.29.0", + "@immich/ui": "^0.43.0", "@mapbox/mapbox-gl-rtl-text": "0.2.3", "@mdi/js": "^7.4.47", "@photo-sphere-viewer/core": "^5.11.5", @@ -45,7 +45,7 @@ "geo-coordinates-parser": "^1.7.4", "geojson": "^0.5.0", "handlebars": "^4.7.8", - "happy-dom": "^18.0.1", + "happy-dom": "^20.0.0", "intl-messageformat": "^10.7.11", "justified-layout": "^4.1.0", "lodash-es": "^4.17.21", @@ -57,20 +57,20 @@ "socket.io-client": "~4.8.0", "svelte-gestures": "^5.2.2", "svelte-i18n": "^4.0.1", - "svelte-maplibre": "^1.2.0", + "svelte-maplibre": "^1.2.5", "svelte-persisted-store": "^0.12.0", "tabbable": "^6.2.0", "thumbhash": "^0.1.1" }, "devDependencies": { - "@eslint/js": "^9.18.0", + "@eslint/js": "^9.36.0", "@faker-js/faker": "^10.0.0", "@koddsson/eslint-plugin-tscompat": "^0.2.0", "@socket.io/component-emitter": "^3.1.0", "@sveltejs/adapter-static": "^3.0.8", "@sveltejs/enhanced-img": "^0.8.0", "@sveltejs/kit": "^2.27.1", - "@sveltejs/vite-plugin-svelte": "6.2.0", + "@sveltejs/vite-plugin-svelte": "6.2.1", "@tailwindcss/vite": "^4.1.7", "@testing-library/jest-dom": "^6.4.2", "@testing-library/svelte": "^5.2.8", @@ -83,30 +83,29 @@ "@types/qrcode": "^1.5.5", "@vitest/coverage-v8": "^3.0.0", "dotenv": "^17.0.0", - "eslint": "^9.18.0", + "eslint": "^9.36.0", "eslint-config-prettier": "^10.1.8", - "eslint-p": "^0.26.0", "eslint-plugin-compat": "^6.0.2", - "eslint-plugin-svelte": "^3.9.0", - "eslint-plugin-unicorn": "^60.0.0", + "eslint-plugin-svelte": "^3.12.4", + "eslint-plugin-unicorn": "^61.0.2", "factory.ts": "^1.4.1", "globals": "^16.0.0", - "happy-dom": "^18.0.1", + "happy-dom": "^20.0.0", "prettier": "^3.4.2", "prettier-plugin-organize-imports": "^4.0.0", "prettier-plugin-sort-json": "^4.1.1", "prettier-plugin-svelte": "^3.3.3", "rollup-plugin-visualizer": "^6.0.0", - "svelte": "5.38.10", + "svelte": "5.43.0", "svelte-check": "^4.1.5", - "svelte-eslint-parser": "^1.2.0", + "svelte-eslint-parser": "^1.3.3", "tailwindcss": "^4.1.7", "typescript": "^5.8.3", - "typescript-eslint": "^8.28.0", + "typescript-eslint": "^8.45.0", "vite": "^7.1.2", "vitest": "^3.0.0" }, "volta": { - "node": "22.19.0" + "node": "24.11.0" } } diff --git a/web/src/lib/actions/__test__/focus-trap.spec.ts b/web/src/lib/actions/__test__/focus-trap.spec.ts index b03064a91d..c4d43dbc71 100644 --- a/web/src/lib/actions/__test__/focus-trap.spec.ts +++ b/web/src/lib/actions/__test__/focus-trap.spec.ts @@ -24,7 +24,7 @@ describe('focusTrap action', () => { it('supports backward focus wrapping', async () => { render(FocusTrapTest, { show: true }); await tick(); - await user.keyboard('{Shift>}{Tab}{/Shift}'); + await user.keyboard('{Shift}{Tab}{/Shift}'); expect(document.activeElement).toEqual(screen.getByTestId('three')); }); diff --git a/web/src/lib/actions/focus-trap.ts b/web/src/lib/actions/focus-trap.ts index 2b03282c2d..9c7b7b3bd2 100644 --- a/web/src/lib/actions/focus-trap.ts +++ b/web/src/lib/actions/focus-trap.ts @@ -1,4 +1,3 @@ -import { shortcuts } from '$lib/actions/shortcut'; import { getTabbable } from '$lib/utils/focus-util'; import { tick } from 'svelte'; @@ -12,6 +11,24 @@ interface Options { export function focusTrap(container: HTMLElement, options?: Options) { const triggerElement = document.activeElement; + // Create sentinel nodes + const startSentinel = document.createElement('div'); + startSentinel.setAttribute('tabindex', '0'); + startSentinel.dataset.focusTrap = 'start'; + + const backupSentinel = document.createElement('div'); + backupSentinel.setAttribute('tabindex', '-1'); + backupSentinel.dataset.focusTrap = 'backup'; + + const endSentinel = document.createElement('div'); + endSentinel.setAttribute('tabindex', '0'); + endSentinel.dataset.focusTrap = 'end'; + + // Insert sentinel nodes into the container + container.insertBefore(startSentinel, container.firstChild); + container.insertBefore(backupSentinel, startSentinel.nextSibling); + container.append(endSentinel); + const withDefaults = (options?: Options) => { return { active: options?.active ?? true, @@ -19,11 +36,19 @@ export function focusTrap(container: HTMLElement, options?: Options) { }; const setInitialFocus = async () => { - const focusableElement = getTabbable(container, false)[0]; + // Use tick() to ensure focus trap works correctly inside + await tick(); + + // Get focusable elements, excluding our sentinel nodes + const allTabbable = getTabbable(container, false); + const focusableElement = allTabbable.find((el) => !Object.hasOwn(el.dataset, 'focusTrap')); + if (focusableElement) { - // Use tick() to ensure focus trap works correctly inside - await tick(); - focusableElement?.focus(); + focusableElement.focus(); + } else { + backupSentinel.setAttribute('tabindex', '-1'); + // No focusable elements found, use backup sentinel as fallback + backupSentinel.focus(); } }; @@ -32,39 +57,56 @@ export function focusTrap(container: HTMLElement, options?: Options) { } const getFocusableElements = () => { - const focusableElements = getTabbable(container); + // Get all tabbable elements except our sentinel nodes + const allTabbable = getTabbable(container); + const focusableElements = allTabbable.filter((el) => !Object.hasOwn(el.dataset, 'focusTrap')); + return [ focusableElements.at(0), // focusableElements.at(-1), ]; }; - const { destroy: destroyShortcuts } = shortcuts(container, [ - { - ignoreInputFields: false, - preventDefault: false, - shortcut: { key: 'Tab' }, - onShortcut: (event) => { - const [firstElement, lastElement] = getFocusableElements(); - if (document.activeElement === lastElement && withDefaults(options).active) { - event.preventDefault(); - firstElement?.focus(); - } - }, - }, - { - ignoreInputFields: false, - preventDefault: false, - shortcut: { key: 'Tab', shift: true }, - onShortcut: (event) => { - const [firstElement, lastElement] = getFocusableElements(); - if (document.activeElement === firstElement && withDefaults(options).active) { - event.preventDefault(); - lastElement?.focus(); - } - }, - }, - ]); + // Add focus event listeners to sentinel nodes + const handleStartFocus = () => { + if (withDefaults(options).active) { + const [, lastElement] = getFocusableElements(); + // If no elements, stay on backup sentinel + if (lastElement) { + lastElement.focus(); + } else { + backupSentinel.focus(); + } + } + }; + + const handleBackupFocus = () => { + // Backup sentinel keeps focus when there are no other focusable elements + if (withDefaults(options).active) { + const [firstElement] = getFocusableElements(); + // Only move focus if there are actual focusable elements + if (firstElement) { + firstElement.focus(); + } + // Otherwise, focus stays on backup sentinel + } + }; + + const handleEndFocus = () => { + if (withDefaults(options).active) { + const [firstElement] = getFocusableElements(); + // If no elements, move to backup sentinel + if (firstElement) { + firstElement.focus(); + } else { + backupSentinel.focus(); + } + } + }; + + startSentinel.addEventListener('focus', handleStartFocus); + backupSentinel.addEventListener('focus', handleBackupFocus); + endSentinel.addEventListener('focus', handleEndFocus); return { update(newOptions?: Options) { @@ -74,7 +116,16 @@ export function focusTrap(container: HTMLElement, options?: Options) { } }, destroy() { - destroyShortcuts?.(); + // Remove event listeners + startSentinel.removeEventListener('focus', handleStartFocus); + backupSentinel.removeEventListener('focus', handleBackupFocus); + endSentinel.removeEventListener('focus', handleEndFocus); + + // Remove sentinel nodes from DOM + startSentinel.remove(); + backupSentinel.remove(); + endSentinel.remove(); + if (triggerElement instanceof HTMLElement) { triggerElement.focus(); } diff --git a/web/src/lib/components/ActionButton.svelte b/web/src/lib/components/ActionButton.svelte new file mode 100644 index 0000000000..37bc09fb73 --- /dev/null +++ b/web/src/lib/components/ActionButton.svelte @@ -0,0 +1,16 @@ + + +{#if action.$if?.() ?? true} + +{/if} diff --git a/web/src/lib/components/ApiKeyPermissionsPicker.svelte b/web/src/lib/components/ApiKeyPermissionsPicker.svelte new file mode 100644 index 0000000000..ecdb68b038 --- /dev/null +++ b/web/src/lib/components/ApiKeyPermissionsPicker.svelte @@ -0,0 +1,78 @@ + + +